shfmt-formatting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

shfmt Formatting

shfmt 格式化

Expert knowledge of shfmt's formatting capabilities, patterns, and integration with development workflows for consistent shell script formatting.
关于shfmt格式化能力、模式以及与开发工作流集成的专业知识,用于实现Shell脚本的一致格式化。

Overview

概述

shfmt formats shell scripts for readability and consistency. It parses scripts into an AST and prints them in a canonical format, eliminating style debates and ensuring uniformity across codebases.
shfmt用于格式化Shell脚本,提升可读性和一致性。它会将脚本解析为抽象语法树(AST),并以标准格式输出,从而消除代码风格争议,确保整个代码库的格式统一。

Supported Shell Dialects

支持的Shell方言

shfmt supports multiple shell dialects with different syntax rules:
shfmt支持多种具有不同语法规则的Shell方言:

POSIX Shell

POSIX Shell

Most portable, works with
/bin/sh
on any Unix system:
bash
#!/bin/sh
可移植性最强,适用于任何Unix系统上的
/bin/sh
bash
#!/bin/sh

POSIX-compliant syntax only

POSIX-compliant syntax only

No arrays

No arrays

No [[ ]] tests

No [[ ]] tests

No $() must work in all contexts

No $() must work in all contexts

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

Bash

Bash

Most common for scripts, supports extended features:
bash
#!/usr/bin/env bash
脚本中最常用的方言,支持扩展特性:
bash
#!/usr/bin/env bash

Bash-specific features allowed

Bash-specific features allowed

declare -a array=("one" "two" "three")
if [[ "$var" == "value" ]]; then echo "match" fi
result=$((1 + 2))
undefined
declare -a array=("one" "two" "three")
if [[ "$var" == "value" ]]; then echo "match" fi
result=$((1 + 2))
undefined

mksh (MirBSD Korn Shell)

mksh (MirBSD Korn Shell)

Korn shell variant with its own extensions:
bash
#!/bin/mksh
Korn Shell的变体,拥有自身的扩展特性:
bash
#!/bin/mksh

mksh-specific syntax

mksh-specific syntax

typeset -A assoc assoc[key]=value
undefined
typeset -A assoc assoc[key]=value
undefined

Bats (Bash Automated Testing System)

Bats (Bash Automated Testing System)

For Bats test files:
bash
#!/usr/bin/env bats

@test "example test" {
    run my_command
    [ "$status" -eq 0 ]
}
用于Bats测试文件:
bash
#!/usr/bin/env bats

@test "example test" {
    run my_command
    [ "$status" -eq 0 ]
}

Formatting Patterns

格式化模式

Indentation

缩进

shfmt normalizes indentation throughout scripts:
Before:
bash
if [ "$x" = "y" ]; then
  echo "two spaces"
    echo "four spaces"
 echo "tab"
fi
After (with
-i 2
):
bash
if [ "$x" = "y" ]; then
  echo "two spaces"
  echo "four spaces"
  echo "tab"
fi
shfmt会统一脚本中的缩进:
格式化前:
bash
if [ "$x" = "y" ]; then
  echo "two spaces"
    echo "four spaces"
 echo "tab"
fi
使用
-i 2
格式化后:
bash
if [ "$x" = "y" ]; then
  echo "two spaces"
  echo "four spaces"
  echo "tab"
fi

Spacing

空格

shfmt normalizes spacing around operators and keywords:
Before:
bash
if[$x="y"];then
echo "no spaces"
fi
x=1;y=2;z=3
After:
bash
if [ $x = "y" ]; then
    echo "no spaces"
fi
x=1
y=2
z=3
shfmt会统一操作符和关键字周围的空格:
格式化前:
bash
if[$x="y"];then
echo "no spaces"
fi
x=1;y=2;z=3
格式化后:
bash
if [ $x = "y" ]; then
    echo "no spaces"
fi
x=1
y=2
z=3

Semicolons and Newlines

分号与换行

Multiple statements are split to separate lines:
Before:
bash
if [ "$x" ]; then echo "yes"; else echo "no"; fi
After:
bash
if [ "$x" ]; then
    echo "yes"
else
    echo "no"
fi
多个语句会被拆分到单独的行:
格式化前:
bash
if [ "$x" ]; then echo "yes"; else echo "no"; fi
格式化后:
bash
if [ "$x" ]; then
    echo "yes"
else
    echo "no"
fi

Here Documents

Here文档

Here-docs are preserved but indentation is normalized:
Before:
bash
cat <<EOF
    line 1
    line 2
EOF
After (preserved):
bash
cat <<EOF
    line 1
    line 2
