如何用Go实现bash管道中文本流的读取与格式化?
问题描述
我现在用这条命令格式化npm脚本的输出:
npm run startWin | while IFS= read -r line; do printf '%b\n' "$line"; done | less
这个命令在Linux上正常工作,但同事不用Linux,我想用Go实现while IFS= read -r line; do printf '%b\n' "$line"; done的功能,生成二进制文件用于管道,也就是:
npm run startWin | magical-go-formater
我尝试了下面的代码,但程序没有输出任何文本流内容:
package main import ( "fmt" "io/ioutil" "os" "strings" ) func main() { fi, _ := os.Stdin.Stat() // get the FileInfo struct if (fi.Mode() & os.ModeCharDevice) == 0 { bytes, _ := ioutil.ReadAll(os.Stdin) str := string(bytes) arr := strings.Fields(str) for _, v := range arr { fmt.Println(v) } }
问题分析与修复
你的代码有几个关键问题导致无输出或功能不符合预期:
- 语法错误:
main函数里的if块未闭合,编译时直接报错,程序无法正常运行。 - 逻辑偏差:
strings.Fields会把所有空白字符(换行、空格、制表符等)作为分隔符,将整段输入拆成单个单词输出,完全偏离了逐行处理的需求。 - 非流式处理:
ioutil.ReadAll会等待整个输入流结束才开始处理,对于持续输出的npm脚本(比如开发服务器日志),会导致迟迟看不到输出。 - 未实现核心功能:bash的
printf '%b'会解析字符串中的反斜杠转义序列(比如\n转换行、\t转制表符),你的代码直接输出原始字符串,没做转义解析。 - 忽略错误处理:
os.Stdin.Stat()和ioutil.ReadAll的错误被直接忽略,潜在问题无法排查。
下面是修复后的代码,完全实现目标功能:
package main import ( "bufio" "fmt" "os" "strconv" "strings" ) // 实现bash printf '%b'的转义解析逻辑 func parseEscapes(s string) string { var result strings.Builder n := len(s) for i := 0; i < n; i++ { if s[i] != '\\' { result.WriteByte(s[i]) continue } i++ if i >= n { result.WriteByte('\\') break } switch s[i] { case 'n': result.WriteByte('\n') case 't': result.WriteByte('\t') case 'r': result.WriteByte('\r') case 'b': result.WriteByte('\b') case 'f': result.WriteByte('\f') case '\\': result.WriteByte('\\') case '\'': result.WriteByte('\'') case '"': result.WriteByte('"') case '0', '1', '2', '3', '4', '5', '6', '7': // 解析八进制转义,最多3位 j := i for j < n && j < i+3 && s[j] >= '0' && s[j] <= '7' { j++ } octal := s[i:j] val, err := strconv.ParseInt(octal, 8, 8) if err == nil { result.WriteByte(byte(val)) } else { result.WriteString("\\" + octal) } i = j - 1 case 'x': // 解析十六进制转义,2位 if i+2 > n { result.WriteString("\\x") break } hex := s[i+1 : i+2] val, err := strconv.ParseInt(hex, 16, 8) if err == nil { result.WriteByte(byte(val)) i += 1 } else { result.WriteString("\\x" + hex) i += 1 } default: // 未知转义,保留原样 result.WriteByte('\\') result.WriteByte(s[i]) } } return result.String() } func main() { // 检查是否存在管道输入 fi, err := os.Stdin.Stat() if err != nil { fmt.Fprintf(os.Stderr, "读取标准输入信息失败: %v\n", err) os.Exit(1) } if (fi.Mode() & os.ModeCharDevice) == 0 { // 逐行读取输入,流式处理实时输出 scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() // 解析转义序列,对应bash的printf '%b' parsedLine := parseEscapes(line) // 输出解析后的内容并添加换行,对应printf '%b\n' fmt.Println(parsedLine) } if err := scanner.Err(); err != nil { fmt.Fprintf(os.Stderr, "读取标准输入失败: %v\n", err) os.Exit(1) } } else { // 直接运行程序时提示用法 fmt.Println("用法: 程序需要通过管道接收输入,例如:") fmt.Println("npm run startWin | magical-go-formater") } }
代码说明
- 逐行流式处理:用
bufio.Scanner逐行读取标准输入,收到一行就处理一行,适合实时日志输出场景。 - 转义解析:
parseEscapes函数实现了bashprintf '%b'的核心转义解析逻辑,支持常见的转义序列。 - 错误处理:添加完整的错误捕获和提示,方便排查问题。
- 交互提示:直接运行程序时会显示用法说明,避免用户误用。
编译与使用
在Go环境下编译:
go build -o magical-go-formater main.go
编译出的二进制文件可在Windows、Linux、macOS上直接使用,用法和预期一致:
npm run startWin | magical-go-formater
内容的提问来源于stack exchange,提问作者Pedro Gomes Branquinho Facti
相关产品推荐
相关产品推荐

