惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
Jina AI
Jina AI
博客园 - 司徒正美
雷峰网
雷峰网
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园_首页
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
GbyAI
GbyAI
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
I
InfoQ
博客园 - Franky
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
Cyberwarzone
Cyberwarzone
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
P
Privacy & Cybersecurity Law Blog
T
Threatpost
Cloudbric
Cloudbric
D
Docker
M
MIT News - Artificial intelligence
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Vercel News
Vercel News
Martin Fowler
Martin Fowler
J
Java Code Geeks
AWS News Blog
AWS News Blog
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
L
Lohrmann on Cybersecurity
Hacker News: Ask HN
Hacker News: Ask HN
Last Week in AI
Last Week in AI
S
Security @ Cisco Blogs
Help Net Security
Help Net Security
C
Cisco Blogs
V
V2EX
博客园 - 【当耐特】
I
Intezer
爱范儿
爱范儿
F
Fortinet All Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Privacy International News Feed
IT之家
IT之家
L
LINUX DO - 最新话题
B
Blog RSS Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Razeen`s Blog

Let's Encrypt 推出 Gen Y 根证书架构:揭示 Web PKI 的五大未来趋势 通过 Wi-Fi 自动备份你的 iPhone 到 Nas 管理培训感悟:从技术视角看管理之路 UPS 一拖多:保护你的 NAS 和 Linux 服务器 从影音中心到 AI 助手:我的50款 Homelab 服务清单 从SSL证书有效期将缩短到47天聊开去 部署一个自己的 Running Page 谈谈特斯拉 Model Y 用车一年的感受和费用分析 弃用 Disqus 评论,使用自建 Waline 使用 Prometheus 和 Grafana 搭建你的证书监控面板 服务器迁移记:一次磁盘换板引发的Linux分区与挂载的学习 改善信息来源, 利用 RSS 高效获取资讯 (RSShub + Reeder5 + WeWe RSS) 多种 Docker 镜像拉取解决方案与实践 App分享 | AppCleaner - Mac上的卸载神器 Azure OpenAI API 申请和使用 如何开通 OneKey 虚拟信用卡,并充值消费 如何拥有一个可长期在国内使用的国外手机号码 分享开通 ChatGPT Plus 过程 如何开通 Depay 虚拟信用卡,并充值消费 如何开通欧易Web3钱包, 交易入账 如何5分钟内1块钱注册 ChatGPT 如何注册美区的 Apple ID (2023年/无需科学上网) NAS折腾记(11): NASTool3.0体验和降级 NAS折腾记(10): Docker版本的NASTool配置 NAS折腾记(9): NASTool与微信交互,微信发送消息远程下载电影【多图】 NAS折腾记(8):群晖安装 NASTool 实现影音半自动化【多图】 内网穿透(2):Tailscale 组网实现内网穿透,操作简单,无成本 内网穿透(1):总结了11中内网穿透的方式,总有一种适合你 NAS折腾记(7):从零开始设置(黑)群晖系统 NAS折腾记(6):黑群晖系统安装好后怎么洗白? NAS折腾记(5):群晖硬盘休眠设置与分析 NAS折腾记(4): 20分钟手把手带你完美安装 DS918+ 黑群晖7.1.1 NAS折腾记(3):NAS 装机 NAS折腾记(2):B360-ITX 双M.2 双2.5G网口 6 SATA主版 开箱 NAS折腾记(1):328元的4盘位Nas机箱开箱 Openwrt + Clash 全局科学上网 Newifi3 刷入 OpenWrt 固件 v21.02 利用 Markdown 画一些流程图、时序图、甘特图等 Homelab(8): 搭建自用 Gitlab 与 Docker 仓库 Nginx Tcp 转发保留客户端真实 IP (PROXY Protocol) Homelab (7):IPSec VPN(基于证书认证)客户端设置 Homelab (6): 基于自签发证书的 IPSec VPN 搭建 Homelab (5): DDNS 动态域名解析 Homelab (4): Linux 服务器基础环境准备 Homelab (3): 整体网络与基础硬件介绍 Go学习笔记(十)老项目迁移 go module 大型灾难记录 Github Actions 初体验之自动化部署 Hexo 博客 记一次 Nginx DNS 缓存导致转发问题 Homelab (2): 电信悦me网关修改桥接模式,路由器拨号 Homelab (1):5分钟上手黑群晖 NAS 终极 Bash 脚本指南 Typora 自动上传图片到七牛云 Ubuntu 20.04 LTS 有线网卡驱动安装 折腾 Ubuntu 20.04 LTS 开发环境 Go学习笔记(九) 计时器的生命周期[译] 利用 git hook 规范你的代码与 commit message 规范 git commit message 与自动化版本控制 超详细 vim 配置 (with MacVim) Golang 中的 RESTful API 最佳实践 折腾服务器(开篇) 我的第一台个人服务器 Newifi3 实现低成本家庭级科学上网 如何用 Go 调用 Windows API Mac OS 自动根据 WI-FI 名字改变网络位置 关于 Docker 清理 MIME Types 速查表 Go学习笔记(七) | 理解OAuth 2.0并实现一个客户端 我又又又把博客迁移了 Go学习笔记(六) | 使用swaggo自动生成Restful API文档 Go学习笔记(五) | 使用代码片段(snippets)提高编码效率 书单 - 2018 IPFS 初体验,利用 IPFS 托管你的静态网站 记一次 PostgreSQL LIKE 索引优化,联合字段 LIKE 查询优化。 Disqus 添加有趣的 Reactions 的功能 TLS 1.3 详解 (RFC 8446解读) gRPC在Go中的使用(三)gRPC实现TLS加密通信与流模式 gRPC在Go中的使用(二)gRPC实现简单通讯 gRPC在Go中的使用(一)Protocol Buffers语法与相关使用 CentOS 安装 tshark 抓包工具 Go学习笔记(四) | win上使用VSCode搭建Go开发环境 日常 Postgres 数据库点滴记录 简单了解 PKCS 规范 美食篇 | DIY戚风蛋糕(烤箱做蛋糕) Go学习笔记(三) | 怎么写Go基准测试(性能测试) TLS1.3正式更新,为Nginx添加TLS1.3的支持 一次诡异的数据库删除 GitHub Pages自定义域名开启HTTPS 证书透明度是什么?它是怎么工作的? Go学习笔记(二) | 我对 recover 的一点误解 搭建证书透明度(certificate-transparency)日志服务之从入门到放弃 修复远程登陆 Centos 时,出现 UTF-8 Warning HTTPS篇之SSL握手过程详解 Go学习笔记(一) | postgres与golang点点滴滴 AWS 命令行界面(aws-cli)从安装到快速上手 数字证书分类及怎么区分各类数字证书 常用 linux 命令小结(一)文件目录操作 云服务器搭建 hexo 博客,git hooks自动更新 SSH 免密登陆, SSH Config 配置 Golang CGO Mac 交叉编译 Windows 使用 goose 让数据库迁移更加轻松 开始使用Ghost
Go学习笔记(八) | 使用 os/exec 执行命令
2019-05-31 · via Razeen`s Blog