EOF
Indented here-docs with
<<-
allow tab stripping:
bash
if true; then
    cat <<-EOF
 indented content
 more content
 EOF
fi
Here文档会被保留,但缩进会被统一:
格式化前:
bash
cat <<EOF
    line 1
    line 2
EOF
格式化后(保留原格式):
bash
cat <<EOF
    line 1
    line 2
EOF
使用
<<-
的缩进式Here文档支持移除制表符:
bash
if true; then
    cat <<-EOF
 indented content
 more content
 EOF
fi

Function Definitions

函数定义

Functions are formatted consistently:
Before:
bash
function my_func
{
echo "old style"
}
my_func2 () { echo "one liner"; }
After:
bash
function my_func {
    echo "old style"
}
my_func2() {
    echo "one liner"
}
With
-fn
(function next line):
bash
function my_func
{
    echo "brace on new line"
}
函数会被一致地格式化:
格式化前:
bash
function my_func
{
echo "old style"
}
my_func2 () { echo "one liner"; }
格式化后:
bash
function my_func {
    echo "old style"
}
my_func2() {
    echo "one liner"
}
使用
-fn
(函数大括号换行):
bash
function my_func
{
    echo "brace on new line"
}

Case Statements

Case语句

Case statements are formatted consistently:
Before:
bash
case "$1" in
start) do_start;;
stop)
do_stop
;;
*) echo "unknown";;
esac
After:
bash
case "$1" in
start)
    do_start
    ;;
stop)
    do_stop
    ;;
*)
    echo "unknown"
    ;;
esac
With
-ci
(case indent):
bash
case "$1" in
    start)
        do_start
        ;;
    stop)
        do_stop
        ;;
esac
Case语句会被一致地格式化:
格式化前:
bash
case "$1" in
start) do_start;;
stop)
do_stop
;;
*) echo "unknown";;
esac
格式化后:
bash
case "$1" in
start)
    do_start
    ;;
stop)
    do_stop
    ;;
*)
    echo "unknown"
    ;;
esac
使用
-ci
(Case缩进):
bash
case "$1" in
    start)
        do_start
        ;;
    stop)
        do_stop
        ;;
esac

Binary Operators

二元操作符

Line continuation with binary operators:
Default:
bash
if [ "$a" = "foo" ] &&
    [ "$b" = "bar" ]; then
    echo "match"
fi
With
-bn
(binary next line):
bash
if [ "$a" = "foo" ] \
    && [ "$b" = "bar" ]; then
    echo "match"
fi
二元操作符的换行续行:
默认格式:
bash
if [ "$a" = "foo" ] &&
    [ "$b" = "bar" ]; then
    echo "match"
fi
使用
-bn
(二元操作符换行):
bash
if [ "$a" = "foo" ] \
    && [ "$b" = "bar" ]; then
    echo "match"
fi

Redirections

重定向

Redirection formatting:
Default:
bash
echo "hello" >file.txt
cat <input.txt 2>&1
With
-sr
(space redirects):
bash
echo "hello" > file.txt
cat < input.txt 2>&1
重定向的格式化:
默认格式:
bash
echo "hello" >file.txt
cat <input.txt 2>&1
使用
-sr
(重定向符后加空格):
bash
echo "hello" > file.txt
cat < input.txt 2>&1

Common Formatting Issues

常见格式化问题

Issue: Mixed Tabs and Spaces

问题:制表符与空格混合

Problem: Script has inconsistent indentation
Solution:
bash
undefined
问题描述: 脚本缩进不一致
解决方案:
bash
undefined

Convert all to spaces (2-space indent)

转换为空格(2空格缩进)

shfmt -i 2 -w script.sh
shfmt -i 2 -w script.sh

Or convert all to tabs

或者转换为制表符

shfmt -i 0 -w script.sh
undefined
shfmt -i 0 -w script.sh
undefined

Issue: Trailing Semicolons

问题:尾随分号

Problem: Unnecessary semicolons at end of lines
Before:
bash
echo "hello";
x=1;
After (shfmt removes them):
bash
echo "hello"
x=1
问题描述: 行尾存在不必要的分号
格式化前:
bash
echo "hello";
x=1;
格式化后(shfmt会移除分号):
bash
echo "hello"
x=1

Issue: Inconsistent Quotes

问题:引号不一致

shfmt preserves quote style but normalizes unnecessary quotes:
Before:
bash
echo 'single' "double" $'ansi'
x="simple"
After (preserved):
bash
echo 'single' "double" $'ansi'
x="simple"
shfmt会保留引号风格,但会统一不必要的引号:
格式化前:
bash
echo 'single' "double" $'ansi'
x="simple"
格式化后(保留原风格):
bash
echo 'single' "double" $'ansi'
x="simple"

