シェルスクリプトプログラミング

C言語でのプログラミングに入る前に、本章では、シェルスクリプトを用いたプログラミングについて説明します。

GUIを前提としたWindowsとは異なり、Linuxを含むUNIXシステムでは、基本的にすべての操作をコマンドラインインターフェースから行うことができます。

シェルは、コマンドラインプロンプトから入力されたコマンドを、Linuxシステムに受け渡す機能を持ったプログラムです。シェルは、ユーザーから入力されたコマンドを一つ一つ実行するだけでなく、ファイルに記述されたコマンドを逐次実行するインタープリタとしての機能も有しています。

シェルが解釈できる形式で記述されたプログラムを、シェルスクリプトと呼びます。

シェルは構造化プログラミングが可能で、その機能は非常に強力です。Linuxの豊富なコマンドと合わせることで、アプリケーションプログラムのプロトタイプをシェルスクリプトで記述することもできるでしょう。

5.1. シェルの種類

Linuxで最も一般的なシェルは、bash(Bourne-Again Shell)です。Debian GNU/Linuxでも、標準のシェルとして採用されています。

Atmark Distを使用した場合は、bashの代わりにBusyBoxのashが使われます。ashは、bashと良く似ていますが一部の機能が省かれており、軽量ですので組み込みシステムに向いています。

一つのLinuxシステムに複数のシェルをインストールすることも可能です。システム標準のシェルを使用したい場合は、/bin/shを使用します。Debian GNU/Linuxなどでは、/bin/shは/bin/bashへのシンボリックリンクとなっています。Atmark Distを使用した場合は、/bin/busybox(ash)へのシンボリックリンクとなっています。

5.2. シェルスクリプトの書き方

シェルは、単にコマンドを一つ一つ実行するだけでなく、変数も使うことができ、forやifなどの構造化プログラミングが可能な構文を解釈することができます。

例えば、ルートディレクトリ直下のディレクトリをすべて表示するには、以下のようにします。構文については、詳しくは「シェルの構文」で説明しますが、ここではルートディレクトリ直下のディレクトリ名をdirという変数に代入して、順番にechoしていると考えてください。

