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)
}

注意事项

  1. 当索引Bash关联数组时,总是使用引号。否则静态解析器将不得不假设索引是一个算术表达式。
$ echo '${array[spaced string]}' | shfmt
<standard input>:1:16: not a valid arithmetic operator: string
  1. $((((的歧义不支持。POSIX规范建议如果要使用$( (,请对操作数进行空格处理。
$ echo '$((foo); (bar))' | shfmt
1:1: reached ) without matching $(( with ))
  1. exportletdeclare被解析为关键字。

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脚本处理能力,包括:

  1. 解析shell脚本为AST
  2. 格式化shell脚本
  3. 变量扩展和环境处理
  4. 语法检查和验证
  5. AST遍历和修改

通过合理使用sh库,可以构建安全的命令执行器、脚本格式化工具、CI/CD流水线中的脚本分析工具等。该库API设计良好,文档齐全,是处理shell相关任务的优秀选择。

注意:在实际生产环境中使用shell命令执行功能时,务必做好安全检查和权限控制,避免命令注入等安全问题。

回到顶部