シェル芸人を目指して


私は業務でシェルスクリプトを書く機会が多いのですが、書籍やサイトで学習してみると、意外とまだまだ知らないテクニックや、非推奨の書き方を使っていたりすることが多いです。シェル芸人を極める道を歩む上で、知っておいたほうがよさそうなことをいくつかまとめてみました。

前置き

本記事のシェルスクリプトは全てbashzshを想定しています。
FreeBSD系やSolaris系の場合は動作しない/使えないテクニックがあるので、移植する可能性がある場合はご注意ください。

ShellCheck

ShellCheck
ShellScriptを書く上で欠かせない、Linterツールです。

macならhomebrewで、Debian系ならaptでインストール可能です。

if文での算術式

bashで数値を比較するif文では、通常下記のようにdouble bracket(testコマンドと等価なbracketの拡張)を使うかと思います。

a=1
if [[ $a -gt 0 ]]; then
    echo "a is positive."
fi

bashでは以下の算術式がif文の条件式としても使えます。
こちらのほうが可読性が高い場合もあるかと思います。

a=1
if (( a >= 0 )); then
    echo "a is positive."
fi

.コマンドとsourceコマンドの違い

別のshellscriptを読み込む方法には.(ドットコマンド)とsourceコマンドがあります。

# .profileファイルを読み込む
. "$HOME/.profile"
source "$HOME/.profile"

実はこの2つのコマンドに機能的な差異はありません。
sourceコマンドはcsh由来なのでFreeBSD系やSolaris系では動作しないという点が異なります。
使用できる環境に関しては現在確認中...(2018/12/10)

個人的には.はディレクトリパスや正規表現でも多様するので、sourceコマンドのほうが可読性がよいかと思います。

execコマンドの使いどころ

execコマンドは新しいプロセスを作らずにコマンドを実行します。
ラッパースクリプトなどでshellとjava等の2つのプロセスが残ってしまうケースに利用したほうがよさそうです。

setコマンドとシバン(1行目の#!/bin/bash)

シバンとはUNIXのスクリプトの#!から始まる1行目のことです。
#!/bin/bashと指定した場合はファイルを./test.shのように実行した場合、このシバンで指定されたコマンドでファイルが実行されます。

bashの-xオプション(デバッグ表示)や-eオプション(エラー時にスクリプト終了)等もシバンに指定できるので、使い分けたいところ。

setコマンド

setは上記でも述べたbashのオプションをスクリプト中から指定するコマンドです。
(位置パラメータもセット可能です)
スクリプトの冒頭でset -eと宣言すれば、コマンドが偽の終了ステータスで終了した場合、コマンドを終了します。

未定義参照をエラーにする

シェルスクリプトでは未定義の変数を参照しても、空文字列が返ります。
デバッグの妨げになることがしばしばあります。
-uオプションを使用すると、未定義の変数にアクセスした場合、エラーとすることができます

 $ set -u
 $ echo $a
zsh: a: parameter not set

コマンド置換$()

コマンド中に別のコマンドの標準出力を利用する、コマンド置換があります。

basename "$(pwd)"
# reireias

上記はpwdの結果が/home/reireiasであるので、basename /home/reireiasが実行され、reireiasという出力を得ます。

もちろん、スクリプト中では変数に代入して渡すということも可能なのですが、ワンライナーで書く際にも重宝します。

また、バッククォートで囲んでいました。(今でもこの書式でもかけます)
ですが、エスケープ処理の面で$()のほうが優れているため、バッククォートはshellcheckでも非推奨となっているようです。

ブレース展開

複数の文字列の組み合わせから、文字列を展開するブレース展開という機能があります。
こちらは、スクリプトよりもワンライナーや普段のターミナル操作でよく使用します。

# 列挙した値の展開
$ echo /home/{a,b,c}
/home/a /home/b /home/c
# 連続での展開
$ for i in {1..3}; do echo $i; done
1
2
3
# いくつかのバージョンのディレクトリの削除
$ rm -r /path/to/{1.0,1.2,2.5}/hoge

exprより$(())を使う

数値を計算するコマンドとして、exprがありますが、bashの場合は$(())という算術式展開の利用が推奨されます。

算術式展開に関しては、こちらの記事に詳しく紹介されているので、一読をおすすめします。

if文の代わりに、&&||を使う

&&は前のコマンドの終了コードが真の場合、後ろのコマンドが実行されます。また、||は前のコマンドが偽の場合、後ろのコマンドが実行されます。(所謂and, or演算)
これは多用しすぎると可読性を損なう可能性がありますが、簡潔に記述できるテクニックだと思います。

a=1
# if文の場合
if [[ a -eq 1 ]]; then
  echo "equal"
fi
# &&の場合
[[ a -eq 1 ]] && echo "equal"

参考文献