用Go去调用一些外部的命令其实很愉快的,这遍文章就总结一下我自己日常用的比较多的几种方法。

关于Unix标准输入输出

在具体聊os/exec的使用前,了解一下shell的标准输出是很有必要的。

我们平常会用到或看到这样的命令:

$ ls xxx 1>out.txt 2>&1
$ nohup xxx 2>&1 &

你知道这里1,2含义么?

其实这里的1,2指的就是Unix文件描述符。文件描述符其实就一数字,每一个文件描述符代表的都是一个文件。如果你打开100个文件,你就会获取到100个文件描述符。

这里需要注意的一点就是,在Unix中一切皆文件。当然,这里我们不必去深究,我们需要知道的是1,2代表的是标准输出stdout与标准错误输出stderr。还有0代表标准输入stdin

os/exec中就用到了Stdin,Stdout,Stderr,这些基本Unix知识或能帮助我们更好理解这些参数。

os/exec

os/exec包内容并不多,我们大概过一下。

  1. LookPath(file string) (string, error)

    寻找可执行文件路径,如果你指定的可执行文件在$PATH中,就会返回这个可执行文件的相对/绝对路径;如果你指定的是一个文件路径,他就是去判断文件是否可读取/执行,返回的是一样的路径。

    在我们需要使用一些外部命令/可执行文件的时候,我们可以先使用该函数判断一下该命令/可执行文件是否有效。

  2. Command(name string, arg …string) *Cmd

    使用你输入的参数,返回Cmd指针,可用于执行Cmd的方法。

    这里name就是我们的命令/可执行文件,后面的参数可以一个一个输入。

  3. CommandContext(ctx context.Context, name string, arg …string) *Cmd

    和上面功能一样,不过我们可以用上下文做一些超时等控制。

  4. 之后几个就是Cmd的一些方法。

