shfmt脚本格式化Skill shfmt-formatting

shfmt脚本格式化技能,用于自动化和标准化shell脚本的格式化,支持POSIX、Bash、mksh和Bats等多种方言,确保代码一致性和可读性,适用于DevOps工作流、CI/CD集成、编辑器自动格式化和代码审查。

DevOps 0 次安装 0 次浏览 更新于 3/25/2026

名称: shfmt-formatting 用户可调用: false 描述: 在使用shfmt格式化shell脚本时使用。涵盖一致的格式化模式、shell方言支持、常见问题和编辑器集成。 允许的工具:

  • 读取
  • 写入
  • 编辑
  • Bash
  • Grep
  • Glob

shfmt 格式化

关于shfmt的格式化能力、模式以及集成到开发工作流中以实现一致shell脚本格式化的专家知识。

概述

shfmt格式化shell脚本以提高可读性和一致性。它将脚本解析成AST并以规范格式打印,消除风格争议并确保跨代码库的统一性。

支持的Shell方言

shfmt支持多种具有不同语法规则的shell方言:

POSIX Shell

最便携,适用于任何Unix系统上的/bin/sh

#!/bin/sh
# 仅使用POSIX兼容语法

# 无数组
# 无[[ ]]测试
# $()必须在所有上下文中工作

if [ "$var" = "value" ]; then
    echo "match"
fi

Bash

脚本中最常见,支持扩展功能:

#!/usr/bin/env bash
# 允许Bash特定功能

declare -a array=("one" "two" "three")

if [[ "$var" == "value" ]]; then
    echo "match"
fi

result=$((1 + 2))

mksh(MirBSD Korn Shell)

Korn shell变体,具有自己的扩展:

#!/bin/mksh
# mksh特定语法

typeset -A assoc
assoc[key]=value

Bats(Bash Automated Testing System)

用于Bats测试文件:

#!/usr/bin/env bats

@test "示例测试" {
    run my_command
    [ "$status" -eq 0 ]
}

格式化模式

缩进

shfmt在整个脚本中规范化缩进:

之前:

if [ "$x" = "y" ]; then
  echo "两个空格"
    echo "四个空格"
 echo "制表符"
fi

之后(使用-i 2):

if [ "$x" = "y" ]; then
  echo "两个空格"
  echo "四个空格"
  echo "制表符"
fi

间距

shfmt规范化操作符和关键字周围的间距:

之前:

if[$x="y"];then
echo "无间距"
fi
x=1;y=2;z=3

之后:

if [ $x = "y" ]; then
    echo "无间距"
fi
x=1
y=2
z=3

分号和换行符

多个语句被拆分成单独的行:

之前:

if [ "$x" ]; then echo "是"; else echo "否"; fi

之后:

if [ "$x" ]; then
    echo "是"
else
    echo "否"
fi

Here Documents

Here-docs被保留,但缩进被规范化:

之前:

cat <<EOF
    行 1
    行 2
EOF

之后(保留):

cat <<EOF
    行 1
    行 2
EOF

使用<<-的缩进here-docs允许制表符剥离:

if true; then
    cat <<-EOF
 缩进内容
 更多内容
 EOF
fi

函数定义

函数被一致格式化:

之前:

function my_func
{
echo "旧风格"
}
my_func2 () { echo "单行"; }

之后:

function my_func {
    echo "旧风格"
}
my_func2() {
    echo "单行"
}

使用-fn(函数下一行):

function my_func
{
    echo "大括号在新行"
}

Case语句

Case语句被一致格式化:

之前:

case "$1" in
start) do_start;;
stop)
do_stop
;;
*) echo "未知";;
esac

之后:

case "$1" in
start)
    do_start
    ;;
stop)
    do_stop
    ;;
*)
    echo "未知"
    ;;
esac

使用-ci(case缩进):

case "$1" in
    start)
        do_start
        ;;
    stop)
        do_stop
        ;;
esac

二元操作符

二元操作符的行延续:

默认:

if [ "$a" = "foo" ] &&
    [ "$b" = "bar" ]; then
    echo "匹配"
fi

使用-bn(二元下一行):

if [ "$a" = "foo" ] \
    && [ "$b" = "bar" ]; then
    echo "匹配"
fi

重定向

重定向格式化:

默认:

echo "hello" >file.txt
cat <input.txt 2>&1

使用-sr(空格重定向):

echo "hello" > file.txt
cat < input.txt 2>&1

常见格式化问题

问题:混合制表符和空格

问题: 脚本有缩进不一致

解决方案:

# 全部转换为空格(2空格缩进)
shfmt -i 2 -w script.sh

# 或全部转换为制表符
shfmt -i 0 -w script.sh

问题:尾部多余分号

问题: 行尾有不必要的分号

之前:

echo "hello";
x=1;

之后(shfmt移除它们):

echo "hello"
x=1

