これは、なにをしたくて書いたもの?
shfmtを使うと、シェルスクリプトのフォーマットができるようです。今回はUbuntu Linux 24.04 LTSに導入してみます。
shfmt
shfmtは、以下のGitHubリポジトリーに含まれるツールのひとつです。
GitHub - mvdan/sh: A shell parser, formatter, and interpreter with bash support; includes shfmt
文字通り、シェルスクリプトのフォーマットができます。
デフォルトのフォーマットスタイルはこちら。
https://github.com/mvdan/sh/blob/v3.8.0/syntax/canonical.sh
ドキュメントはこちら。
https://github.com/mvdan/sh/blob/v3.8.0/cmd/shfmt/shfmt.1.scd
環境
今回の環境はこちら。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.3 LTS Release: 24.04 Codename: noble $ uname -srvmpio Linux 6.8.0-79-generic #79-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 14:42:46 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
shfmtをインストールする
shfmtは、Linuxディストリビューションのパッケージマネージャーでインストールできます。
Ubuntu Linuxの場合はaptですね。
$ apt show shfmt Package: shfmt Version: 3.8.0-1 Built-Using: golang-1.21 (= 1.21.6-1), golang-github-google-renameio (= 2.0.0-2), golang-github-pkg-diff (= 0.0~git20210226.20ebb0f-1), golang-golang-x-sync (= 0.6.0-1), golang-golang-x-sys (= 0.16.0-1), golang-golang-x-term (= 0.16.0-1), golang-mvdan-editorconfig (= 0.2.0+git20231228.1925077-1) Priority: optional Section: universe/utils Source: golang-mvdan-sh Origin: Ubuntu Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> Original-Maintainer: Debian Go Packaging Team <team+pkg-go@tracker.debian.org> Bugs: https://bugs.launchpad.net/ubuntu/+filebug Installed-Size: 3,038 kB Enhances: bash, dash, mksh Homepage: https://github.com/mvdan/sh Download-Size: 1,131 kB APT-Sources: http://jp.archive.ubuntu.com/ubuntu noble/universe amd64 Packages Description: format shell programs shfmt is shell formatter, which supports POSIX Shell, Bash, and mksh.
インストールしてみます。
$ sudo apt install shfmt
Ubuntu Linux 24.0.4 LTSでは、3.8.0がインストールされました。
$ shfmt --version 3.8.0
ヘルプ。
$ shfmt --help usage: shfmt [flags] [path ...] shfmt formats shell programs. If the only argument is a dash ('-') or no arguments are given, standard input will be used. If a given path is a directory, all shell scripts found under that directory will be used. --version show version and exit -l, --list list files whose formatting differs from shfmt's -w, --write write result to file instead of stdout -d, --diff error with a diff when the formatting differs -s, --simplify simplify the code -mn, --minify minify the code to reduce its size (implies -s) --apply-ignore always apply EditorConfig ignore rules Parser options: -ln, --language-dialect str bash/posix/mksh/bats, default "auto" -p, --posix shorthand for -ln=posix --filename str provide a name for the standard input file Printer options: -i, --indent uint 0 for tabs (default), >0 for number of spaces -bn, --binary-next-line binary ops like && and | may start a line -ci, --case-indent switch cases will be indented -sr, --space-redirects redirect operators will be followed by a space -kp, --keep-padding keep column alignment paddings -fn, --func-next-line function opening braces are placed on a separate line Utilities: -f, --find recursively find all shell files and print the paths --to-json print syntax tree to stdout as a typed JSON --from-json read syntax tree from stdin as a typed JSON For more information, see 'man shfmt' and https://github.com/mvdan/sh.
テスト用に、canonical.shを修正したこんなスクリプトを用意。
script.sh
#!/bin/bash ! foo bar >a & foo() { bar; } { var1="some long value" # var1 comment var2=short # var2 comment } if foo; then bar; fi for foo in a b c do bar done case $foo in a) A ;; b) B ;; esac foo | bar foo && $(bar) && (more) foo 2>&1 foo <<-EOF bar EOF `(3 + 4)`
実行は、スクリプトまたはディレクトリーを指定して行うようです。
$ shfmt script.sh
オプションをなにも指定しないと、結果が標準出力に書き出されます。
#!/bin/bash ! foo bar >a & foo() { bar; } { var1="some long value" # var1 comment var2=short # var2 comment } if foo; then bar; fi for foo in a b c; do bar done case $foo in a) A ;; b) B ;; esac foo | bar foo && $(bar) && (more) foo 2>&1 foo <<-EOF bar EOF $( (3 + 4))
なにが変わったのかわかりにくいので、-d
オプションをつけると差分のみが表示されます。
$ shfmt -d script.sh --- script.sh.orig +++ script.sh @@ -1,24 +1,22 @@ #!/bin/bash - ! foo bar >a & +! foo bar >a & foo() { bar; } { - var1="some long value" # var1 comment - var2=short # var2 comment + var1="some long value" # var1 comment + var2=short # var2 comment } if foo; then bar; fi -for foo in a b c -do +for foo in a b c; do bar done -case $foo -in - a) A ;; - b) +case $foo in +a) A ;; +b) B ;; esac @@ -33,4 +31,4 @@ bar EOF -`(3 + 4)` +$( (3 + 4))
また-w
オプションをつけると直接ファイルを修正します。
いろいろ試してみましたが、個人的には以下のオプション指定で使いたいですね。
$ shfmt -i 4 -sr -bn -d -w [スクリプトまたはディレクトリー] $ shfmt -l -i 4 -sr -bn -w [スクリプトまたはディレクトリー]
意味はこちら。
-l
または--list
… 変更するファイルパスを表示-i
または--indent
… インデントを指定。デフォルトはタブで、数字を指定するとスペースの数の意味になる-sr
または--space-redirects
… リダイレクト演算子を使う時にスペースを入れる-bn
または--binary-next-line
…&&
や|
のようなバイナリー演算子で行を開始する-d
または--diff
…変更差分を表示する-w
または--write
… 結果を標準出力に書き出すのではなくファイルを変更する
-l
と-d
は相反するので、どちらかですね。
こんな感じで使っていこうと思います。