其实读完,结合官方的一些example,使用很简单,下面具体写几个场景。

  1. 本文全部的Demo在这里

  2. ./testcmd/testcmd是我用Go写的一个简单的可执行文件,可以根据指定的参数 输出/延时输出/输出错误,方便我们演示。如下

func main() {

	var (
		start bool
		e     bool
	)

	flag.BoolVar(&start, "s", false, "start output")
	flag.BoolVar(&e, "e", false, "output err")
	flag.Parse()

	if start {
		for i := 5; i > 0; i-- {
			fmt.Fprintln(os.Stdout, "test cmd output", i)
			time.Sleep(1 * time.Second)
		}
	}

	if e {
		fmt.Fprintln(os.Stderr, "a err occur")
		os.Exit(1)
	}

	fmt.Fprintln(os.Stdout, "test cmd stdout")
}

简单执行

// 简单执行
func test1() {
	cmd := exec.Command("./testcmd/testcmd", "-s")

	// 使用CombinedOutput 将stdout stderr合并输出
	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("test1 failed %s\n", err)
	}
	log.Println("test1 output ", string(out))
}

输出:

Run Test 1
2019/06/06 18:02:39 test1 output  test cmd output 5
test cmd output 4
test cmd output 3
test cmd output 2
test cmd output 1
done

整个过程等待5秒,所有结果一次输出。

分离标准输出与错误输出

将错误分开输出,同时开了两个协成,同步的接收命令的输出内容。

// 	stdout & stderr 分开输出
func test2() {
	cmd := exec.Command("./testcmd/testcmd", "-s", "-e")
	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()
	cmd.Start()

	go func() {
		for {
			buf := make([]byte, 1024)
			n, err := stderr.Read(buf)

			if n > 0 {
				log.Printf("read err %s", string(buf[:n]))
			}

			if n == 0 {
				break
			}

			if err != nil {
				log.Printf("read err %v", err)
				return
			}
		}
	}()

	go func() {
		for {
			buf := make([]byte, 1024)
			n, err := stdout.Read(buf)

			if n == 0 {
				break
			}

			if n > 0 {
				log.Printf("read out %s", string(buf[:n]))

			}

			if n == 0 {
				break
			}

			if err != nil {
				log.Printf("read out %v", err)
				return
			}

		}
	}()

	err := cmd.Wait()
	if err != nil {
		log.Printf("cmd wait %v", err)
		return
	}
}

输出:

Run Test 2
2019/06/06 18:02:39 read out test cmd output 5
2019/06/06 18:02:40 read out test cmd output 4
2019/06/06 18:02:41 read out test cmd output 3
2019/06/06 18:02:42 read out test cmd output 2
2019/06/06 18:02:43 read out test cmd output 1
2019/06/06 18:02:44 read err a err occur
2019/06/06 18:02:44 cmd wait exit status 1

按行读取输出内容

使用bufio按行读取输出内容。

// 按行读输出的内容
func test3() {
	cmd := exec.Command("./testcmd/testcmd", "-s", "-e")
	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()
	oReader := bufio.NewReader(stdout)
	eReader := bufio.NewReader(stderr)

	cmd.Start()

	go func() {
		for {
			line, err := oReader.ReadString('\n')

			if line != "" {
				log.Printf("read line %s", line)
			}

			if err != nil || line == "" {
				log.Printf("read line err %v", err)
				return
			}

		}
	}()

	go func() {
		for {
			line, err := eReader.ReadString('\n')

			if line != "" {
				log.Printf("read err %s", line)
			}

			if err != nil || line == "" {
				log.Printf("read err %v", err)
				return
			}

		}
	}()

	err := cmd.Wait()
	if err != nil {
		log.Printf("cmd wait %v", err)
		return
	}
}

