最近在学习go语言,把之前picbed的命令行cli.py用go重写,在编写过程中,go实现相应功能走了些 弯路,在本文拎出来分享给大家(新手)。

三端复制剪贴板

三端指的是Windows、Linux、macOS,实现用命令复制内容到系统剪贴板。

Windows

clip
echo hello world | clip

Linux

xclip
echo "hello world" | xclip -selection clipboard

macOS

pbcopy
echo "hello world" | pbcopy

使用golang调用带有管道符的命令

os/execsh -ccmd.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桌面消息通知

powershell xxx.ps1  <Sub-Title></code></pre>
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

以picbed-cli的发行版为例, 我已经编译了macOS版本的压缩包,在release中有附件,压缩包里只有一个picbed-cli可执行文件。

打开macOS终端,执行命令:

brew create 程序包网络下载地址
xxx.rbdef installsystem ./configurebin.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

示例内容保存为xxx.rb提交到你的git仓库 homebrew-XXX 中

更新程序版本

只需要编辑git仓库homebrew-XXX下的xxx.rb,修改url为新版本压缩包路径、sha256为新版本压缩包 sha256值,再提交后即可。

brew update && brew upgrade picbed-cli

其他经验补充说明

ldflags -X
-i/--infoldflag -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"

均失败!

-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 module

此刻,好想吐槽(自己?)

废话太多了,分享的大概意思就是:

go modulego build -ldflag -Xpackage_pathmodulego build -ldflag -X module/package_path.variable_name=value
·End·