Issue: Long Lines

问题:过长行

shfmt does not wrap long lines automatically. Use manual line continuation:
bash
undefined
shfmt不会自动换行过长的行,需要手动添加续行符:
bash
undefined

Long command with continuation

长命令使用续行符

very_long_command
--option1 value1
--option2 value2
--option3 value3
undefined
very_long_command
--option1 value1
--option2 value2
--option3 value3
undefined

Issue: Array Formatting

问题:数组格式化

Arrays are formatted on single or multiple lines as written:
bash
undefined
数组会按单行或多行的原始写法格式化:
bash
undefined

Single line (preserved)

单行(保留原格式)

array=(one two three)
array=(one two three)

Multi-line (preserved)

多行(保留原格式)

array=( one two three )
undefined
array=( one two three )
undefined

Editor Integration

编辑器集成

VS Code

VS Code

Install "shell-format" extension:
json
// settings.json
{
  "shellformat.path": "/usr/local/bin/shfmt",
  "shellformat.flag": "-i 2 -ci -bn",
  "[shellscript]": {
    "editor.defaultFormatter": "foxundermoon.shell-format",
    "editor.formatOnSave": true
  }
}
安装“shell-format”扩展:
json
// 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

Vim/Neovim

Using ALE:
vim
" .vimrc
let g:ale_fixers = {
\   'sh': ['shfmt'],
\}
let g:ale_sh_shfmt_options = '-i 2 -ci -bn'
let g:ale_fix_on_save = 1
Using native formatting:
vim
" .vimrc
autocmd FileType sh setlocal formatprg=shfmt\ -i\ 2\ -ci
使用ALE插件:
vim
" .vimrc
let g:ale_fixers = {
\   'sh': ['shfmt'],
\}
let g:ale_sh_shfmt_options = '-i 2 -ci -bn'
let g:ale_fix_on_save = 1
使用原生格式化:
vim
" .vimrc
autocmd FileType sh setlocal formatprg=shfmt\ -i\ 2\ -ci

Emacs

Emacs

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

(add-hook 'sh-mode-hook 'shfmt-on-save-mode)
使用reformatter:
elisp
;; 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 IDEs

JetBrains IDEs

Install "Shell Script" plugin, configure in: Settings -> Tools -> Shell Scripts -> Formatter
Path to shfmt: /usr/local/bin/shfmt
Options: -i 2 -ci -bn
安装“Shell Script”插件,在以下路径配置: 设置 -> 工具 -> Shell脚本 -> 格式化工具
Path to shfmt: /usr/local/bin/shfmt
Options: -i 2 -ci -bn

Diff and Check Modes

差异对比与检查模式

Check Formatting (CI Mode)

检查格式(CI模式)

bash
undefined
bash
undefined

Show diff of what would change (exit 1 if changes needed)

显示将要修改的差异(如果需要修改则返回1)

shfmt -d script.sh shfmt -d .
shfmt -d script.sh shfmt -d .

List files that need formatting

列出需要格式化的文件

shfmt -l .
shfmt -l .

Exit codes:

退出码说明:

0 = no changes needed

0 = 无需修改

1 = changes needed or error

1 = 需要修改或出现错误

undefined
undefined

Format in Place

就地格式化

bash
undefined
bash
undefined

Overwrite files with formatted version

用格式化后的内容覆盖原文件

shfmt -w script.sh shfmt -w .
undefined
shfmt -w script.sh shfmt -w .
undefined

Preview Changes

预览修改

bash
undefined
bash
undefined

Output formatted version to stdout

将格式化后的内容输出到标准输出

shfmt script.sh
shfmt script.sh

Output formatted version to file

将格式化后的内容输出到文件

shfmt script.sh > formatted.sh
undefined
shfmt script.sh > formatted.sh
undefined

Working with Git

与Git配合使用

Format Staged Files

格式化暂存区文件

bash
undefined
bash
undefined

Format only staged shell scripts

仅格式化暂存区中的Shell脚本

git diff --cached --name-only --diff-filter=ACM |
grep '.sh$' |
xargs -r shfmt -w
undefined
git diff --cached --name-only --diff-filter=ACM |
grep '.sh$' |
xargs -r shfmt -w
undefined

Pre-commit Hook

提交前钩子

bash
#!/usr/bin/env bash
bash
#!/usr/bin/env bash

.git/hooks/pre-commit

.git/hooks/pre-commit