输出:

Run Test 3
2019/06/06 18:06:44 read line test cmd output 5
2019/06/06 18:06:45 read line test cmd output 4
2019/06/06 18:06:46 read line test cmd output 3
2019/06/06 18:06:47 read line test cmd output 2
2019/06/06 18:06:48 read line test cmd output 1
2019/06/06 18:06:49 read err a err occur
2019/06/06 18:06:49 cmd wait exit status 1

设置执行超时时间

有时候我们要控制命令的执行时间,这是就可以使用上下文去控制了。

// 通过上下文控制超时
func test4() {

	ctx, calcel := context.WithTimeout(context.Background(), 2*time.Second)
	defer calcel()

	cmd := exec.CommandContext(ctx, "./testcmd/testcmd", "-s", "-e")

	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()
	oReader := bufio.NewReader(stdout)
	eReader := bufio.NewReader(stderr)

	cmd.Start()

	go func() {
		for {
			line, err := oReader.ReadString('\n')

			if line != "" {
				log.Printf("read line %s", line)
			}

			if err != nil || line == "" {
				log.Printf("read line err %v", err)
				return
			}

		}
	}()

	go func() {
		for {
			line, err := eReader.ReadString('\n')

			if line != "" {
				log.Printf("read err %s", line)
			}

			if err != nil || line == "" {
				log.Printf("read err %v", err)
				return
			}

		}
	}()

	err := cmd.Wait()
	if err != nil {
		log.Printf("cmd wait %v", err)
		return
	}
}

输出:

Run Test 4
2019/06/06 18:06:49 read line err EOF
2019/06/06 18:06:49 read err EOF
2019/06/06 18:06:49 read line test cmd output 5
2019/06/06 18:06:50 read line test cmd output 4
2019/06/06 18:06:51 read line err EOF
2019/06/06 18:06:51 read err EOF
2019/06/06 18:06:51 cmd wait signal: killed

持续输入指令,交互模式

有很多命令支持交互模式,我们进入之后就可以持续的输入一些命令,同时获取输出。如openssl命令。

下面我们需要进入交换模式,执行输入三个命令,并获取输出。

// 持续输入
func test5() {
	cmd := exec.Command("openssl")

	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()

	stdin, _ := cmd.StdinPipe()

	cmd.Start()

	// 读
	var wg sync.WaitGroup
	wg.Add(3)
	go func() {
		defer wg.Done()
		for {
			buf := make([]byte, 1024)
			n, err := stderr.Read(buf)

			if n > 0 {
				fmt.Println(string(buf[:n]))
			}

			if n == 0 {
				break
			}

			if err != nil {
				log.Printf("read err %v", err)
				return
			}
		}
	}()

	go func() {
		defer wg.Done()
		for {
			buf := make([]byte, 1024)
			n, err := stdout.Read(buf)

			if n == 0 {
				break
			}

			if n > 0 {
				fmt.Println(string(buf[:n]))
			}

			if n == 0 {
				break
			}

			if err != nil {
				log.Printf("read out %v", err)
				return
			}

		}
	}()

	// 写
	go func() {
		stdin.Write([]byte("version\n\n"))
		stdin.Write([]byte("ciphers -v\n\n"))
		stdin.Write([]byte("s_client -connect razeen.me:443"))
		stdin.Close()
		wg.Done()
	}()

	wg.Wait()
	err := cmd.Wait()
	if err != nil {
		log.Printf("cmd wait %v", err)
		return
	}

}

这里,我们就用到了stdin标准输入了。输出如下:

Run Test 5
OpenSSL> LibreSSL 2.6.5
OpenSSL> OpenSSL>
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-RSA-AES256-SHA    SSLv3 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA256
DES-CBC-SHA             SSLv3 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=SHA1
...
OpenSSL> OpenSSL>
4466583148:error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:1205:SSL alert number 40
4466583148:error:140040E5:SSL routines:CONNECT_CR_SRVR_HELLO:ssl handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-22.260.1/libressl-2.6/ssl/ssl_pkt.c:585:

CONNECTED(00000005)
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Start Time: 1559815613
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

—END—