[darmadillo ~]$ for dir in /*; do echo $dir; done
/bin
/boot
/dev
/etc
/home
/lib
/lost+found
/media
/mnt
/opt
/proc
/root
/sbin
/selinux
/srv
/sys
/tmp
/usr
/var

図5.1 for文の例1


これは、以下のように複数行に分けて書くこともできます。

[darmadillo ~]$ for dir in /*
> do
> echo $dir
> done
/bin
/boot
/dev
/etc
/home
/lib
/lost+found
/media
/mnt
/opt
/proc
/root
/sbin
/selinux
/srv
/sys
/tmp
/usr
/var

図5.2 for文の例2


コマンド入力の2行目から、プロンプトが「>」に変わっていることに注目してください。コマンドの終了はシェルが自動的に判断し、入力されたコマンドを実行してプロンプトを元に戻します。

シェルは、シェルスクリプトとしてファイルに記述されたコマンドを実行することもできます。

#!/bin/sh

#シェルスクリプトの例

for dir in /*; do
    echo $dir
done

図5.3 シェルスクリプトの例(example.sh)


シェルスクリプトの決まり事として、先頭行にある「#!」の後にそのスクリプトを処理するプログラムを指定することできます。今回の場合は、「/bin/sh」を指定しているのでシステムの標準シェルでスクリプトが実行されます。

先頭行以外にある「#!」以外の「#」以降の文字列はコメントして扱われます。そのため、「#シェルスクリプトの例」という行はコメントになり、実行結果に影響を与えません。

上記プログラムを実行すると、以下のようになります。

[darmadillo ~]$ chmod +x example.sh
[darmadillo ~]$ ./example.sh
/bin
/boot
/dev
/etc
/home
/lib
/lost+found
/media
/mnt
/opt
/proc
/root
/sbin
/selinux
/srv
/sys
/tmp
/usr
/var

図5.4 シェルスクリプト実行例


シェルは拡張子を判別しないので、「.sh」という拡張子はつける必要はありません。

5.3. シェルの構文

本章では、シェルの基本的な構文について説明します。bashとashの構文の違いについても指摘します。bash/ashの構文規則は、本章で説明するものがすべてではありません。より詳細な説明は、bashのmanページを参照してください。

5.3.1. 基本的なコマンドの実行方法

シェルでコマンドを実行するには、以下のように記述します。

[darmadillo ~]$ echo hello world
hello world
[darmadillo ~]$ echo hello           world
hello world

図5.5 シェルでコマンドを実行する


先頭に実行するコマンドの名前を指定します。「echo」は、複数の引数をとり、各引数を表示するプログラムです。コマンドと引数、引数と引数の間はスペースで区切ります。スペースで区切られた、シェルが1単位とみなす文字列を単語といいます。単語は、スペース以外に|、&、;、(、)、タブで区切ることができます。単語の区切りとなる文字をメタ文字といいます。

メタ文字は複数あっても構いませんので、上記二つのコマンドは等価です。

引数に空白を含めたい場合は、"か'で囲みます。

[darmadillo ~]$ echo "hello           world"
hello           world

図5.6 シェルでコマンドを実行する(空白を含む引数)


5.3.2. 変数

シェルでは、変数を使用することができます。C言語と異なり、変数の宣言は必要ありません。標準では、すべての変数は文字列型として扱われます。

変数名は、英数字とアンダースコア(_)だけから構成され、かつ最初の文字が英字かアンダースコアである必要があります。英字の大文字と小文字は区別されます。

変数に値を代入する際には、=を使用します。=の前後に空白を入れてはいけません。空白を含む文字列を代入する場合は、"又は'で囲む必要があります。また、変数の値を参照するときには、変数名の前に$を付けます。または、ブレースを使用して${変数名}と記述することもできます。

[darmadillo ~]$ variable=hello
[darmadillo ~]$ echo $variable
hello
[darmadillo ~]$ variable='hello world'
[darmadillo ~]$ echo $variable
hello world
[darmadillo ~]$ variable=1+2
[darmadillo ~]$ echo $variable
1+2

図5.7 変数の例


変数に対して算術演算を行う場合は、$(())構文を使います。

[darmadillo ~]$ variable=1
[darmadillo ~]$ echo $variable
1
[darmadillo ~]$ variable=$(($variable+1))
[darmadillo ~]$ echo $variable
2
[darmadillo ~]$ variable=$(($variable*2))
[darmadillo ~]$ echo $variable
4

図5.8 算術演算の例


bashでは変数の1次元配列を扱うこともできます。残念ながら、ashでは配列を扱うことができないので、ここでは説明を省きます。

5.3.3. 環境変数

bashでは、いくつかの変数が環境変数(シェル変数)としてあらかじめ設定されています。環境変数の名前は、大文字になっていますので、これらと混同しないようにユーザーが設定する変数には小文字を使うのが良いでしょう。

以下に、主な環境変数のリストを示します。

表5.1 主な環境変数のリスト

環境変数 説明

HOME

現在のユーザのホームディレクトリ。

PWD

現在の作業ディレクトリ。

OLDPWD

1つ前の作業ディレクトリ。

PATH

コマンドの検索パス。シェルがコマンドを検索するディレクトリをコロンで区切って並べたリスト。

IFS

内部フィールド区切り文字。デフォルト値は‘`<空白><タブ><改行>’'。

PS1

プライマリのプロンプト文字列。PS1を変更することでプロンプトの表示をカスタマイズすることができる。

PS2

セカンダリのプロンプト文字列。図5.2「for文の例2」のように複数行に分けてコマンドを記述した場合に使用される。


envコマンドを使用すると、すべての環境変数を表示することができます。

[darmadillo ~]$ env
SHELL=/bin/sh
TERM=vt100
HUSHLOGIN=FALSE
USER=Armadillo
MAIL=/var/mail/Armadillo
PATH=/usr/local/bin:/usr/bin:/bin:/usr/games
PWD=/home/Armadillo
SHLVL=1
HOME=/home/Armadillo
LOGNAME=Armadillo
_=/usr/bin/env

図5.9 すべての環境変数の表示


新しく環境変数を設定するには、exportコマンドを使います。

[darmadillo ~]$ export MY_ENV=value
[darmadillo ~]$ echo $MY_ENV
value

図5.10 環境変数の設定


5.3.4. パラメータ

パラメータは、値を保持するためのものです。パラメータは名前、数字、特定の特殊文字のいずれかで表現されます。変数とは、名前で表現されたパラメータにすぎません。

変数以外のパラメータとして、位置パラメータ、特殊パラメータがあります。

位置パラメータは、1以上の数値で表されるパラメータです。位置パラメータには、シェルの引数が代入されます。第1引数は$1に、第2引数は$2にというように順番に代入されていきます。10以上の位置パラメータを参照する場合には、${10}のように、ブレースをつけます。

シェル関数の中では、位置パラメータは関数の引数に置き換えられます。(「関数」参照。)

特殊パラメータは参照だけが可能であり、値を代入することはできないパラメータです。表5.2「特殊パラメータのリスト」に、bashとashで使用可能な特殊パラメータの一覧を示します。

表5.2 特殊パラメータのリスト

特殊パラメータ 説明

*

すべての位置パラメータに展開される。

@

すべての位置パラメータに展開される。[a]

#

位置パラメータの個数に展開される。

?

最後に実行されたフォアグラウンドプロセスの終了ステータスに展開される。

!

最後に実行されたバックグラウンドプロセスのプロセスIDに展開される。

$

プロセスIDに展開される。

0

シェルまたはシェルスクリプトの名前に展開される。

[a] ダブルクォート内部での展開のされ方が$*と異なります。「展開」参照。


5.3.5. クォート

クォートを使うと、特殊文字の意味や後述する展開を無効にすることができます。クォートの方法には、エスケープ文字、シングルクォート、ダブルクォートの3種類があります。

クォートされていないバックスラッシュ(\)をエスケープ文字といいます。エスケープ文字の直後の特殊文字は、特殊文字としての意味を失います。

シングルクォート(')で文字を囲むと、クォート内部では特殊文字は特殊文字としての意味を失い、展開もおこなわれません。シングルクォートの間にシングルクォートを置くことはできません。

ダブルクォート(")で文字を囲むと、クォート内部では$、`、バックスラッシュ以外の特殊文字は、特殊文字としての意味を失います。$と`は、特殊文字としての意味を保持します。バックスラッシュは、直後の文字が$、`、"、<newline>のいずれかの場合、特殊文字としての意味を保持します。

[darmadillo ~]$ echo $HOME
/home/Armadillo
[darmadillo ~]$ echo \$HOME
$HOME
[darmadillo ~]$ echo '$HOME'
$HOME
[darmadillo ~]$ echo "$HOME"
/home/Armadillo
[darmadillo ~]$ echo "\$HOME"
$HOME

図5.11 クォートの例


5.3.6. 展開

シェルでは、特定の書き方をした場合、別の文字列として置き換えられて解釈されることがあります。この置換処理を展開といいます。

展開は、ブレース展開、チルダ展開、パラメータ・変数・算術式展開、コマンド置換(左から右へ)、単語分割、パス名展開の順番で行われます。

ブレース展開では、a{D,C,B}eがaDe aCe aBeに展開されます。bashでは使用可能ですが、ashでは使用できません。

チルダ(~)で単語が始まった場合、スラッシュよりも前にある文字が、チルダプレフィックスと解釈されます。有効なチルダプレフィックスの場合、チルダ展開が行われます。

チルダプレフィックスがユーザー名と一致した場合、そのユーザーのホームディレクトリに展開されます。ユーザー名が指定されない場合は、シェルを実行しているユーザのホームディレクトリに展開されます。

チルダプレフィックスが+の場合、環境変数PWDに、-の場合、環境変数OLDPWDに展開されます。(この展開は、ashではおこなわれません。)

[darmadillo ~]$ echo ~root
/root
[darmadillo ~]$ echo ~Armadillo/file
/home/Armadillo/file
[darmadillo ~]$ echo ~unavailuser
~unavailuser
[darmadillo ~]$ pwd
/home/Armadillo
[darmadillo ~]$ cd /
[darmadillo /]$ echo ~+
/
[darmadillo /]$ echo ~-
/home/Armadillo

図5.12 チルダ展開の例


$文字があると、パラメータ展開、コマンド置換、算術式展開が行われます。展開されるパラメータ名やシンボルは、ブレースで括ることもできます。ブレースは省略可能ですが、変数の直後に変数名の一部と解釈できる文字が置かれた場合に、その文字と共にパラメータが展開されてしまうのを防ぐために用意されています。

パラメータ展開では、これまで説明してきた変数、環境変数、位置パラメータ、特殊パラメータの展開が行われます。

ダブルクォートの内部で$*の展開が行われた時は、それぞれのパラメータをIFSでの最初の文字で区切って並べた1つの単語に展開されます。IFSが設定されていなければ、パラメータは空白で区切られます。IFSが空文字列の場合、すべてのパラメータはつなげられます。

ダブルクォートの内部で$@の展開が行われた時は、それぞれのパラメータは別々の単語に展開されます。位置パラメータがない場合は、空文字に展開されます。(つまり、取り除かれます。)

$(command)または、`command`と書くと、commandの実行結果に置き換えられます。

[darmadillo ~]$ var=$(date)
[darmadillo ~]$ echo $var
Fri Sep 3 15:21:23 JST 2010

図5.13 コマンド置換の例


$((expression))と書くと、expressionを算術式評価して、その結果に置き換えられます。

シェルは上記の展開を行った後、IFSに指定されたそれぞれの文字を区切り文字として、単語に分割します。

単語分割に続いて、パス名展開をおこないます。単語分割を行った後の単語が、*、?、[を含んでいた場合、その単語はパターンとみなされます。パターンに一致するファイルがあれば、その単語はマッチするファイル名をアルファベット順にソートしたリストに置換されます。マッチするファイル名がない場合、その単語は置き換えられません。

*は、空文字列を含む任意の文字列にマッチします。また、?は、任意の1文字にマッチします。

[と]で文字列を括ると、マッチする文字のパターンを指定できます。単に1文字以上の文字を指定した場合は、指定した文字のうち、任意の1文字にマッチします。2つの文字の間にハイフンをいれたものを指定した場合、指定した文字とその間の範囲にある文字にマッチします。また、[の直後に!または^を指定した場合は、指定した文字以外の任意の文字がマッチします。

[と]の間には、文字クラスを指定することができます。文字クラスは、[:class:]のようにコロン(:)で括ります。文字クラスには、alnum、alpha、ascii、blank、cntrl、digit、graph、lower、print、punct、space、upper、xdigitがあります。

最後に、クウォートの削除がおこなわれます。クウォートされていない\、'、"のうち、上記の展開の結果でないものはすべて削除されます。

5.3.7. 終了ステータス

シェルは、終了ステータス0で終了したコマンドは正常終了したとみなします。0以外の終了ステータスは失敗を意味します。

あるコマンドが、シグナルNで終了したときには、終了ステータスは128+Nになります。コマンドが見つからなかった場合の終了ステータスは127で、コマンドが見つかったけれど実行できなかった場合には、126になります。

[darmadillo ~]$ echo hello
hello
[darmadillo ~]$ echo $?
0
[darmadillo ~]$ sleep 100
Ctrl-C
[darmadillo ~]$ echo $?
130
[darmadillo ~]$ noexistent-command
bash: noexistent-command: not found
[darmadillo ~]$ echo $?
127
[darmadillo ~]$ touch not-executable-file
[darmadillo ~]$ ./not-executable-file
bash: ./not-executable-file: Permission denied
[darmadillo ~]$ echo $?
126

図5.14 終了ステータスの例


exitコマンドを使用すると、シェルの終了ステータスを指定することができます。

5.3.8. 入出力の扱い

5.3.8.1. 標準入出力

C言語でプログラミングする際に、標準出力(stdout)、標準入力(stdin)、標準エラー出力(stderr)を使用したことがあると思います。printf関数で出力される先が標準出力で、scanf関数での入力元が標準入力です。

シェルを介してプログラムを実行する際、標準出力、標準入力、標準エラー出力は、通常、コンソールとなります。

シェルでは、以降で説明するリダイレクトとパイプという機能を用いて、標準入出力の入力元や出力先を、コンソールではなくファイルや他のプログラムにすることができます。

5.3.8.2. リダイレクト

シェルには、プログラムの入出力の入力元や出力先を変更する機能があります。その機能のことを、リダイレクトといいます。

標準出力をコンソールではなく、ファイルにする場合には以下のようにします。

[darmadillo ~]$ echo hello > log

図5.15 出力のリダイレクト


このコマンドを実行すると、「log」という名前のファイルに「hello」と書き込まれます。fopen("log", "w")とした時と同様に、ファイルが存在しなければファイルが新たに作成され、また、ファイルが存在する場合はファイルの既存の内容はすべて上書きされます。

「log」に追記したい場合は、以下のように>>を使用します。

[darmadillo ~]$ echo hello >> log

図5.16 出力のリダイレクト(追記)


リダイレクトする際には、ファイルディスクリプタを指定することもできます。標準出力のファイルディスクリプタは1、標準入力は2、標準エラー出力は3と決まっています。

そのため、標準エラー出力への出力だけ、ファイルに出力したい場合は、以下のように書けます。

[darmadillo ~]$ cat nonexistent_file 2> error
[darmadillo ~]$ cat error
cat: nonexistent_file: No such file or directory

図5.17 標準エラー出力のリダイレクト


「nonexistent_file」(存在しないファイル)をcatコマンドで表示しようとした際のエラー出力を「error」という名前のファイルに保存しています。

ファイルディスクリプタを指定しない場合は、標準出力として扱われるので、以下の二つのコマンドは等価です。

[darmadillo ~]$ cat hello > log
[darmadillo ~]$ cat hello 1> log

図5.18 標準出力のリダイレクト


また、リダイレクトは複数指定することもできます。

[darmadillo ~]$ somecommand > log 2> error

図5.19 複数の出力のリダイレクト


上記の記述では、「somecommand」の標準出力への出力を「log」というファイルに書き込み、標準エラー出力への出力を「error」というファイルに書き込みます。

[darmadillo ~]$ somecommand > log 2>&1

図5.20 標準出力と標準エラー出力を同じファイルにリダイレクト


このように記述すると、「somecommand」の標準出力と標準エラー出力への出力を同時に「log」というファイルに書き込む事ができます。

コマンドの途中経過をコンソールに表示する必要がない場合もあります。そのような場合は、/dev/nullへ書き込むことで出力を捨てることができます。

[darmadillo ~]$ somecommand > /dev/null 2>&1

図5.21 出力を/dev/nullにリダイレクト


出力のリダイレクトと同様に、入力もリダイレクトすることができます。

[darmadillo ~]$ cat < /etc/hostname
debian

図5.22 入力のリダイレクト


5.3.8.3. パイプ

リダイレクト機能を使うと、入出力をファイルと結びつけることができました。パイプ機能を使うと、あるプログラムの出力と別のプログラムの入力を結びつけることができます。

以下の例では、echoプログラムの標準出力をcatプログラムの標準入力に結びつけています。

[darmadillo ~]$ echo hello | cat
hello

図5.23 パイプ


以下の例では、カーネルコンフィギュレーションの中に、「ARMADILLO」と書かれた行が何行あるか表示しています。

[darmadillo ~]$ zcat /proc/config.gz | grep ARMADILLO | wc -l
21

図5.24 パイプの例


まず、zcatコマンドが/proc/config.gz(カーネルコンフィギュレーションをgzip圧縮したファイル)を展開して標準出力に出力します。その出力を、grepコマンドの標準入力にパイプで繋いでいます。grepコマンドは、標準入力からの入力のうち、「ARMADILLO」という文字列を含む行だけを標準出力に出力します。wcコマンドは、-lオプションが指定されたとき、標準入力から入力された行数を表示します。

このように、パイプを使うことで、シンプルな機能を持ったプログラムを組み合わせて、複雑な処理を簡単に記述することができます。

5.3.8.4. ヒアドキュメント

ヒアドキュメントを使うと、複数行に渡る長い文をコマンドの標準入力にすることができます。

<<[-]word
        here-document
delimiter

図5.25 ヒアドキュメントの文法


「<<」に続き、「word」を指定した次の行からヒアドキュメントとして、扱われます。ヒアドキュメントは、「word」のクォートを取り除いた「delimiter」が単独で現れる行まで続きます。「word」には、パラメータ展開、コマンド置換、算術展開、パス名展開は行われません。

[darmadillo ~]$ cat <<EOF
> hello
> world
> EOF
hello
world

図5.26 ヒアドキュメントの例


「word」がクォートされていない場合、ヒアドキュメントはパラメータ展開、コマンド置換、算術式展開されます。「word」が一部でもクォートされている場合、ヒアドキュメントの展開は行われません。

[darmadillo ~]$ var="hello world"
[darmadillo ~]$ cat <<EOF
> $var
> EOF
hello world
[darmadillo ~]$ cat <<'EOF'
> $var
> EOF
$var

図5.27 ヒアドキュメントの例(クォート)


「<<」と「word」の間に-を指定した場合、ヒアドキュメントの各行の先頭のタブが取り除かれます。この機能により、シェルスクリプト中にヒアドキュメントを記述する際に、自然なインデントで記述することができます。

5.3.9. 様々なコマンドの実行方法

ここで、コマンドの実行方法を再度確認しておきます。

5.3.9.1. 単純なコマンド

「基本的なコマンドの実行方法」では、コマンド名の後に引数を指定してコマンドを実行すると紹介しました。

コマンドの構文を、図5.28「単純なコマンドの文法」に示します。

コマンド名の前には、複数の変数の代入を書くことができます。

最初の単語が、コマンド名になります。コマンド名だけは必須です。

コマンド名の後には、複数の単語を引数として書くことができます。

引数の後には、リダイレクトに関する記述を書くことができます。

最後に制御演算子を書くことができます。制御演算子は、||、&、&&、;、;;、(、)、|、<newline>のいずれかの文字列です。

[変数の代入...] <単語> [単語...] [リダイレクション] [制御演算子]

図5.28 単純なコマンドの文法


5.3.9.2. パイプライン

パイプラインは、|で区切った一つ以上の単純なコマンドの列です。

[!] <command1> [| command2...]

図5.29 パイプラインの文法


「パイプ」で説明したように、パイプで接続すると、「command1」の標準出力は「command2」の標準入力に接続されます。この接続は、リダイレクトよりも先に実行されます。

パイプラインの終了ステータスは、最後のコマンドの終了ステータスになります。パイプラインの前に予約後である!がある場合、そのパイプラインの終了ステータスは、最後のコマンドの終了ステータスの論理否定をとった値になります。また、終了ステータスを返す前に、シェルはパイプライン中のすべてのコマンドの終了を待ちます。

パイプライン中の各コマンドは、サブシェル内で、それぞれ別のプロセスとして実行されます。

5.3.9.3. リスト

リストは、1つ以上のパイプラインを演算子;、&、&&、||のいずれかで区切って並べ、最後に;、&、<newline>のいずれかを置いたものです。

<pipeline1> [演算子 pipeline2...] [; or & or <newline>]

図5.30 リストの文法


リストコマンドが制御演算子&で終わっている場合、シェルはコマンドをサブシェル内でバックグラウンド実行します。シェルはコマンドが終了するのを待たずに、返却ステータス0を返します。

コマンドを;で区切った場合には、これらは順番に実行されます。シェルはそれぞれのコマンドが終了するのを順番に待ちます。返却ステータスは、最後に実行したコマンドの終了ステータスになります。

制御演算子&&はANDリストを示し、||はORリストを示します。ANDリストの場合は「pipeline1」が終了ステータス0を返した場合に限り「pipeline2」が実行されます。ORリストの場合は、「pipeline1」が0以外の終了ステータスを返した場合に限り「pipeline2」が実行されます。ANDリストとORリストの返却ステータスは、リスト中で最後に実行されたコマンドの終了ステータスです。

5.3.9.4. 複合コマンド

(list)
$(list)

図5.31 サブシェルの文法


()で括られた「list」はサブシェル内で実行されます。返却ステータスは「list」の終了ステータスです。

$()で括られた場合は、コマンド置換が行われます。

{ list; }

図5.32 グループコマンドの文法


「list」が単に現在のシェル環境で実行されます。「list」の最後は改行文字かセミコロンでなければなりません。これはグループコマンドと呼ばれます。返却ステータスは「list」の終了ステータスです。

((expression))
$((expression))

図5.33 算術評価の文法


「expression」が算術式評価の規則に従って評価されます。式の値が0でない場合、返却ステータスは0になります。そうでない場合、返されるステータスは1になります。

$(())で括られた場合は、算術式展開が行われます。

[[ expression ]]

図5.34 条件式評価の文法


条件式「expression」の評価値に従って0または1を返します。単語分割とパス名展開はの間の単語に対しては行われません。チルダ展開、パラメータと変数の展開、算術式展開、コマンド置換、プロセス置換、クォート除去は行われます。

==演算子と!=演算子が使われたとき、演算子の右の文字列はパターンと解釈され、パターンマッチング規則に従ってマッチングが行われます。文字列がパターンにマッチすれば返り値は0であり、マッチしなければ返り値は1になります。パターンの任意の部分をクォートして、文字列としてマッチさせることもできます。

5.3.9.5. バックグラウンド実行

リストの最後に&をつけて実行すると、そのリストはバックグラウンドプロセスとして実行されます。対して、&を付けずに実行したプロセスはフォアグラウンドプロセスといいます。

バックグラウンドプロセスは、コンソールからの入力を受け付けられません。

フォアグラウンドプロセスの実行中に、サスペンド文字(通常はCtrl+Z)を入力すると、そのプロセスは停止させられ、シェルに制御が戻ります。

この時、bgと入力するとバックグラウンドプロセスとして、プロセスを再開します。また、fgと入力するとフォアグラウンドプロセスとして実行を再開します。

フォアグラウンドプロセスは、最大一つだけしか実行できませんが、停止中のプロセスやバックグラウンドプロセスは複数存在することができます。それらには、サスペンド文字の入力によって停止したときや、&を付けて実行されたときにジョブ番号が割り振られます。bgやfgは、ジョブ番号を指定することができます。ジョブ番号は、%の後に続く数値で指定できます。

5.3.10. 制御構文

if、forなどの構造化プログラミングを行うために必要な制御構文について説明します。

5.3.10.1. if文

if文の書式を以下に示します。

if list1; then list2; [elif list3; then list4; ] ... [ else list5; ] fi

図5.35 if文の構文


最初に、「if list1」が実行されます。「list1」の終了ステータスが0ならば、「then list2」が実行されます。「list1」の終了ステータスが0以外で、「elif list3」があれば、「list3」が実行されます。「list3」の終了ステータスが0ならば「list4」が実行され、「list3」の終了ステータスが0以外でさらにelifがあれば、そのリストが実行されます。if、elifのリストがすべて0以外のステータスで終了し、「else list5」が指定されていた場合、「list5」が実行されます。

if文の終了ステータスは、最後に実行されたコマンドの終了ステータスとなります。真と評価された条件が一つもなかった場合は、0となります。

[ティップ]シェルでの真偽

シェルでは、0を真として扱います。

C言語での真偽とは逆なので混同しないようにしてください。

if文は、多くの場合条件式と共に用いられます。条件式は、[[またはtest、[コマンドで使用でき、ファイルの属性を調べたり、文字列比較、算術式比較を行うことができます。

条件式には、表5.3「文字列比較の条件式」表5.4「算術比較の条件式」表5.5「ファイル比較の条件式」に示すものがあります。

表5.3 文字列比較の条件式

条件式 結果

string

文字列がnull(空文字、長さ0)でなければ真

-n string

文字列がnull(空文字、長さ0)でなければ真

-z string

文字列がnull(空文字、長さ0)であれば真

string1 = string2

2つの文字列が等しければ真

string1 != string2

2つの文字列が等しくなければ真


表5.4 算術比較の条件式

条件式 結果

arg1 -eq arg2

2つの式が等しければ真

arg1 -ne arg2

2つの式が等しくなければ真

arg1 -gt arg2

arg1arg2より大きければ真

arg1 -ge arg2

arg1arg2と等しい、又はより大きければ真

arg1 -lt arg2

arg1arg2より小さければ真

arg1 -le arg2

arg1arg2と等しい、又はより小さければ真

! arg1

arg1が偽ならば真、arg1が真ならば偽


表5.5 ファイル比較の条件式

条件式 結果

-a file

fileが存在すれば真

-d file

fileが存在し、ディレクトリならば真

-f file

fileが存在し、通常ファイルならば真

-c file

fileが存在し、キャラクタデバイスファイルならば真

-b file

fileが存在し、ブロックデバイスファイルならば真

-r file

fileが存在し、読み込み可能ならば真

-w file

fileが存在し、書き込み可能ならば真

-x file

fileが存在し、実行可能ならば真

-s file

fileが存在し、サイズが0より大きければ真


#!/bin/sh

if [ ! -e $1 ]; then
        echo "$1: No such file or directory."
        exit 0
fi

if [ -d $1 ]; then
        echo "$1 is directory."
elif [ -f $1 ]; then
        echo "$1 is regular file."
elif [ -c $1 ]; then
        echo "$1 is character device file."
elif [ -b $1 ]; then
        echo "$1 is block device file."
else
        echo "$1 is unknown type file."
fi

図5.36 if文の例(if_sample.sh)


[ティップ]testコマンド

[は、引数の最後に]を取る、testコマンドの別名です。if文などを自然にかけるように、このような名前になっています。

[はコマンド名なので、[の後ろと]の前には、空白が必要です。

5.3.10.2. case文

C言語でのswitch文は、シェルではcase文で記述することができます。

case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac

図5.37 case文の構文


case文では、まず「word」を展開し、各「pattern」にマッチするか調べます。「word」にはチルダ展開、パラメータ展開、変数展開、算術式展開、コマンド展開、クォート除去が行われます。「pattern」は、「word」と同様に展開されたあと、評価されます。

いずれかの「pattern」にマッチすると、対応する「list」が実行されます。返却コードは、最後に実行された「list」の終了コードになります。マッチするパターンがなかった場合、返却コードは0となります。

case文の使用例を図5.38「case文の例(case_sample.sh)」に示します。第1引数にaまたはbを指定すると、「alfa」または「blabo」と表示します。cまたはCを指定すると、「charlie」と表示します。第1引数に、これら以外の文字列を指定する又は何も指定ない場合、「other string」と表示します。

#!/bin/sh

case $1 in
        a) echo alfa;;
        (b) echo bravo;;
        c|C) echo charlie;;
        *) echo "other string";;
esac

図5.38 case文の例(case_sample.sh)


5.3.10.3. for文

for文は、図5.39「for文の構文」のように書くことができます。

for name [in word]; do list; done

図5.39 for文の構文


「in」の後ろに記述された「word」が展開され、各単語が順に変数「name」に代入されます。代入の都度、「list」が実行されます。「in word」が省略された場合、すべての位置パラメータに対して「list」が実行されます。

返却ステータスは最後に実行されたコマンドの終了ステータスになります。「word」の展開結果が空となった場合、「list」は一度も実行されず、返却ステータスは0となります。

「word」を固定の文字列の並びで書く例を、図5.40「for文の例1(for_sample1.sh)」に示します。

#!/bin/sh

for a in alfa bravo charlie; do
        echo $a
done

図5.40 for文の例1(for_sample1.sh)


「word」はパス名展開されますので、図5.41「for文の例2(for_sample2.sh)」のように書くと、ルートディレクトリ直下のディレクトリ、ファイルをすべて表示します。

#!/bin/sh

for f in /*; do
        echo $f
done

図5.41 for文の例2(for_sample2.sh)


コマンド展開も行われますので、図5.42「for文の例3(for_sample3.sh)」のように書いても等価です。

#!/bin/sh

for f in $(ls /); do
        echo $f
done

図5.42 for文の例3(for_sample3.sh)


5.3.10.4. while文

for文は、図5.43「while文の構文」のように書くことができます。

while list1; do list2; done

図5.43 while文の構文


while文では「list1」が実行され、終了ステータスが0であれば、「list2」を実行します。これを、「list1」の終了ステータスが0である限り続けます。返却ステータスは、最後に実行された「list2」の終了ステータスになります。一度も実行されていないときは、0です。

#!/bin/sh

while true; do
        date
        sleep 1
done

図5.44 while文の例(while_sample.sh)


5.3.11. 関数

シェルでは、C言語と同様に関数を使うこともできます。関数の構文は、以下の通りです。

name() { list; }

図5.45 関数の構文


関数を定義したあと、関数名を記述することで、その関数を実行することができます。

#!/bin/sh

foo() {
        echo foo
}

echo "call foo function"
foo

図5.46 関数の例(function_sample1.sh)


[darmadillo ~]$ ./function_sample1.sh
call foo function
foo
call bar function
bar

図5.47 function_sample1.shの実行結果


関数は、通常のコマンドと同様に引数を取ることが出来ます。関数内では、一時的に位置パラメータが関数の引数に置き換えられます。

#!/bin/sh

foo() {
        echo "in function"
        echo $@
}

echo "in global"
echo $@

foo A B C

echo "in global"
echo $@

図5.48 関数の例(function_sample2.sh)


[darmadillo ~]$ ./function_sample2.sh 1 2 3
in global
1 2 3
in function
A B C
in global
1 2 3

図5.49 function_sample2.shの実行結果


シェルでは、変数のスコープは標準でグローバルです。localコマンドを使うことで、関数内のみのスコープを持つローカル変数を作ることができます。

#!/bin/sh

A="global"
B="global"
C="global"

foo() {
        echo "in function"
        A="function"
        local B="function"

        echo $A
        echo $B
        echo $C
}

echo "in global"
echo $A
echo $B
echo $C

foo

echo "in global"
echo $A
echo $B
echo $C

図5.50 関数の例(function_sample3.sh)


[darmadillo ~]$ ./function_sample3.sh
in global
global
global
global
in function
function
function
global
in global
function
global
global

図5.51 function_sample3.shの実行結果


returnコマンドを使用することで、関数の戻り値を指定することができます。returnコマンドがなかったり、returnコマンドに戻り値を指定しなかった場合は、関数の中で最後に実行されたコマンドの終了ステータスが戻り値になります。

シェルの関数は、戻り値に文字列を指定することはできません。関数の結果を文字列で受け取りたい場合は、グローバル変数を使うか、関数内で標準出力へ文字列を出力しコマンド置換する方法があります。

#!/bin/sh

foo() {
        return 10
}

bar() {
        echo "string"
}

foo
echo "foo returns $?"
echo "bar returns $(bar)"

図5.52 関数の例(function_sample4.sh)


[darmadillo ~]$ ./function_sample4.sh
foo returns 10
bar returns string

図5.53 function_sample4.shの実行結果