shfmt-formatting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseshfmt 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 on any Unix system:
/bin/shbash
#!/bin/sh可移植性最强,适用于任何Unix系统上的:
/bin/shbash
#!/bin/shPOSIX-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
undefinedif [ "$var" = "value" ]; then
echo "match"
fi
undefinedBash
Bash
Most common for scripts, supports extended features:
bash
#!/usr/bin/env bash脚本中最常用的方言,支持扩展特性:
bash
#!/usr/bin/env bashBash-specific features allowed
Bash-specific features allowed
declare -a array=("one" "two" "three")
if [[ "$var" == "value" ]]; then
echo "match"
fi
result=$((1 + 2))
undefineddeclare -a array=("one" "two" "three")
if [[ "$var" == "value" ]]; then
echo "match"
fi
result=$((1 + 2))
undefinedmksh (MirBSD Korn Shell)
mksh (MirBSD Korn Shell)
Korn shell variant with its own extensions:
bash
#!/bin/mkshKorn Shell的变体,拥有自身的扩展特性:
bash
#!/bin/mkshmksh-specific syntax
mksh-specific syntax
typeset -A assoc
assoc[key]=value
undefinedtypeset -A assoc
assoc[key]=value
undefinedBats (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"
fiAfter (with ):
-i 2bash
if [ "$x" = "y" ]; then
echo "two spaces"
echo "four spaces"
echo "tab"
fishfmt会统一脚本中的缩进:
格式化前:
bash
if [ "$x" = "y" ]; then
echo "two spaces"
echo "four spaces"
echo "tab"
fi使用格式化后:
-i 2bash
if [ "$x" = "y" ]; then
echo "two spaces"
echo "four spaces"
echo "tab"
fiSpacing
空格
shfmt normalizes spacing around operators and keywords:
Before:
bash
if[$x="y"];then
echo "no spaces"
fi
x=1;y=2;z=3After:
bash
if [ $x = "y" ]; then
echo "no spaces"
fi
x=1
y=2
z=3shfmt会统一操作符和关键字周围的空格:
格式化前:
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=3Semicolons and Newlines
分号与换行
Multiple statements are split to separate lines:
Before:
bash
if [ "$x" ]; then echo "yes"; else echo "no"; fiAfter:
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"
fiHere Documents
Here文档
Here-docs are preserved but indentation is normalized:
Before:
bash
cat <<EOF
line 1
line 2
EOFAfter (preserved):
bash
cat <<EOF
line 1
line 2
EOFIndented here-docs with allow tab stripping:
<<-bash
if true; then
cat <<-EOF
indented content
more content
EOF
fiHere文档会被保留,但缩进会被统一:
格式化前:
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
fiFunction 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 (function next line):
-fnbash
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"
}使用(函数大括号换行):
-fnbash
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";;
esacAfter:
bash
case "$1" in
start)
do_start
;;
stop)
do_stop
;;
*)
echo "unknown"
;;
esacWith (case indent):
-cibash
case "$1" in
start)
do_start
;;
stop)
do_stop
;;
esacCase语句会被一致地格式化:
格式化前:
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使用(Case缩进):
-cibash
case "$1" in
start)
do_start
;;
stop)
do_stop
;;
esacBinary Operators
二元操作符
Line continuation with binary operators:
Default:
bash
if [ "$a" = "foo" ] &&
[ "$b" = "bar" ]; then
echo "match"
fiWith (binary next line):
-bnbash
if [ "$a" = "foo" ] \
&& [ "$b" = "bar" ]; then
echo "match"
fi二元操作符的换行续行:
默认格式:
bash
if [ "$a" = "foo" ] &&
[ "$b" = "bar" ]; then
echo "match"
fi使用(二元操作符换行):
-bnbash
if [ "$a" = "foo" ] \
&& [ "$b" = "bar" ]; then
echo "match"
fiRedirections
重定向
Redirection formatting:
Default:
bash
echo "hello" >file.txt
cat <input.txt 2>&1With (space redirects):
-srbash
echo "hello" > file.txt
cat < input.txt 2>&1重定向的格式化:
默认格式:
bash
echo "hello" >file.txt
cat <input.txt 2>&1使用(重定向符后加空格):
-srbash
echo "hello" > file.txt
cat < input.txt 2>&1Common Formatting Issues
常见格式化问题
Issue: Mixed Tabs and Spaces
问题:制表符与空格混合
Problem: Script has inconsistent indentation
Solution:
bash
undefined问题描述: 脚本缩进不一致
解决方案:
bash
undefinedConvert 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
undefinedshfmt -i 0 -w script.sh
undefinedIssue: 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=1Issue: 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
undefinedshfmt不会自动换行过长的行,需要手动添加续行符:
bash
undefinedLong command with continuation
长命令使用续行符
very_long_command
--option1 value1
--option2 value2
--option3 value3
--option1 value1
--option2 value2
--option3 value3
undefinedvery_long_command
--option1 value1
--option2 value2
--option3 value3
--option1 value1
--option2 value2
--option3 value3
undefinedIssue: Array Formatting
问题:数组格式化
Arrays are formatted on single or multiple lines as written:
bash
undefined数组会按单行或多行的原始写法格式化:
bash
undefinedSingle line (preserved)
单行(保留原格式)
array=(one two three)
array=(one two three)
Multi-line (preserved)
多行(保留原格式)
array=(
one
two
three
)
undefinedarray=(
one
two
three
)
undefinedEditor 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 = 1Using 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\ -ciEmacs
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 -bnDiff and Check Modes
差异对比与检查模式
Check Formatting (CI Mode)
检查格式(CI模式)
bash
undefinedbash
undefinedShow 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 = 需要修改或出现错误
undefinedundefinedFormat in Place
就地格式化
bash
undefinedbash
undefinedOverwrite files with formatted version
用格式化后的内容覆盖原文件
shfmt -w script.sh
shfmt -w .
undefinedshfmt -w script.sh
shfmt -w .
undefinedPreview Changes
预览修改
bash
undefinedbash
undefinedOutput formatted version to stdout
将格式化后的内容输出到标准输出
shfmt script.sh
shfmt script.sh
Output formatted version to file
将格式化后的内容输出到文件
shfmt script.sh > formatted.sh
undefinedshfmt script.sh > formatted.sh
undefinedWorking with Git
与Git配合使用
Format Staged Files
格式化暂存区文件
bash
undefinedbash
undefinedFormat only staged shell scripts
仅格式化暂存区中的Shell脚本
git diff --cached --name-only --diff-filter=ACM |
grep '.sh$' |
xargs -r shfmt -w
grep '.sh$' |
xargs -r shfmt -w
undefinedgit diff --cached --name-only --diff-filter=ACM |
grep '.sh$' |
xargs -r shfmt -w
grep '.sh$' |
xargs -r shfmt -w
undefinedPre-commit Hook
提交前钩子
bash
#!/usr/bin/env bashbash
#!/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
undefinedfiles=$(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
undefinedFormat Changed Files
格式化变更文件
bash
undefinedbash
undefinedFormat files changed since main branch
格式化自main分支以来变更的文件
git diff --name-only main...HEAD |
grep '.sh$' |
xargs -r shfmt -w
grep '.sh$' |
xargs -r shfmt -w
undefinedgit diff --name-only main...HEAD |
grep '.sh$' |
xargs -r shfmt -w
grep '.sh$' |
xargs -r shfmt -w
undefinedMinification (Advanced)
代码压缩(进阶功能)
shfmt can minify scripts by removing whitespace:
bash
undefinedshfmt可以通过移除空白字符来压缩脚本:
bash
undefinedMinify script
压缩脚本
shfmt -mn script.sh > script.min.sh
**Before:**
```bash
#!/bin/bashshfmt -mn script.sh > script.min.sh
**压缩前:**
```bash
#!/bin/bashComment
Comment
function hello {
echo "Hello, World!"
}
hello
**After (minified):**
```bash
#!/bin/bash
function hello { echo "Hello, World!"; }
helloNote: 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
最佳实践
- Format on Save - Configure editor to format automatically
- CI Validation - Run in CI pipelines
shfmt -d - Consistent Team Settings - Commit to repository
.shfmt.toml - Match Shebang - Ensure shell dialect matches script shebang
- Review After Formatting - Verify changes make sense
- Don't Mix Styles - Use same settings across all scripts
- Pre-commit Hooks - Prevent unformatted code from being committed
- 保存时自动格式化 - 配置编辑器,实现保存时自动格式化
- CI校验 - 在CI流水线中运行检查格式
shfmt -d - 团队统一配置 - 将提交到代码仓库
.shfmt.toml - 匹配Shebang - 确保Shell方言与脚本的Shebang一致
- 格式化后复查 - 验证格式化后的代码符合预期
- 不混合风格 - 所有脚本使用相同的格式化配置
- 提交前钩子 - 阻止未格式化的代码被提交
Troubleshooting
故障排查
Parse Errors
解析错误
If shfmt fails to parse a script:
bash
undefined如果shfmt无法解析脚本:
bash
undefinedCheck syntax first
先检查语法
bash -n script.sh
bash -n script.sh
Or for POSIX
或者针对POSIX脚本
sh -n script.sh
undefinedsh -n script.sh
undefinedWrong 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.shPreserving Intentional Formatting
保留有意的格式
For code that should not be reformatted, consider:
- Moving it to a separate file not processed by shfmt
- Using comments (not supported - use file exclusion)
# shfmt:ignore - Accepting the formatted version
对于不应被重新格式化的代码,可以考虑:
- 将其移至不被shfmt处理的单独文件中
- 使用注释(暂不支持 - 可使用文件排除)
# shfmt:ignore - 接受格式化后的版本
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的格式化决策