シェルは、コマンドラインプロンプトから入力されたコマンドを、Linuxシステムに受け渡す機能を持ったプログラムです。シェルは、ユーザーから入力されたコマンドを一つ一つ実行するだけでなく、ファイルに記述されたコマンドを逐次実行するインタープリタとしての機能も有しています。
シェルは構造化プログラミングが可能で、その機能は非常に強力です。Linuxの豊富なコマンドと合わせることで、アプリケーションプログラムのプロトタイプをシェルスクリプトで記述することもできるでしょう。
シェルは、単にコマンドを一つ一つ実行するだけでなく、変数も使うことができ、forやifなどの構造化プログラミングが可能な構文を解釈することができます。
例えば、ルートディレクトリ直下のディレクトリをすべて表示するには、以下のようにします。構文については、詳しくは「シェルの構文」で説明しますが、ここではルートディレクトリ直下のディレクトリ名をdirという変数に代入して、順番にechoしていると考えてください。
これは、以下のように複数行に分けて書くこともできます。
コマンド入力の2行目から、プロンプトが「>」に変わっていることに注目してください。コマンドの終了はシェルが自動的に判断し、入力されたコマンドを実行してプロンプトを元に戻します。
シェルは、シェルスクリプトとしてファイルに記述されたコマンドを実行することもできます。
シェルスクリプトの決まり事として、先頭行にある「#!」の後にそのスクリプトを処理するプログラムを指定することできます。今回の場合は、「/bin/sh」を指定しているのでシステムの標準シェルでスクリプトが実行されます。
先頭行以外にある「#!」以外の「#」以降の文字列はコメントして扱われます。そのため、「#シェルスクリプトの例」という行はコメントになり、実行結果に影響を与えません。
上記プログラムを実行すると、以下のようになります。
シェルは拡張子を判別しないので、「.sh」という拡張子はつける必要はありません。
本章では、シェルの基本的な構文について説明します。bashとashの構文の違いについても指摘します。bash/ashの構文規則は、本章で説明するものがすべてではありません。より詳細な説明は、bashのmanページを参照してください。
シェルでコマンドを実行するには、以下のように記述します。
先頭に実行するコマンドの名前を指定します。「echo」は、複数の引数をとり、各引数を表示するプログラムです。コマンドと引数、引数と引数の間はスペースで区切ります。スペースで区切られた、シェルが1単位とみなす文字列を単語といいます。単語は、スペース以外に「|、&、;、(、)、タブ」で区切ることができます。単語の区切りとなる文字をメタ文字といいます。
メタ文字は複数あっても構いませんので、上記二つのコマンドは等価です。
引数に空白を含めたい場合は、「"」か「'」で囲みます。
シェルでは、変数を使用することができます。C言語と異なり、変数の宣言は必要ありません。標準では、すべての変数は文字列型として扱われます。
変数名は、英数字と「_」(アンダースコア)だけから構成され、かつ最初の文字が英字か「_」である必要があります。英字の大文字と小文字は区別されます。
変数に値を代入する際には、「=」を使用します。「=」の前後に空白を入れてはいけません。空白を含む文字列を代入する場合は、「"」又は「'」で囲む必要があります。また、変数の値を参照するときには、変数名の前に「$」を付けます。または、{}(ブレース)を使用して${変数名}と記述することもできます。
変数に対して算術演算を行う場合は、$(())構文を使います。
bashでは変数の1次元配列を扱うこともできます。残念ながら、ashでは配列を扱うことができないので、ここでは説明を省きます。
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では使用可能ですが、ashでは使用できません。
チルダ「~」で単語が始まった場合、スラッシュよりも前にある文字が、チルダプレフィックスと解釈されます。有効なチルダプレフィックスの場合、チルダ展開が行われます。
チルダプレフィックスがユーザー名と一致した場合、そのユーザーのホームディレクトリに展開されます。ユーザー名が指定されない場合は、シェルを実行しているユーザのホームディレクトリに展開されます。
チルダプレフィックスが+の場合、環境変数PWDに、-の場合、環境変数OLDPWDに展開されます。(この展開は、ashではおこなわれません。)
「$」文字があると、パラメータ展開、コマンド置換、算術式展開が行われます。展開されるパラメータ名やシンボルは、ブレースで括ることもできます。ブレースは省略可能ですが、変数の直後に変数名の一部と解釈できる文字が置かれた場合に、その文字と共にパラメータが展開されてしまうのを防ぐために用意されています。
パラメータ展開では、これまで説明してきた変数、環境変数、位置パラメータ、特殊パラメータの展開が行われます。
ダブルクォートの内部で$*の展開が行われた時は、それぞれのパラメータを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言語でプログラミングする際に、標準出力(stdout)、標準入力(stdin)、標準エラー出力(stderr)を使用したことがあると思います。printf関数で出力される先が標準出力で、scanf関数での入力元が標準入力です。
シェルを介してプログラムを実行する際、標準出力、標準入力、標準エラー出力は、通常、コンソールとなります。
シェルでは、以降で説明するリダイレクトとパイプという機能を用いて、標準入出力の入力元や出力先を、コンソールではなくファイルや他のプログラムにすることができます。
シェルには、プログラムの入出力の入力元や出力先を変更する機能があります。その機能のことを、リダイレクトといいます。
標準出力をコンソールではなく、ファイルにする場合には以下のようにします。
このコマンドを実行すると、「log」という名前のファイルに「hello」と書き込まれます。fopen("log", "w")とした時と同様に、ファイルが存在しなければファイルが新たに作成され、また、ファイルが存在する場合はファイルの既存の内容はすべて上書きされます。
「log」に追記したい場合は、以下のように>>を使用します。
リダイレクトする際には、ファイルディスクリプタを指定することもできます。標準出力のファイルディスクリプタは1、標準入力は2、標準エラー出力は3と決まっています。
そのため、標準エラー出力への出力だけ、ファイルに出力したい場合は、以下のように書けます。
「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.28「単純なコマンドの文法」に示します。
コマンド名の前には、複数の変数の代入を書くことができます。
最初の単語が、コマンド名になります。コマンド名だけは必須です。
コマンド名の後には、複数の単語を引数として書くことができます。
引数の後には、リダイレクトに関する記述を書くことができます。
最後に制御演算子を書くことができます。制御演算子は「||、&、&&、;、;;、(、)、|、<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になります。パターンの任意の部分をクォートして、文字列としてマッチさせることもできます。
リストの最後に「&」をつけて実行すると、そのリストはバックグラウンドプロセスとして実行されます。対して、「&」を付けずに実行したプロセスはフォアグラウンドプロセスといいます。
バックグラウンドプロセスは、コンソールからの入力を受け付けられません。
フォアグラウンドプロセスの実行中に、サスペンド文字(通常はCtrl+Z)を入力すると、そのプロセスは停止させられ、シェルに制御が戻ります。
この時、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「ファイル比較の条件式」に示すものがあります。
表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 ファイル比較の条件式
条件式 | 結果
|
---|
-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より大きければ真
|
| testコマンド |
---|
「[」は、引数の最後に「]」を取る、testコマンドの別名です。if文などを自然にかけるように、このような名前になっています。 「[」はコマンド名なので、「[」の後ろと「]」の前には、空白が必要です。 |
C言語でのswitch文は、シェルでは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」と表示します。
for文は、図5.43「while文の構文」のように書くことができます。
while文では「list1」が実行され、終了ステータスが0であれば、「list2」を実行します。これを、「list1」の終了ステータスが0である限り続けます。返却ステータスは、最後に実行された「list2」の終了ステータスになります。一度も実行されていないときは、0です。
シェルでは、C言語と同様に関数を使うこともできます。関数の構文は、以下の通りです。
関数を定義したあと、関数名を記述することで、その関数を実行することができます。
関数は、通常のコマンドと同様に引数を取ることができます。関数内では、一時的に位置パラメータが関数の引数に置き換えられます。
シェルでは、変数のスコープは標準でグローバルです。localコマンドを使うことで、関数内のみのスコープを持つローカル変数を作ることができます。
returnコマンドを使用することで、関数の戻り値を指定することができます。returnコマンドがなかったり、returnコマンドに戻り値を指定しなかった場合は、関数の中で最後に実行されたコマンドの終了ステータスが戻り値になります。
シェルの関数は、戻り値に文字列を指定することはできません。関数の結果を文字列で受け取りたい場合は、グローバル変数を使うか、関数内で標準出力へ文字列を出力しコマンド置換する方法があります。