Check if shfmt is available

检查shfmt是否可用

if ! command -v shfmt &>/dev/null; then echo "shfmt not found, skipping format check" exit 0 fi
if ! command -v shfmt &>/dev/null; then echo "shfmt not found, skipping format check" exit 0 fi

Get staged shell files

获取暂存区中的Shell文件

files=$(git diff --cached --name-only --diff-filter=ACM | grep '.sh$')
if [ -n "$files" ]; then # Check formatting if ! echo "$files" | xargs shfmt -d; then echo "Shell scripts need formatting. Run: shfmt -w <files>" exit 1 fi fi
undefined
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
undefined

Format Changed Files

格式化变更文件

bash
undefined
bash
undefined

Format files changed since main branch

格式化自main分支以来变更的文件

git diff --name-only main...HEAD |
grep '.sh$' |
xargs -r shfmt -w
undefined
git diff --name-only main...HEAD |
grep '.sh$' |
xargs -r shfmt -w
undefined

Minification (Advanced)

代码压缩(进阶功能)

shfmt can minify scripts by removing whitespace:
bash
undefined
shfmt可以通过移除空白字符来压缩脚本:
bash
undefined

Minify script

压缩脚本

shfmt -mn script.sh > script.min.sh

**Before:**

```bash
#!/bin/bash
shfmt -mn script.sh > script.min.sh

**压缩前:**

```bash
#!/bin/bash

Comment

Comment

function hello { echo "Hello, World!" } hello

**After (minified):**

```bash
#!/bin/bash
function hello { echo "Hello, World!"; }
hello
Note: Minification removes comments and most whitespace. Use only for distribution, not development.
function hello { echo "Hello, World!" } hello

**压缩后:**

```bash
#!/bin/bash
function hello { echo "Hello, World!"; }
hello
注意:压缩会移除注释和大部分空白字符。仅用于发布脚本,不适合开发阶段使用。

Best Practices

最佳实践

  1. Format on Save - Configure editor to format automatically
  2. CI Validation - Run
    shfmt -d
    in CI pipelines
  3. Consistent Team Settings - Commit
    .shfmt.toml
    to repository
  4. Match Shebang - Ensure shell dialect matches script shebang
  5. Review After Formatting - Verify changes make sense
  6. Don't Mix Styles - Use same settings across all scripts
  7. Pre-commit Hooks - Prevent unformatted code from being committed
  1. 保存时自动格式化 - 配置编辑器,实现保存时自动格式化
  2. CI校验 - 在CI流水线中运行
    shfmt -d
    检查格式
  3. 团队统一配置 - 将
    .shfmt.toml
    提交到代码仓库
  4. 匹配Shebang - 确保Shell方言与脚本的Shebang一致
  5. 格式化后复查 - 验证格式化后的代码符合预期
  6. 不混合风格 - 所有脚本使用相同的格式化配置
  7. 提交前钩子 - 阻止未格式化的代码被提交

Troubleshooting

故障排查

Parse Errors

解析错误

If shfmt fails to parse a script:
bash
undefined
如果shfmt无法解析脚本:
bash
undefined

Check syntax first

先检查语法

bash -n script.sh
bash -n script.sh

Or for POSIX

或者针对POSIX脚本

sh -n script.sh
undefined
sh -n script.sh
undefined

Wrong Dialect Detection

方言检测错误

Force the correct dialect:
bash
shfmt -ln bash script.sh
shfmt -ln posix script.sh
强制指定正确的方言:
bash
shfmt -ln bash script.sh
shfmt -ln posix script.sh

Preserving Intentional Formatting

保留有意的格式

For code that should not be reformatted, consider:
  1. Moving it to a separate file not processed by shfmt
  2. Using
    # shfmt:ignore
    comments (not supported - use file exclusion)
  3. Accepting the formatted version
对于不应被重新格式化的代码,可以考虑:
  1. 将其移至不被shfmt处理的单独文件中
  2. 使用
    # shfmt:ignore
    注释(暂不支持 - 可使用文件排除)
  3. 接受格式化后的版本

When to Use This Skill

何时使用该技能

  • Formatting shell scripts for consistency
  • Integrating shfmt into development workflow
  • Resolving formatting issues in scripts
  • Setting up format-on-save in editors
  • Configuring CI/CD format checks
  • Understanding shfmt's formatting decisions
  • 格式化Shell脚本以保证一致性
  • 将shfmt集成到开发工作流中
  • 解决脚本中的格式化问题
  • 在编辑器中配置保存时自动格式化
  • 配置CI/CD格式检查
  • 理解shfmt的格式化决策