问题:不一致的引号

shfmt保留引号风格,但规范化不必要的引号:

之前:

echo 'single' "double" $'ansi'
x="simple"

之后(保留):

echo 'single' "double" $'ansi'
x="simple"

问题:长行

shfmt不会自动换行。使用手动行延续:

# 长命令延续
very_long_command \
    --option1 value1 \
    --option2 value2 \
    --option3 value3

问题:数组格式化

数组根据原样格式化在单行或多行:

# 单行(保留)
array=(one two three)

# 多行(保留)
array=(
    one
    two
    three
)

编辑器集成

VS Code

安装“shell-format”扩展:

// settings.json
{
  "shellformat.path": "/usr/local/bin/shfmt",
  "shellformat.flag": "-i 2 -ci -bn",
  "[shellscript]": {
    "editor.defaultFormatter": "foxundermoon.shell-format",
    "editor.formatOnSave": true
  }
}

Vim/Neovim

使用ALE:

" .vimrc
let g:ale_fixers = {
\   'sh': ['shfmt'],
\}
let g:ale_sh_shfmt_options = '-i 2 -ci -bn'
let g:ale_fix_on_save = 1

使用原生格式化:

" .vimrc
autocmd FileType sh setlocal formatprg=shfmt\ -i\ 2\ -ci

Emacs

使用reformatter:

;; init.el
(use-package reformatter
  :config
  (reformatter-define shfmt
    :program "shfmt"
    :args '("-i" "2" "-ci")))

(add-hook 'sh-mode-hook 'shfmt-on-save-mode)

JetBrains IDE

安装“Shell Script”插件,在设置中配置: 设置 -> 工具 -> Shell Scripts -> Formatter

Path to shfmt: /usr/local/bin/shfmt
Options: -i 2 -ci -bn

Diff和检查模式

检查格式化(CI模式)

# 显示将更改的差异(如果需要更改则退出1)
shfmt -d script.sh
shfmt -d .

# 列出需要格式化的文件
shfmt -l .

# 退出代码:
# 0 = 无需更改
# 1 = 需要更改或错误

原地格式化

# 用格式化版本覆盖文件
shfmt -w script.sh
shfmt -w .

预览更改

# 输出格式化版本到标准输出
shfmt script.sh

# 输出格式化版本到文件
shfmt script.sh > formatted.sh

与Git配合使用

格式化暂存文件

# 仅格式化暂存的shell脚本
git diff --cached --name-only --diff-filter=ACM | \
    grep '\.sh$' | \
    xargs -r shfmt -w

预提交钩子

#!/usr/bin/env bash
# .git/hooks/pre-commit

# 检查shfmt是否可用
if ! command -v shfmt &>/dev/null; then
    echo "shfmt not found, skipping format check"
    exit 0
fi

# 获取暂存的shell文件
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$')

if [ -n "$files" ]; then
    # 检查格式化
    if ! echo "$files" | xargs shfmt -d; then
        echo "Shell scripts need formatting. Run: shfmt -w <files>"
        exit 1
    fi
fi

格式化更改的文件

# 格式化自main分支以来更改的文件
git diff --name-only main...HEAD | \
    grep '\.sh$' | \
    xargs -r shfmt -w

压缩(高级)

shfmt可以通过移除空格压缩脚本:

# 压缩脚本
shfmt -mn script.sh > script.min.sh

之前:

#!/bin/bash
# 注释
function hello {
    echo "Hello, World!"
}
hello

之后(压缩后):

#!/bin/bash
function hello { echo "Hello, World!"; }
hello

注意:压缩移除注释和大多数空格。仅用于分发,不用于开发。

最佳实践

  1. 保存时格式化 - 配置编辑器自动格式化
  2. CI验证 - 在CI流水线中运行shfmt -d
  3. 一致的团队设置 - 提交.shfmt.toml到仓库
  4. 匹配Shebang - 确保shell方言匹配脚本shebang
  5. 格式化后审查 - 验证更改合理
  6. 不混合风格 - 在所有脚本中使用相同设置
  7. 预提交钩子 - 防止未格式化代码被提交

故障排除

解析错误

如果shfmt解析脚本失败:

# 先检查语法
bash -n script.sh

# 或对于POSIX
sh -n script.sh

错误方言检测

强制正确的方言:

shfmt -ln bash script.sh
shfmt -ln posix script.sh

保留有意格式化

对于不应重新格式化的代码,考虑:

  1. 将其移动到单独的文件,不由shfmt处理
  2. 使用# shfmt:ignore注释(不支持 - 使用文件排除)
  3. 接受格式化版本

何时使用此技能

  • 为一致性格式化shell脚本
  • 将shfmt集成到开发工作流
  • 解决脚本中的格式化问题
  • 在编辑器中设置保存时格式化
  • 配置CI/CD格式检查
  • 理解shfmt的格式化决策