最近在学习go语言,把之前picbed的命令行cli.py用go重写,在编写过程中,go实现相应功能走了些 弯路,在本文拎出来分享给大家(新手)。
三端复制剪贴板
三端指的是Windows、Linux、macOS,实现用命令复制内容到系统剪贴板。
Windows
cmd内置的 clip
命令实现复制剪贴板,示例:
echo hello world | clip
Linux
需要安装第三方软件包 xclip
,用于桌面环境,
测试支持Deepin、Fedora、Manjaro、Ubuntu、CentOS,
其他系统未测试,示例:
echo "hello world" | xclip -selection clipboard
macOS
内置的 pbcopy
命令可以实现复制到系统剪贴板,示例:
echo "hello world" | pbcopy
使用golang调用带有管道符的命令
go执行系统命令使用 os/exec
标准库,不过当有管道符的时候就有点麻烦了,这时候google找到了
捷径,Linux、macOS使用 sh -c
,而Windows使用 cmd.exe /C
,示例:
Linux/macOS
package main
import "fmt"
import "os/exec"
func main() {
content := "hello world"
// if macOS
// cmd := fmt.Sprintf(`echo "%s" | pbcopy`, content)
cmd := fmt.Sprintf(`echo "%s" | xclip -selection clipboard`, content)
err := exec.Command("bash", "-c", cmd).Run()
if err != nil {
fmt.Println("exec error")
}
}
Windows
package main
import (
"fmt"
"strings"
"os/exec"
)
func main() {
content := "hello world"
cmd := fmt.Sprintf(`echo %s | clip`, strings.ReplaceAll(content, "\n", "\\n"))
err := exec.Command("cmd.exe", "/C", cmd).Run()
if err != nil {
fmt.Println("exec error")
}
}
使用golang调用powershell触发Windows10桌面消息通知
找到一个Win10桌面通知的脚本不容易,得用powershell调起,以下内容保存为xxx.ps1,执行它
通过 powershell xxx.ps1 <Title> <Sub-Title>
param(
[String] $Title,
[String] $SubTitle
)
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
$APP_ID = '110366bd-56e2-47ed-9bdf-3ce1fa408b6c'
$template = @"
<toast>
<visual>
<binding template="ToastText02">
<text id="1">$($Title)</text>
<text id="2">$($SubTitle)</text>
</binding>
</visual>
</toast>
"@
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast)
在go程序中想调用它,我一时没想到别的方法,暂时思路是把内容保存为临时文件,再用 os/exec 调用, 如下示例(没有错误处理):
package main
import (
"fmt"
"os"
"os/exec"
"io/ioutil"
)
func main() {
sf := genTmpPS1()
exec.Command(
"powershell", "-ExecutionPolicy", "Unrestricted", sf,
"上传成功", "已复制到剪贴板",
).Run()
}
func genTmpPS1() (filepath string) {
tpl := []byte(`
param(
[String] $Title,
[String] $SubTitle
)
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
$APP_ID = '110366bd-56e2-47ed-9bdf-3ce1fa408b6c'
$template = @"
<toast>
<visual>
<binding template="ToastText02">
<text id="1">$($Title)</text>
<text id="2">$($SubTitle)</text>
</binding>
</visual>
</toast>
"@
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast)
`)
tmpfile, err := ioutil.TempFile(os.TempDir(), "*.ps1")
tmpfile.Write(tpl)
tmpfile.Close()
return tmpfile.Name()
}
使用homebrew安装golang编译的可执行程序
go程序编译完成后就一个二进制的可执行文件,在macOS系统中,可以自建一个Tap方便homebrew安装。
相关概念可以参考 使用 Homebrew 维护自己的软件仓库 这篇文章,这里不再赘述。
新发布程序
当你的程序是第一次发布时,需要先创建一个 homebrew-XXX
的git仓库,比如我的
staugur/homebrew-tap
以picbed-cli的发行版为例, 我已经编译了macOS版本的压缩包,在release中有附件,压缩包里只有一个picbed-cli可执行文件。
打开macOS终端,执行命令:
brew create 程序包网络下载地址
下载包,然后提示ENTER回车直接使用编辑器进入编辑 xxx.rb
文件,修改下desc、homepage两个
描述信息,修改 def install
把 system ./configure
那几行注释,增加 bin.install
,
参考示例:
class PicbedCli < Formula
desc "picbed client cli"
homepage "https://github.com/staugur/picbed-cli"
url "https://static.saintic.com/download/picbed-cli/picbed-cli.0.4.2-darwin-amd64.tar.gz"
sha256 "c33ae9aae32273e9ea681eff904bfbae912753e7afa0175ad69765fe17b002ff"
def install
bin.install "picbed-cli"
end
test do
system "false"
end
end
其实也可以不需要 brew create
命令,参照上述示例填写,把压缩包的地址和sha256填上即可。
示例内容保存为xxx.rb提交到你的git仓库 homebrew-XXX 中
更新程序版本
只需要编辑git仓库homebrew-XXX下的xxx.rb,修改url为新版本压缩包路径、sha256为新版本压缩包 sha256值,再提交后即可。
使用 brew update && brew upgrade picbed-cli
执行版本更新。
其他经验补充说明
1、使用ldflags -X
设置额外信息
在picbed-cli开发中有一个 -i/--info
选项使用的变量是编译时 ldflag -X
动态传入的:
go build -ldflags "-s -w -X main.commitID=xxx -X main.built=xxx"
这个程序比较简单,就一个main包,传入没什么问题。
不过有时候你可能想往子包中传值,参考这篇文章:使用ldflags设置Go应用程序的版本信息
假如你的程序目录结构如下:
rtfd
├── cmd
│ └── root.go
├── go.mod
├── main.go
├── Makefile
└── VERSION
其中main.go是main包,cmd下go文件属于cmd包,按照上面文章应该这么传值:
$ go build -ldflags "-X cmd.commitID=xxx -X cmd.built=xxx"
$ go build -ldflags "-X rtfd/cmd.commitID=xxx -X rtfd/cmd.built=xxx"
均失败!
ldflag格式是:-ldflags="-X 'package_path.variable_name=new_value'"
可能是新手,没搞明白package_path含义,google也是没找到教新的分享, 捣鼓好久突然灵机一动,改为这么传值:
go build -ldflags "-X tcw.im/rtfd/cmd.commitID=xxx -X tcw.im/rtfd/cmd.built=xxx"
行了!
因为我的go项目全都是go module
模式!
此刻,好想吐槽(自己?)
废话太多了,分享的大概意思就是:
使用
go module
模式时,go build -ldflag -X
传递给子包变量时package_path
需要添加module
前缀:
go build -ldflag -X module/package_path.variable_name=value