Linux や MacOS、Windows の WSL でシェルスクリプト(Bash)を書く場合は、組み込みコマンド set
を活用しましょう。より完成度の高い処理を書くことができます。
目次
1. シェルスクリプトとは?
コンピュータでは、ターミナル上でコマンドを実行していろいろな操作を行うことができますが、このコマンドを解釈して意図した処理を実行してくれているプログラムを シェル と言います。
Linux や MacOS などの UNIX系OS であれば、「Bourne Again Shell(Bash)」「KornShell(Ksh)」「C shell(Csh)」「Z shell(Zsh)」などがありますし、Windows には「PowerShell」があります。
この例で実行している figlet
コマンドは、引数として与えた文字列をアスキーアートにして表示してくれるコマンドです(かなりマイナーな半分お遊び用コマンドです。詳細はこちらをご覧ください → FIGlet – hosted by PLiG)。
そして、複数(1つでもよいですが)のコマンドを記述して何かの処理をさせるプログラムのことを シェルスクリプト と言います。
例えば、hello.sh
というファイルに以下の内容を記述します。
#!/usr/bin/bash
figlet Hello,
figlet World!
1行目に「bash」という文字がある通り、これは Bash というシェルを使ったシェルスクリプトになります。(この場合、システムに Bashがインストールされており、/usr/bin/bash
というパスで bash プログラムが配置されている必要があります)
このファイルの中では、figlet
コマンドを2回実行しています。
このファイルを実行するには、chmod
コマンドで実行権限を与えて、
$ chmod u+x hello.sh
以下のようにして実行します。
$ ./hello.sh
実際に実行した結果が以下になります。
figlet
コマンドが順番に実行されました。これは意図した通りの挙動です。
以上、シェルスクリプトがどういったものかについて説明しました。
このページでは、引き続き「Bash」について説明します。
※ 「シェルスクリプト」と書いただけでは、どのシェルに向けたスクリプトなのか分かりませんが、UNIX系OS でシェルスクリプトと言えば、Bash用のスクリプトと考えてよいです。
2. Bash のマニュアルと組み込みコマンド
Bash の詳細については、man
コマンドで読むことができます(オンラインマニュアル)。
$ man bash
このマニュアルは内容が多くて読むのが大変ですが、この内容のうち「Bashの組み込みコマンド (shell builtin commands)」の部分については、help
という組み込みコマンドを使って読めるようになっています。こちらであれば、一度に表示される文章が限られていますので読むのがラクです。
例えば、help
自体の説明を見てみます。
$ help help
と入力して Enter キーを押して下さい。私の環境では、以下のように表示されました。
$ help help
help: help [-dms] [pattern ...]
組み込みコマンドの情報を表示します。
組み込みコマンドに関する簡潔な要約を表示します。もし PATTERN が
指定された場合は、PATTERN に一致する全てのコマンドに対する詳細な
ヘルプを表示します。それ以外はヘルプトピックを表示します。
オプション:
-d 各ヘルプトピックに対して短い説明を出力します
-m 使用法を擬似的な man ページ形式で表示します
-s 一致した各トピックに対して簡単な使用法のみを表示します
PATTERN
引数:
PATTERN ヘルプトピックを指定するパターン
終了ステータス:
PATTERN が見つからないか無効なオプションが与えられない限り成功を返します。
help
組み込みコマンドが使えるのは、この時のシェルが Bash だからです。
今どのシェルの中にいるのかを知るには、次のコマンドを実行して下さい。
$ echo $SHELL
また、そのシステムにどのシェルがインストールされているかは、以下のコマンドで知ることができます。
$ cat /etc/shells
/bin/sh
/bin/bash
/sbin/nologin
/usr/bin/sh
/usr/bin/bash
/usr/sbin/nologin
/bin/tcsh
/bin/csh
例えば、ここに表示された /bin/csh
にシェルを変更する場合は、以下のコマンドを実行してからログインし直して下さい。
$ chsh -s /bin/csh
bash の組み込みコマンドには、以下のようなコマンドがあります(主なものを抜粋しました)。
- source
- alias
- cd
- echo
- exec
- export
- history
- jobs
- kill
- pwd
- set
- test
- type
- umask
この中の set
は、Bash によるシェルスクリプトにおいて、挙動を変更することができる大変重要な組み込みコマンドです。
以下、この set
について説明します。
3. Bash の組み込みコマンド set
オプションを指定して set
を実行することにより、シェルスクリプトの挙動を変更することができます。
先程説明した help
を使って、set
の説明を読んでみましょう。
$ help set
私の環境では以下のように表示されました。この部分は日本語化されていないのか、英語になっています。
$ help set
set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
Set or unset values of shell options and positional parameters.
Change the value of shell attributes and positional parameters, or
display the names and values of shell variables.
Options:
-a Mark variables which are modified or created for export.
-b Notify of job termination immediately.
-e Exit immediately if a command exits with a non-zero status.
-f Disable file name generation (globbing).
-h Remember the location of commands as they are looked up.
-k All assignment arguments are placed in the environment for a
command, not just those that precede the command name.
-m Job control is enabled.
-n Read commands but do not execute them.
-o option-name
Set the variable corresponding to option-name:
allexport same as -a
braceexpand same as -B
emacs use an emacs-style line editing interface
errexit same as -e
errtrace same as -E
functrace same as -T
hashall same as -h
histexpand same as -H
history enable command history
ignoreeof the shell will not exit upon reading EOF
interactive-comments
allow comments to appear in interactive commands
keyword same as -k
monitor same as -m
noclobber same as -C
noexec same as -n
noglob same as -f
nolog currently accepted but ignored
notify same as -b
nounset same as -u
onecmd same as -t
physical same as -P
pipefail the return value of a pipeline is the status of
the last command to exit with a non-zero status,
or zero if no command exited with a non-zero status
posix change the behavior of bash where the default
operation differs from the Posix standard to
match the standard
privileged same as -p
verbose same as -v
vi use a vi-style line editing interface
xtrace same as -x
-p Turned on whenever the real and effective user ids do not match.
Disables processing of the $ENV file and importing of shell
functions. Turning this option off causes the effective uid and
gid to be set to the real uid and gid.
-t Exit after reading and executing one command.
-u Treat unset variables as an error when substituting.
-v Print shell input lines as they are read.
-x Print commands and their arguments as they are executed.
-B the shell will perform brace expansion
-C If set, disallow existing regular files to be overwritten
by redirection of output.
-E If set, the ERR trap is inherited by shell functions.
-H Enable ! style history substitution. This flag is on
by default when the shell is interactive.
-P If set, do not follow symbolic links when executing commands
such as cd which change the current directory.
-T If set, the DEBUG trap is inherited by shell functions.
-- Assign any remaining arguments to the positional parameters.
If there are no remaining arguments, the positional parameters
are unset.
- Assign any remaining arguments to the positional parameters.
The -x and -v options are turned off.
Using + rather than - causes these flags to be turned off. The
flags can also be used upon invocation of the shell. The current
set of flags may be found in $-. The remaining n ARGs are positional
parameters and are assigned, in order, to $1, $2, .. $n. If no
ARGs are given, all shell variables are printed.
Exit Status:
Returns success unless an invalid option is given.
8行目くらいに「Options」とあり、その下に「-a」とか「-b」とか書いてあります。これがset のオプションです。この1つ1つのオプションによって、Bash によるシェルスクリプトの挙動を変更することができるのです。例えば、-a だけ適用したいなら、set -a
というコマンドを実行すればよいですし、-a と -b の両方を適用したいのであれば、set -ab
というコマンドを実行します。そうすると、それ以降そのオプションの効果が有効になった状態になります。
オプションには名前が付いています。
例えば、上の中に以下の記述があります。
allexport same as -a
これは、-a オプションには「allexport」というオプション名がついていることを表します。
また、この1つ1つオプションのことを「フラグ」とも呼び、有効になっていることを「オン」になっていると表現します。
デフォルト状態で設定されているオプション(オンになっているフラグ)
では、set
を使わず、デフォルト状態でどのようなフラグがオンになっている見てみましょう。
それには、-o
オプションを使うか、$-
を使います。
まず、-o
オプションを試すために、default-set-options.sh
という名前のファイルに、以下を記述して実行してみましょう。
#!/usr/bin/bash
set -o
ファイルに実行権限を与えて、実行します。
$ chmod u+x default-set-options.sh
$ ./default-set-options.sh
以下のような出力が得られました。オプション名に対して、on もしくは off が表示されています。分かりやすいです。
allexport off
braceexpand on
emacs off
errexit off
errtrace off
functrace off
hashall on
histexpand off
history off
ignoreeof off
interactive-comments on
keyword off
monitor off
noclobber off
noexec off
noglob off
nolog off
notify off
nounset off
onecmd off
physical off
pipefail off
posix off
privileged off
verbose off
vi off
xtrace off
on になっているのは、以下の 3つです。
- braceexpand
- hashall
- interactive-comments
次に、$-
を試してみます。この記号は、現在オンになっているフラグを表示することができます。
では、default-set-options2.sh
という名前のファイルに、以下を記述して実行してみましょう。
#!/usr/bin/bash
echo $-
こちらも同様に、ファイルに実行権限を与えて実行します。
$ chmod u+x default-set-options2.sh
$ ./default-set-options2.sh
結果はこうなりました。
hB
こちらは、オプション名ではなく -h
や -B
といった表現に使うアルファベットが示されます。
先程、help set
で見たオプションの説明の中に、
- “braceexpand same as -B”(braceexpand は -B と同じ)
- “hashall same as -h”(hashall は -h と同じ)
とあったことから分かるように、-B と -h についてはどちらの結果でも有効になっていることが分かります。ただ、「interactive-comments」については、後者の結果には表示されていませんね。対応するオプションがありませんでしたので、フラグとして表示されないのかもしれません(コマンドラインで set -o
を実行すると、”i” という文字が表示されるのですが、これはこのオプションのことを指しているという情報もあります。ここではこれ以上深い入りするのはやめておきます)。
では、どちらの結果でも「オン」になっていた 2つのオプションの説明を、set
のマニュアルから探してみましょう。
-B
-B the shell will perform brace expansion
-B
オプションをつけると、シェルは中括弧の展開を実行すると書いてあります。
これは例えば、
$ ls {a,b}.txt
というコマンドを実行した場合、{a,b}
の部分が a
と b
に展開されて、結果的に、a.txt
と b.txt
の2つが ls
コマンドの引数になるという機能のことを指しています。
これは普段よく使う機能なので、デフォルトでオンになっているのだと思います。実際よく使います。
-h
-h Remember the location of commands as they are looked up.
Bashが独自に持っている「コマンドのパス記憶機能」を hash と言いますが、この機能を有効にします。この機能を使うことで、「よく使うコマンドのパスを Bashのレベルでキャッシュするので、動作が速くなる」とか、「既存のコマンドと同名で別のコマンドを一時的に使うことができる」などのメリットがあります。とはいっても、この機能を意識して使う機会はあまりないかもしれません。
今、それぞれのオプションについて少し具体的に説明しましたが、残念ながら help set
で表示される説明文だけでは、ここまで知ることはできません。なので、詳細はインターネットや本で補う必要があります。とはいえ、そのための概要や手掛かりはつかめると思います。
以上で、デフォルトで有効になっているオプションについては分かりました。
ちなみに、「-」の代わりに「+」を使うと、そのフラグをオフにすることができます。
4. その他のオプションを試してみる
その他のオプションで、よく使うものを紹介します。
これらのオプションを使うことで、シェルスクリプトの質が上がります。
errexit (-e)
オンラインマニュアルにはこう説明されています。
-e Exit immediately if a command exits with a non-zero status.
最初の方で、シェルスクリプトは複数のコマンドを並べたものであると書きましたが、このフラグをオンにすると、途中のコマンドでエラーが起きた場合に、そこで処理を終了させることができます。逆に言うと、デフォルトの状態だと、途中でエラーが起きてもそのまま次のコマンドに進んでしまうのです。これは便利なので、割とよく使います。
例えば、以下のシェルスクリプトを実行すると、ls
コマンドでエラーが起きますが、最後のコマンドまで実行されます。
#!/usr/bin/bash
#set -e
echo "Start"
# 存在しないファイル名に対して ls コマンドを実行する → エラーになる
ls aabbccdd
echo "End"
しかし、set -e
の先頭のコメントを外して(頭の #
を削除します)実行すると、最後の echo
は実行されません。これは、errexit フラグがオンになっているからです。
nounset (-u)
-u Treat unset variables as an error when substituting.
セットされてない変数を使うとエラーにしてくれます。
この手の機能は、バグの少ないプログラムを書くために、いろいろなプログラミング言語で用意されています。
例えば、以下のシェルスクリプトを実行すると、何も表示されずに終了しますが、set -u
のコメントを外してから実行すると、「FOOBAR: 未割り当ての変数です」といったエラーを出力してくれます。
#!/usr/bin/bash
#set -u
echo $FOOBAR
補足
ある変数が定義されているかどうか判定する場合は、-v
オプションを使います。
FOOという変数が定義されているか判定するスクリプトの例
if [ -v FOO ]; then
echo "FOO is defined";
else
echo "FOO is NOT defined";
fi
xtrace (-x)
-x Print commands and their arguments as they are executed.
これは、シェルスクリプトをデバッグしたい場合、つまりどこでエラーが起きているかをチェックしたい場合などに使います。
例えば、以下のシェルスクリプトをそのまま実行すると、echo に指定された文字列が出力されるだけです。
#!/usr/bin/bash
#set -x
echo "Start"
echo "Now running!"
echo "End"
しかし、set -x
のコメントを外してから実行すると、実行した echo
の部分まで出力してくれます。
こんな感じです。
$ ./xtrace.sh
+ echo Start
Start
+ echo 'Now running!'
Now running!
+ echo End
End
5. おわりに
コマンドを複数並べれば済む処理をしたい場合、シェルスクリプトとしてファイルに保存しておけば、後から同じ処理をする時にラクですし、記録にも残ります。
そうした時に、set
を使うとより安全で確実なシェルスクリプトを書くことができます。
シェルスクリプトを書く際には、是非とも使っておきたい機能です。