golang Shell解析与格式化插件库sh的使用
Golang Shell解析与格式化插件库sh的使用
简介
sh是一个用于解析、格式化和解释Shell脚本的Go库。它支持POSIX Shell、Bash和mksh,需要Go 1.23或更高版本。
快速开始
安装
go install mvdan.cc/sh/v3/cmd/shfmt@latest
使用示例
shfmt
可以格式化Shell程序。例如:
shfmt -l -w script.sh
代码示例
解析和格式化Shell脚本
package main
import (
"fmt"
"mvdan.cc/sh/v3/syntax"
"os"
)
func main() {
// 创建一个解析器
parser := syntax.NewParser()
// 解析Shell脚本
file, err := parser.Parse(os.Stdin, "example.sh")
if err != nil {
fmt.Println("解析错误:", err)
return
}
// 创建一个格式化器
printer := syntax.NewPrinter()
// 打印格式化后的脚本
if err := printer.Print(os.Stdout, file); err != nil {
fmt.Println("格式化错误:", err)
}
}
执行Shell扩展
package main
import (
"fmt"
"mvdan.cc/sh/v3/shell"
)
func main() {
// 定义要扩展的字符串
expr := "Hello ${USER:-world}"
// 执行Shell扩展
expanded, err := shell.Expand(expr, nil)
if err != nil {
fmt.Println("扩展错误:", err)
return
}
fmt.Println("扩展结果:", expanded)
}
注意事项
- 当索引Bash关联数组时,总是使用引号。否则静态解析器将不得不假设索引是一个算术表达式。
$ echo '${array[spaced string]}' | shfmt
<standard input>:1:16: not a valid arithmetic operator: string
$((
和((
的歧义不支持。POSIX规范建议如果要使用$( (
,请对操作数进行空格处理。
$ echo '$((foo); (bar))' | shfmt
1:1: reached ) without matching $(( with ))
export
、let
和declare
被解析为关键字。
Docker使用
所有发布标签都通过Docker发布,如v3.5.1
。最新稳定版本发布为v3
,最新开发版本为latest
。
构建Docker镜像:
docker build -t my:tag -f cmd/shfmt/Dockerfile .
使用Docker镜像:
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD:/mnt" -w /mnt my:tag <shfmt arguments>
相关项目
以下编辑器集成了shfmt
:
- BashSupport-Pro - JetBrains IDE的Bash插件
- dockerfmt - 使用shfmt的Dockerfile格式化工具
- intellij-shellcript - Intellij Jetbrains shell脚本插件
- micro - 带有内置插件的编辑器
- neoformat - (Neo)Vim插件
- shell-format - VS Code插件
- vscode-shfmt - VS Code插件
- shfmt.el - Emacs包
- Sublime-Pretty-Shell - Sublime Text 3插件
- Trunk - 通用linter
- vim-shfmt - Vim插件
其他值得注意的集成包括:
- modd - 响应文件系统变化的开发工具
- prettier-plugin-sh - 使用sh-syntax的Prettier插件
- sh-checker - 执行Shell脚本静态分析的GitHub Action
- mdformat-shfmt - 格式化Markdown中嵌入的Shell脚本的mdformat插件
- pre-commit-shfmt - pre-commit shfmt钩子
更多关于golang Shell解析与格式化插件库sh的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang Shell解析与格式化插件库sh的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang Shell解析与格式化插件库sh的使用
sh是一个强大的Golang库,用于解析和格式化shell命令。它提供了简单直观的API来处理shell语法,支持命令解析、格式化、变量扩展等功能。下面我将详细介绍sh库的使用方法。
安装
首先安装sh库:
go get github.com/mvdan/sh
基本用法
1. 解析shell命令
package main
import (
"fmt"
"log"
"strings"
"github.com/mvdan/sh/syntax"
)
func main() {
// 解析shell命令
parser := syntax.NewParser()
file, err := parser.Parse(strings.NewReader("echo hello world"), "")
if err != nil {
log.Fatal(err)
}
// 遍历AST
syntax.Walk(file, func(node syntax.Node) bool {
switch x := node.(type) {
case *syntax.CallExpr:
fmt.Printf("命令: %v\n", x.Args)
}
return true
})
}
2. 格式化shell脚本
package main
import (
"fmt"
"strings"
"github.com/mvdan/sh/syntax"
)
func main() {
// 原始shell脚本
src := `for i in 1 2 3; do echo $i; done`
// 解析
parser := syntax.NewParser()
file, err := parser.Parse(strings.NewReader(src), "")
if err != nil {
panic(err)
}
// 格式化
var buf strings.Builder
printer := syntax.NewPrinter()
if err := printer.Print(&buf, file); err != nil {
panic(err)
}
fmt.Println("格式化后的脚本:")
fmt.Println(buf.String())
}
高级功能
1. 变量扩展
package main
import (
"fmt"
"os"
"strings"
"github.com/mvdan/sh/expand"
"github.com/mvdan/sh/syntax"
)
func main() {
// 带变量的shell命令
cmd := "echo $HOME $USER"
// 解析
parser := syntax.NewParser()
file, err := parser.Parse(strings.NewReader(cmd), "")
if err != nil {
panic(err)
}
// 配置变量扩展
config := &expand.Config{
Env: expand.ListEnviron(os.Environ()...),
}
// 扩展变量
expanded, err := expand.Document(config, file)
if err != nil {
panic(err)
}
var buf strings.Builder
syntax.NewPrinter().Print(&buf, expanded)
fmt.Println("扩展后的命令:", buf.String())
}
2. 脚本语法检查
package main
import (
"fmt"
"strings"
"github.com/mvdan/sh/syntax"
)
func main() {
// 可能有语法错误的脚本
script := "if [ -f file.txt ]; then echo 'exists'; fi"
// 解析并检查语法
parser := syntax.NewParser()
_, err := parser.Parse(strings.NewReader(script), "")
if err != nil {
fmt.Println("语法错误:", err)
} else {
fmt.Println("脚本语法正确")
}
}
3. 遍历AST节点
package main
import (
"fmt"
"strings"
"github.com/mvdan/sh/syntax"
)
func main() {
script := `
#!/bin/bash
for i in {1..3}; do
echo "Number: $i"
done
`
parser := syntax.NewParser()
file, err := parser.Parse(strings.NewReader(script), "")
if err != nil {
panic(err)
}
// 遍历AST节点
syntax.Walk(file, func(node syntax.Node) bool {
switch x := node.(type) {
case *syntax.ForClause:
fmt.Printf("发现for循环: %v\n", x.Loop)
case *syntax.Word:
if x.Parts[0].(*syntax.Lit).Value == "echo" {
fmt.Println("发现echo命令")
}
}
return true
})
}
实际应用示例
构建安全的shell命令执行器
package main
import (
"fmt"
"os/exec"
"strings"
"github.com/mvdan/sh/syntax"
)
func SafeExec(cmdStr string) error {
// 解析命令
parser := syntax.NewParser()
file, err := parser.Parse(strings.NewReader(cmdStr), "")
if err != nil {
return fmt.Errorf("命令解析失败: %v", err)
}
// 检查命令是否只包含允许的命令
var safeCommands = map[string]bool{
"echo": true,
"ls": true,
"date": true,
"whoami": true,
}
var cmdName string
syntax.Walk(file, func(node syntax.Node) bool {
if call, ok := node.(*syntax.CallExpr); ok && len(call.Args) > 0 {
if word, ok := call.Args[0].(*syntax.Word); ok && len(word.Parts) > 0 {
if lit, ok := word.Parts[0].(*syntax.Lit); ok {
cmdName = lit.Value
if !safeCommands[cmdName] {
return false // 停止遍历
}
}
}
}
return true
})
if cmdName == "" || !safeCommands[cmdName] {
return fmt.Errorf("不允许的命令: %s", cmdName)
}
// 执行命令
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("命令执行失败: %v, 输出: %s", err, output)
}
fmt.Printf("命令输出:\n%s\n", output)
return nil
}
func main() {
err := SafeExec("echo hello && date")
if err != nil {
fmt.Println("错误:", err)
}
}
总结
sh库为Golang提供了强大的shell脚本处理能力,包括:
- 解析shell脚本为AST
- 格式化shell脚本
- 变量扩展和环境处理
- 语法检查和验证
- AST遍历和修改
通过合理使用sh库,可以构建安全的命令执行器、脚本格式化工具、CI/CD流水线中的脚本分析工具等。该库API设计良好,文档齐全,是处理shell相关任务的优秀选择。
注意:在实际生产环境中使用shell命令执行功能时,务必做好安全检查和权限控制,避免命令注入等安全问题。