在本教程中,我们将介绍在 golang 中执行 shell
命令的多种方法和场景。
使用 exec.Command()
运行简单的 shell 命令
这是一个简单的 golang 代码,它使用 exec.Command()
函数打印当前目录的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "fmt" "os/exec" )
func main() { cmd := exec.Command("ls") out, err := cmd.Output()
if err != nil { panic(err) }
fmt.Println(string(out)) }
|
如果要将参数传递给命令,可以将它们作为附加参数包含在
exec.Command()
. 例如,要运行
ls -l -a
,您可以使用:
1 2 3
|
cmd := exec.Command("ls", "-l", "-a")
|
是否可以在不存储输出的情况下执行shell命令?
如果您需要仅执行某些 shell 命令而不存储输出,那么我们可以使用
Run()
函数而不是 Output()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "os/exec" )
func main() { cmd := exec.Command("/bin/bash", "-c", "ls")
err := cmd.Run()
if err != nil { panic(err) } }
|
该代码不会产生任何输出,它只会触发 ls
命令并退出。
为什么我们不应该使用
exec.Command() 函数?
虽然 exec.Command()
可以让我们执行 shell
命令,但是我们应该尽量避免 exec.Command()
,原因有多种:
- 安全风险:如果没有正确清理,传递给的参数
exec.Command
可能容易受到命令注入攻击。
- 资源使用:
exec.Command
为每个命令创建一个新进程,这可能会占用大量资源并导致性能不佳。
- 有限控制:
exec.Command
将命令作为单独的进程启动并立即返回,这意味着命令运行后您对其的控制权有限。
- 错误处理:如果
exec.Command
执行的命令以非零状态代码退出,则返回错误,但不提供有关错误的详细信息。
- 不可预测的行为:当命令在不同平台上运行或环境发生变化时,可能会出现意外的行为。
- 有限的互操作性:当您需要在默认 shell 之外的不同 shell
中运行命令时,这不是最佳选择。
虽然 exec.Command
对于运行简单的 shell
命令很有用,但对于更复杂的命令或当您需要对命令执行进行更多控制时,它可能不是最佳选择。
您可以尝试考虑使用其他库(例如
Cobra)来处理应用程序中的命令行参数和命令。
在后台执行 shell
命令并等待其完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "fmt" "os/exec" )
func main() { cmd := exec.Command("sleep", "10") fmt.Println("Starting now!") err := cmd.Start()
if err != nil { panic(err) }
err = cmd.Wait() fmt.Println("Completed..") if err != nil { panic(err) } }
|
输出:
1 2
| Starting now! Completed..
|
使用上下文执行 shell 命令
我们还可以使用 os/exec
包的 CommandContext
功能,它允许传递上下文并将参数作为字符串切片传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import ( "context" "fmt" "os/exec" )
func main() { ctx := context.Background() cmd := exec.CommandContext(ctx, "ls", "-l", "-a") out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
这里的 context
可以用于取消命令的执行(使用
context.WithCancel()
即可)。
如何将变量传递给 shell 命令?
我们可能还需要将变量从 golang 代码传递到 shell
命令作为输入参数。这需要一些额外的处理,这里有一些可能的方法。
方法 1:传递变量作为输入参数
我们可以将变量作为输入参数传递给 exec.Command()
如下例所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "fmt" "os/exec" )
func main() { message := "Hello, World!" cmd := exec.Command("echo", message) out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
方法 2:使用 fmt.Sprintf()
函数
我们还可以使用 Sprintf
函数创建一个包含命令和变量的字符串,然后将该字符串传递给
Command
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "fmt" "os/exec" )
func main() { message := "Hello, World!" cmdStr := fmt.Sprintf("echo %s", message) cmd := exec.Command("bash", "-c", cmdStr) out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
将整数作为变量传递给 shell
命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "fmt" "os/exec" )
func main() { x := 42 cmd := exec.Command("echo", fmt.Sprintf("%d", x)) out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
将浮点数作为变量传递给 shell
命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "fmt" "os/exec" )
func main() { y := 3.14 cmd := exec.Command("echo", fmt.Sprintf("%f", y)) out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
使用管道符 (|) 传递 shell 命令
方法 1:使用 exec.Command()
我们可以通过使用 exec.Command()
并将命令作为由管道字符
“|” 分隔的单个字符串来传递,从而使用管道运行 shell
命令。以下是运行简单命令 ls
、将其输出通过管道传输到 grep
命令并搜索特定文件的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package main
import ( "fmt" "os/exec" )
func main() { cmd := exec.Command("bash", "-c", "ls | grep main.go") out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
我们还可以使用以下格式的管道传递多个命令:
1
| cmd := exec.Command("bash", "-c", "command1 | command2 | command3")
|
方法2:使用context包
我们可以使用 os/exec
包的 CommandContext
函数来实现相同的目的,该函数允许传递上下文并在字符串切片中传递命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main
import ( "context" "fmt" "os/exec" )
func main() { ctx := context.Background() cmd := exec.CommandContext(ctx, "bash", "-c", "ls | grep main.go") out, err := cmd.Output() if err != nil { panic(err) } fmt.Println(string(out)) }
|
运行多个 shell 命令
方法 1:使用 exec.Command()
函数
我们可以再次使用 exec.Command()
函数来提供要按顺序执行的命令列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main
import ( "fmt" "os/exec" )
func main() { commands := []string{ "ping -c 2 google.com", "ping -c 2 facebook.com", "ping -c 2 www.golinuxcloud.com", } for _, command := range commands { cmd := exec.Command("bash", "-c", command) out, err := cmd.Output() if err != nil { fmt.Println(err) } fmt.Println(string(out)) } }
|
方法2:使用上下文功能
我们还可以使用 os/exec
包的 CommandContext
函数来实现相同的目的,该函数允许传递上下文并在字符串切片中传递命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "context" "fmt" "os/exec" )
func main() { ctx := context.Background() commands := []string{ "ping -c 2 google.com", "ping -c 2 yahoo.com", "ping -c 2 www.golinuxcloud.com", } for _, command := range commands { cmd := exec.CommandContext(ctx, "bash", "-c", command) out, err := cmd.Output() if err != nil { fmt.Println(err) } fmt.Println(string(out)) } }
|
总结
在本文中,我们尝试介绍可在 golang 中使用的各种可能的方法来执行 shell
命令。以下是我们使用的一些方法:
exec.Command
:这是在 Go 中运行 shell
命令最常用的方法。它创建一个新进程并在该进程中运行命令。该函数将命令及其参数作为单独的参数,并返回一个
exec.Cmd
结构体,该结构体提供与命令交互的方法。
exec.CommandContext
:它类似于
exec.Command
,但它允许将上下文传递给命令(功能类似我们
http
中常用的 context
)。
我们还学习了如何使用 Start
和 Wait
函数在后台启动进程并等待其完成。