シェルは、コマンドラインプロンプトから入力されたコマンドを、Linuxシス
テムに受け渡す機能を持ったプログラムです。シェルは、ユーザーから入力さ
れたコマンドを一つ一つ実行するだけでなく、ファイルに記述されたコマンド
を逐次実行するインタープリタとしての機能も有しています。
シェルは構造化プログラミングが可能で、その機能は非常に強力です。Linux
の豊富なコマンドと合わせることで、アプリケーションプログラムのプロトタ
イプをシェルスクリプトで記述することもできるでしょう。
シェルは、単にコマンドを一つ一つ実行するだけでなく、変数も使うことがで
き、forやifなどの構造化プログラミングが可能な構文を解釈することができ
ます。
例えば、ルートディレクトリ直下のディレクトリをすべて表示するには、以下の
ようにします。構文については、詳しくは「シェルの構文」で説明し
ますが、ここではルートディレクトリ直下のディレクトリ名をdirという変数
に代入して、順番にechoしていると考えてください。
これは、以下のように複数行に分けて書くこともできます。
コマンド入力の2行目から、プロンプトが「>」に変わっていることに注目して
ください。コマンドの終了はシェルが自動的に判断し、入力されたコマンドを
実行してプロンプトを元に戻します。
シェルは、シェルスクリプトとしてファイルに記述されたコマンドを実行する
こともできます。
シェルスクリプトの決まり事として、先頭行にある「#!」の後にそのスクリプ
トを処理するプログラムを指定することできます。今回の場合は、「/bin/sh」
を指定しているのでシステムの標準シェルでスクリプトが実行されます。
先頭行以外にある「#!」以外の「#」以降の文字列はコメントして扱われます。
そのため、「#シェルスクリプトの例」という行はコメントになり、実行結果
に影響を与えません。
上記プログラムを実行すると、以下のようになります。
シェルは拡張子を判別しないので、「.sh」という拡張子はつける必要はあり
ません。
本章では、シェルの基本的な構文について説明します。bashの構文規則は、
本章で説明するものがすべてではありません。より詳細な説明は、
bashのmanページを参照してください。
シェルでコマンドを実行するには、以下のように記述します。
先頭に実行するコマンドの名前を指定します。「echo」は、複数の引数をとり、
各引数を表示するプログラムです。コマンドと引数、引数と引数の間はスペー
スで区切ります。スペースで区切られた、シェルが1単位とみなす文字列を単
語といいます。単語は、スペース以外に「|、&、;、(、)、タブ」で区
切ることができます。単語の区切りとなる文字をメタ文字といいます。
メタ文字は複数あっても構いませんので、上記二つのコマンドは等価です。
引数に空白を含めたい場合は、「"」か「'」で囲みます。
シェルでは、変数を使用することができます。C言語と異なり、変数の宣言は
必要ありません。標準では、すべての変数は文字列型として扱われます。
変数名は、英数字と「_」(アンダースコア)だけから構成され、かつ最初の文
字が英字か「_」である必要があります。英字の大文字と小文字は区別されま
す。
変数に値を代入する際には、「=」を使用します。「=」の前後に空白を入れて
はいけません。空白を含む文字列を代入する場合は、「"」又は「'」で囲む必
要があります。また、変数の値を参照するときには、変数名の前に「$」を付
けます。または、{}(ブレース)を使用して${変数名}と記述することもでき
ます。
変数に対して算術演算を行う場合は、$(())構文を使います。
bashでは変数の1次元配列を扱うこともできます。
bashでは、いくつかの変数が環境変数(シェル変数)としてあらかじめ設定され
ています。環境変数の名前は、大文字になっていますので、これらと混同しな
いようにユーザーが設定する変数には小文字を使うのが良いでしょう。
以下に、主な環境変数のリストを示します。
表5.1 主な環境変数のリスト
環境変数 | 説明 |
---|
HOME | 現在のユーザーのホームディレクトリ。 |
PWD | 現在の作業ディレクトリ。 |
OLDPWD | 1つ前の作業ディレクトリ。 |
PATH | コマンドの検索パス。シェルがコマンドを検索するディレクトリをコロンで区切って並べたリスト。 |
IFS | 内部フィールド区切り文字。デフォルト値は「`<空白><タブ><改行>」'。 |
PS1 | プライマリのプロンプト文字列。PS1を変更することでプロンプトの表示をカスタマイズすることができる。 |
PS2 | セカンダリのプロンプト文字列。図5.2「for文の例2」のように複数行に分けてコマンドを記述した場合に使用される。 |
envコマンドを使用すると、すべての環境変数を表示することができます。
新しく環境変数を設定するには、exportコマンドを使います。
パラメータは、値を保持するためのものです。パラメータは名前、数字、特定
の特殊文字のいずれかで表現されます。変数とは、名前で表現されたパラメー
タにすぎません。
変数以外のパラメータとして、位置パラメータ、特殊パラメータがあります。
位置パラメータは、1以上の数値で表されるパラメータです。位置パラメータ
には、シェルの引数が代入されます。第1引数は$1に、第2引数は$2にというよ
うに順番に代入されていきます。10以上の位置パラメータを参照する場合には、
${10}のように、{}をつけます。
シェル関数の中では、位置パラメータは関数の引数に置き換えられます。
(「関数」参照。)
特殊パラメータは参照だけが可能であり、値を代入することはできないパラメー
タです。表5.2「特殊パラメータのリスト」に、bashとashで使用可能な特殊
パラメータの一覧を示します。
表5.2 特殊パラメータのリスト
特殊パラメータ | 説明 |
---|
* | すべての位置パラメータに展開される。 |
@ | すべての位置パラメータに展開される。[] |
# | 位置パラメータの個数に展開される。 |
? | 最後に実行されたフォアグラウンドプロセスの終了ステータスに展開される。 |
! | 最後に実行されたバックグラウンドプロセスのプロセスIDに展開される。 |
$ | プロセスIDに展開される。 |
0 | シェルまたはシェルスクリプトの名前に展開される。 |
クォートを使うと、特殊文字の意味や後述する展開を無効にすることができま
す。クォートの方法には、エスケープ文字、シングルクォート、ダブルクォー
トの3種類があります。
クォートされていないバックスラッシュ「\」[]をエスケープ文字
といいます。エスケープ文字の直後の特殊文字は、特殊文字としての意味を失
います。
シングルクォート「'」で文字を囲むと、クォート内部では特殊文字は特殊文字
としての意味を失い、展開もおこなわれません。シングルクォートの間にシン
グルクォートを置くことはできません。
ダブルクォート「"」で文字を囲むと、クォート内部では「$、`、\」以外の特
殊文字は、特殊文字としての意味を失います。「$」と「`」は、特殊文字とし
ての意味を保持します。バックスラッシュは、直後の文字が「$、`、"、
<newline>」のいずれかの場合、特殊文字としての意味を保持します。
シェルでは、特定の書き方をした場合、別の文字列として置き換えられて解釈
されることがあります。この置換処理を展開といいます。
展開は、ブレース展開、チルダ展開、パラメータ・変数・算術式展開、コマン
ド置換(左から右へ)、単語分割、パス名展開の順番で行われます。
ブレース展開では、a{D,C,B}eがaDe aCe aBeに展開されます。bash
では使用可能ですが、shでは使用できません。
チルダ「~」で単語が始まった場合、スラッシュよりも前にある文字が、チル
ダプレフィックスと解釈されます。有効なチルダプレフィックスの場合、チル
ダ展開が行われます。
チルダプレフィックスがユーザー名と一致した場合、そのユーザーのホームディ
レクトリに展開されます。ユーザー名が指定されない場合は、シェルを実行し
ているユーザーのホームディレクトリに展開されます。
チルダプレフィックスが+の場合、環境変数PWDに、-の場合、環境変数OLDPWD
に展開されます。(この展開は、shではおこなわれません。)
「$」文字があると、パラメータ展開、コマンド置換、算術式展開が行われま
す。展開されるパラメータ名やシンボルは、ブレースで括ることもできます。
ブレースは省略可能ですが、変数の直後に変数名の一部と解釈できる文字が置
かれた場合に、その文字と共にパラメータが展開されてしまうのを防ぐために
用意されています。
パラメータ展開では、これまで説明してきた変数、環境変数、位置パラメータ、
特殊パラメータの展開が行われます。
ダブルクォートの内部で$*の展開が行われた時は、それぞれのパラメータを
IFSでの最初の文字で区切って並べた1つの単語に展開されます。IFSが設定さ
れていなければ、パラメータは空白で区切られます。IFSが空文字列の場合、
すべてのパラメータはつなげられます。
ダブルクォートの内部で$@の展開が行われた時は、それぞれのパラメータは別々
の単語に展開されます。位置パラメータがない場合は、空文字に展開されます。
(つまり、取り除かれます。)
$(command)または、`command`と書くと、commandの実行結果に置き換え
られます。
$((expression))と書くと、expressionを算術式評価して、その結果に
置き換えられます。
シェルは上記の展開を行った後、IFSに指定されたそれぞれの文字を区切り文
字として、単語に分割します。
単語分割に続いて、パス名展開をおこないます。単語分割を行った後の単語が、
「*、?、[」を含んでいた場合、その単語はパターンとみなされます。パター
ンに一致するファイルがあれば、その単語はマッチするファイル名をアルファ
ベット順にソートしたリストに置換されます。マッチするファイル名がない場
合、その単語は置き換えられません。
「*」は、空文字列を含む任意の文字列にマッチします。また、「?」は
、任意の1文字にマッチします。
「[」と「]」で文字列を括ると、マッチする文字のパターンを指定できます。
単に1文字以上の文字を指定した場合は、指定した文字のうち、任意の1文字に
マッチします。2つの文字の間にハイフンをいれたものを指定した場合、指定
した文字とその間の範囲にある文字にマッチします。また、「[」の直後に「!」
または「^」を指定した場合は、指定した文字以外の任意の文字がマッチしま
す。
「[」と「]」の間には、文字クラスを指定することができます。文字クラスは
、[:class:]のように「:」コロンで括ります。文字クラスには、
alnum、alpha、ascii、blank、cntrl、digit、graph、lower、print、punct、
space、upper、xdigitがあります。
最後に、クォートの削除がおこなわれます。クォートされていない
「\、'、"」のうち、上記の展開の結果でないものはすべて削除されます。
シェルは、終了ステータス0で終了したコマンドは正常終了したとみなします。
0以外の終了ステータスは失敗を意味します。
あるコマンドが、シグナルNで終了したときには、終了ステータスは128+Nにな
ります。コマンドが見つからなかった場合の終了ステータスは127で、コマン
ドが見つかったけれど実行できなかった場合には、126になります。
exitコマンドを使用すると、シェルの終了ステー
タスを指定することができます。
C言語でプログラミングする際に、標準入力(stdin)、標準出力(stdout)、標準
エラー出力(stderr)を使用したことがあると思います。printf関数で出力され
る先が標準出力で、scanf関数での入力元が標準入力です。
シェルを介してプログラムを実行する際、標準入力、標準出力、標準エラー出
力は、通常、コンソールとなります。
シェルでは、以降で説明するリダイレクトとパイプという機能を用いて、標準
入出力の入力元や出力先を、コンソールではなくファイルや他のプログラムに
することができます。
シェルには、プログラムの入出力の入力元や出力先を変更する機能があります。
その機能のことを、リダイレクトといいます。
標準出力をコンソールではなく、ファイルにする場合には以下のようにします。
このコマンドを実行すると、「log」という名前のファイルに「hello」と書き
込まれます。fopen("log", "w")とした時と同様に、ファイルが存在しなけれ
ばファイルが新たに作成され、また、ファイルが存在する場合はファイルの既
存の内容はすべて上書きされます。
「log」に追記したい場合は、以下のように>>を使用します。
リダイレクトする際には、ファイルディスクリプタを指定することもできます。
標準入力のファイルディスクリプタは0、標準出力は1、標準エラー出力は2と
決まっています。
そのため、標準エラー出力への出力だけ、ファイルに出力したい場合は、以下
のように書けます。
「nonexistent_file」(存在しないファイル)をcat
コマンドで表示しようとした際のエラー出力を「error」という名前のファイ
ルに保存しています。
ファイルディスクリプタを指定しない場合は、標準出力として扱われるので、
以下の二つのコマンドは等価です。
また、リダイレクトは複数指定することもできます。
上記の記述では、「somecommand」の標準
出力への出力を「log」というファイルに書き込み、標準エラー出力への出力
を「error」というファイルに書き込みます。
このように記述すると、「somecommand」
の標準出力と標準エラー出力への出力を同時に「log」というファイルに書き
込む事ができます。
コマンドの途中経過をコンソールに表示する必要がない場合もあります。その
ような場合は、/dev/nullへ書き込むことで出力
を捨てることができます。
出力のリダイレクトと同様に、入力もリダイレクトすることができます。
リダイレクト機能を使うと、入出力をファイルと結びつけることができました。
パイプ機能を使うと、あるプログラムの出力と別のプログラムの入力を結びつ
けることができます。
以下の例では、echoプログラムの標準出力を
catプログラムの標準入力に結びつけています。
以下の例では、カーネルコンフィギュレーションの中に、「ARMADILLO」と書
かれた行が何行あるか表示しています。
まず、zcatコマンドが
/proc/config.gz(カーネルコンフィギュレーシ
ョンをgzip圧縮したファイル)を展開して標準出力に出力します。その出力を
、grepコマンドの標準入力にパイプで繋いでいま
す。grepコマンドは、標準入力からの入力のうち
、「ARMADILLO」という文字列を含む行だけを標準出力に出力します。
wcコマンドは、-lオプシ
ョンが指定されたとき、標準入力から入力された行数を表示します。
このように、パイプを使うことで、シンプルな機能を持ったプログラムを組み
合わせて、複雑な処理を簡単に記述することができます。
ヒアドキュメントを使うと、複数行に渡る長い文をコマンドの標準入力にする
ことができます。
「<<」に続き、「word」を指定した次の行からヒアドキュメントとして、
扱われます。ヒアドキュメントは、「word」のクォートを取り除いた
「delimiter」が単独で現れる行まで続きます。「word」には、パラメータ展
開、コマンド置換、算術展開、パス名展開は行われません。
「word」がクォートされていない場合、ヒアドキュメントはパラメータ展開、
コマンド置換、算術式展開されます。「word」が一部でもクォートされている
場合、ヒアドキュメントの展開は行われません。
「<<」と「word」の間に「-」を指定した場合、ヒアドキュメントの各
行の先頭のタブが取り除かれます。この機能により、シェルスクリプト中にヒ
アドキュメントを記述する際に、自然なインデントで記述することができます
。
ここで、コマンドの実行方法を再度確認しておきます。
「基本的なコマンドの実行方法」では、コマンド名の後に引数を指定してコ
マンドを実行すると紹介しました。
コマンドの構文を、図5.29「単純なコマンドの文法」に示します。
コマンド名の前には、複数の変数の代入を書くことができます。
最初の単語が、コマンド名になります。コマンド名だけは必須です。
コマンド名の後には、複数の単語を引数として書くことができます。
引数の後には、リダイレクトに関する記述を書くことができます。
最後に制御演算子を書くことができます。制御演算子は「||、&、&&、;、;;、
(、)、|、<newline>」のいずれかの文字列です。
パイプラインは、「|」で区切った一つ以上の単純なコマンドの列です。
「パイプ」で説明したように、パイプで接続すると、「command1」の
標準出力は「command2」の標準入力に接続されます。この接続は、リダイレク
トよりも先に実行されます。
パイプラインの終了ステータスは、最後のコマンドの終了ステータスになりま
す。パイプラインの前に予約語である!がある場合、そのパイプラインの終了
ステータスは、最後のコマンドの終了ステータスの論理否定をとった値になり
ます。また、終了ステータスを返す前に、シェルはパイプライン中のすべてのコ
マンドの終了を待ちます。
パイプライン中の各コマンドは、サブシェル内で、それぞれ別のプロセスとし
て実行されます。
リストは、1つ以上のパイプラインを演算子「;、&、&&、||」のいずれかで区
切って並べ、最後に「;、&、<newline>」のいずれかを置いたものです。
リストコマンドが制御演算子&で終わっている場合、シェルはコマンドをサブ
シェル内でバックグラウンド実行します。シェルはコマンドが終了するのを待
たずに、返却ステータス0を返します。
コマンドを;で区切った場合には、これらは順番に実行されます。シェルはそ
れぞれのコマンドが終了するのを順番に待ちます。返却ステータスは、最後に
実行したコマンドの終了ステータスになります。
制御演算子&&はANDリストを示し、「||」はORリストを示します。ANDリストの
場合は「pipeline1」が終了ステータス0を返した場合に限り「pipeline2」が
実行されます。ORリストの場合は、「pipeline1」が0以外の終了ステータスを
返した場合に限り「pipeline2」が実行されます。ANDリストとORリストの返却
ステータスは、リスト中で最後に実行されたコマンドの終了ステータスです。
()で括られた「list」はサブシェル内で実行されます。返却ステータスは
「list」の終了ステータスです。
$()で括られた場合は、コマンド置換が行われます。
「list」が単に現在のシェル環境で実行されます。「list」の最後は改行文字
かセミコロンでなければなりません。これはグループコマンドと呼ばれます。
返却ステータスは「list」の終了ステータスです。
「expression」が算術式評価の規則に従って評価されます。式の値が0でない
場合、返却ステータスは0になります。そうでない場合、返されるステータス
は1になります。
$(())で括られた場合は、算術式展開が行われます。
条件式「expression」の評価値に従って0または1を返します。単語分割とパス
名展開はの間の単語に対しては行われません。チルダ展開、パラメータ
と変数の展開、算術式展開、コマンド置換、プロセス置換、クォート除去は行
われます。
==演算子と!=演算子が使われたとき、演算子の右の文字列はパターンと
解釈され、パターンマッチング規則に従ってマッチングが行われます。文字列
がパターンにマッチすれば返り値は0であり、マッチしなければ返り値は1にな
ります。パターンの任意の部分をクォートして、文字列としてマッチさせるこ
ともできます。
リストの最後に「&」をつけて実行すると、そのリストはバックグラウン
ドプロセスとして実行されます。対して、「&」を付けずに実行したプロ
セスはフォアグラウンドプロセスといいます。
バックグラウンドプロセスは、コンソールからの入力を受け付けられません。
フォアグラウンドプロセスの実行中に、サスペンド文字(通常は
CtrlZ)を
入力すると、そのプロセスは停止させられ、シェルに制御が戻ります。
この時、bgと入力するとバックグラウンドプロセスとして、プロセスを再開し
ます。また、fgと入力するとフォアグラウンドプロセスとして実行を再開しま
す。
フォアグラウンドプロセスは、最大一つだけしか実行できませんが、停止中の
プロセスやバックグラウンドプロセスは複数存在することができます。それら
には、サスペンド文字の入力によって停止したときや、「&」を付けて実
行されたときにジョブ番号が割り振られます。bgやfgは、ジョブ番号を指定す
ることができます。ジョブ番号は、「%」の後に続く数値で指定できます。
if、forなどの構造化プログラミングを行うために必要な制御構文について説
明します。
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より大きければ真 |
| testコマンド |
---|
「[」は、引数の最後に「]」を取る、testコマン
ドの別名です。if文などを自然にかけるように、このような名前になっていま
す。 「[」はコマンド名なので、「[」の後ろと「]」の前には、空白が必要です。 |
C言語でのswitch文は、シェルでは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」と表示します。
while文は、図5.44「while文の構文」のように書くことができます。
while文では「list1」が実行され、終了ステータスが0であれば、「list2」を
実行します。これを、「list1」の終了ステータスが0である限り続けます。返
却ステータスは、最後に実行された「list2」の終了ステータスになります。
一度も実行されていないときは、0です。
シェルでは、C言語と同様に関数を使うこともできます。関数の構文は、以下
の通りです。
関数を定義したあと、関数名を記述することで、その関数を実行することがで
きます。
関数は、通常のコマンドと同様に引数を取ることができます。関数内では、一
時的に位置パラメータが関数の引数に置き換えられます。
シェルでは、変数のスコープは標準でグローバルです。
localコマンドを使うことで、関数内のみのスコー
プを持つローカル変数を作ることができます。
returnコマンドを使用することで、関数の戻り値
を指定することができます。returnコマンドがな
かったり、returnコマンドに戻り値を指定しなか
った場合は、関数の中で最後に実行されたコマンドの終了ステータスが戻り値
になります。
シェルの関数は、戻り値に文字列を指定することはできません。関数の結果を
文字列で受け取りたい場合は、グローバル変数を使うか、関数内で標準出力へ
文字列を出力しコマンド置換する方法があります。