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

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

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

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

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

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

5.1. シェルの種類

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

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

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

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

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

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

図5.1 for文の例1


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

[armadillo ~]# for dir in /*
> do
> echo $dir
> done
/bin
/boot
/dev
/etc
/home
/lib
/lost+found
/media
/mnt
/opt
/proc
/root
/run
/sbin
/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」 を指定しているのでシステムの標準シェルでスクリプトが実行されます。

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

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

[armadillo ~]# chmod +x example.sh
[armadillo ~]# ./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の構文規則は、 本章で説明するものがすべてではありません。より詳細な説明は、 bashのmanページを参照してください。

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

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

[armadillo ~]# echo hello world
hello world
[armadillo ~]# echo hello           world
hello world

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


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

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

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

[armadillo ~]# echo "hello           world"
hello           world

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


5.3.2. 変数

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

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

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

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

図5.7 変数の例


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

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

図5.8 算術演算の例


bashでは変数の1次元配列を扱うこともできます。

[armadillo ~]# a[0]="hello"
[armadillo ~]# a[1]="world"
[armadillo ~]# echo ${a[0]}
hello
[armadillo ~]# echo ${a[1]}
world

図5.9 配列の例


5.3.3. 環境変数

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

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

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

環境変数 説明

HOME

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

PWD

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

OLDPWD

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

PATH

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

IFS

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

PS1

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

PS2

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


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

[armadillo ~]# env
XDG_SESSION_ID=c3
HUSHLOGIN=FALSE
USER=root
PWD=/root
HOME=/root
MAIL=/var/mail/root
SHELL=/bin/bash
TERM=vt220
SHLVL=1
LOGNAME=root
XDG_RUNTIME_DIR=/run/user/0
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env

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


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

[armadillo ~]# export MY_ENV=value
[armadillo ~]# echo $MY_ENV
value

図5.11 環境変数の設定


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種類があります。

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

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

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

[armadillo ~]# echo $HOME
/root
[armadillo ~]# echo \$HOME
$HOME
[armadillo ~]# echo '$HOME'
$HOME
[armadillo ~]# echo "$HOME"
/root
[armadillo ~]# echo "\$HOME"
$HOME

図5.12 クォートの例


5.3.6. 展開

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

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

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

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

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

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

[armadillo ~]# echo ~root
/root
[armadillo ~]# echo ~atmark/file
/home/atmark/file
[armadillo ~]# echo ~unavailuser
~unavailuser
[armadillo ~]# pwd
/root
[armadillo ~]# cd /
[armadillo /]# echo ~+
/
[armadillo /]# echo ~-
/root

図5.13 チルダ展開の例


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

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

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

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

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

[armadillo ~]# var=$(date)
[armadillo ~]# echo $var
Thu Apr 9 13:00:19 JST 2020

図5.14 コマンド置換の例


$((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になります。

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

図5.15 終了ステータスの例


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

5.3.8. 入出力の扱い

5.3.8.1. 標準入出力

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

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

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

5.3.8.2. リダイレクト

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

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

[armadillo ~]# echo hello > log

図5.16 出力のリダイレクト


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

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

[armadillo ~]# echo hello >> log

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


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

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

[armadillo ~]# cat nonexistent_file 2> error
[armadillo ~]# cat error
cat: nonexistent_file: No such file or directory

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


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

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

[armadillo ~]# echo hello > log
[armadillo ~]# echo hello 1> log

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


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

[armadillo ~]# somecommand > log 2> error

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


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

[armadillo ~]# somecommand > log 2>&1

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


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

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

[armadillo ~]# somecommand > /dev/null 2>&1

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


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

[armadillo ~]# cat < /etc/hostname
armadillo

図5.23 入力のリダイレクト


5.3.8.3. パイプ

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

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

[armadillo ~]# echo hello | cat
hello

図5.24 パイプ


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

[armadillo ~]# zcat /proc/config.gz | grep ARMADILLO | wc -l
4

図5.25 パイプの例


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

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

5.3.8.4. ヒアドキュメント

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

<<[-]word
        here-document
delimiter

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


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

[armadillo ~]# cat <<EOF
> hello
> world
> EOF
hello
world

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


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

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

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


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

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

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

5.3.9.1. 単純なコマンド

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

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

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

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

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

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

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

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

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


5.3.9.2. パイプライン

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

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

図5.30 パイプラインの文法


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

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

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

5.3.9.3. リスト

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

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

図5.31 リストの文法


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

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

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

5.3.9.4. 複合コマンド

(list)
$(list)

図5.32 サブシェルの文法


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

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

{ list; }

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


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

((expression))
$((expression))

図5.34 算術評価の文法


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

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

[[ expression ]]

図5.35 条件式評価の文法


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

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

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

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

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

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

この時、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.36 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「ファイル比較の条件式」に示すものがありま す。

記載されていない比較の条件式については、man test を参照してください。

表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

arg1がarg2より大きければ真

arg1 -ge arg2

arg1がarg2と等しい、又はより大きければ真

arg1 -lt arg2

arg1がarg2より小さければ真

arg1 -le arg2

arg1がarg2と等しい、又はより小さければ真

! arg1

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


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

条件式 結果

-e 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.37 if文の例(if_sample.sh)


[ティップ]testコマンド

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

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

5.3.10.2. case文

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

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

図5.38 case文の構文


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

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

case文の使用例を図5.39「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.39 case文の例(case_sample.sh)


5.3.10.3. for文

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

for name [in word]; do list; done

図5.40 for文の構文


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

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

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

#!/bin/sh

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

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


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

#!/bin/sh

for f in /*; do
        echo $f
done

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


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

#!/bin/sh

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

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


5.3.10.4. while文

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

while list1; do list2; done

図5.44 while文の構文


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

#!/bin/sh

while true; do
        date
        sleep 1
done

図5.45 while文の例(while_sample.sh)


5.3.11. 関数

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

name() { list; }

図5.46 関数の構文


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

#!/bin/sh

foo() {
        echo foo
}

echo "call foo function"
foo

図5.47 関数の例(function_sample1.sh)


[armadillo ~]# ./function_sample1.sh
call foo function
foo

図5.48 function_sample1.shの実行結果


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

#!/bin/sh

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

echo "in global"
echo $@

foo A B C

echo "in global"
echo $@

図5.49 関数の例(function_sample2.sh)


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

図5.50 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.51 関数の例(function_sample3.sh)


[armadillo ~]# ./function_sample3.sh
in global
global
global
global
in function
function
function
global
in global
function
global
global

図5.52 function_sample3.shの実行結果


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

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

#!/bin/sh

foo() {
        return 10
}

bar() {
        echo "string"
}

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

図5.53 関数の例(function_sample4.sh)


[armadillo ~]# ./function_sample4.sh
foo returns 10
bar returns string

図5.54 function_sample4.shの実行結果




[23] 環境によっては 「¥」と表示されることもありますが同じ意味です。