シェルはコマンドを一行ずつ読み込んで解釈し、実行します。一行に複数のコマンドがある場合は、それら全てを解釈してから実行します。一つのコマンドが複数行にまたがっている場合は、そのコマンドを解釈し終えるのに必要なだけ後続の行が読み込まれます。コマンドを正しく解釈できない場合は、文法エラーとなり、コマンドは実行されません。
非対話モードで文法エラーが発生した時は、シェルはコマンドの読み込みを中止するため、それ以降のコマンドは一切読み込まれません。
コマンドは、いくつかのトークンによって構成されます。トークンとは、シェルの文法における一つ一つの単語のことを言います。トークンは原則として空白 (空白文字またはタブ文字) によって区切られます。ただしコマンド置換などに含まれる空白はトークンの区切りとは見なしません。
以下の記号は、シェルの文法において特別な意味を持っています。これらの記号も多くの場合他の通常のトークンの区切りとなります。
;
&
|
<
>
(
)
改行以下の記号はトークンの区切りにはなりませんが、文法上特別な意味を持っています。
$
`
\
"
'
*
?
[
#
~
=
%
以下のトークンは特定の場面において予約語と見なされます。予約語は複合コマンドなどを構成する一部となります。
!
{
}
case
do
done
elif
else
esac
fi
for
if
in
then
until
while
これらのトークンは以下の場面において予約語となります。
case
, for
, in
を除く) の直後のトークンのとき
in
in
または do
トークンが #
で始まる場合、その #
から行末まではコメントと見なされます。コマンドの解釈においてコメントは完全に無視されます。
空白や上記の区切り記号・予約語などを通常の文字と同じように扱うには、適切な引用符でクォートする必要があります。引用符として、バックスラッシュ・一重引用符・二重引用符の三種類が使えます。引用符自体は最終的に元の入力から取り除かれます。
バックスラッシュ (\
) は直後の一文字をクォートします。
例外として、バックスラッシュの直後に改行がある場合、それは改行をクォートしているのではなく、行の連結と見なされます。バックスラッシュと改行が削除され、バックスラッシュがあった行とその次の行が元々一つの行であったかのように扱われます。
二つの一重引用符 ('
) で囲んだ部分では、全ての文字は通常の文字と同じように扱われます。改行を一重引用符で囲むこともできます。ただし、一重引用符を一重引用符で囲むことはできません。
二つの二重引用符 ("
) で囲んだ部分も一重引用符で囲んだ部分と同様にクォートされますが、いくつか例外があります。二重引用符で囲んだ部分では、パラメータ展開・コマンド置換・数式展開が通常通り解釈されます。またバックスラッシュは $
, `
, "
, \
の直前にある場合および行の連結を行う場合にのみ引用符として扱われ、それ以外のバックスラッシュは通常の文字と同様に扱われます。
コマンドを構成する各トークンは、それが予め登録されたエイリアスの名前に一致するかどうか調べられます。一致するものがあれば、そのトークンはそのエイリアスの内容に置き換えられて、その後コマンドの解析が続けられます。これをエイリアス置換といいます。
エイリアスの名前に引用符を含めることはできないので、引用符を含むトークンはエイリアス置換されません。また、予約語やコマンドを区切る記号もエイリアス置換されません。
エイリアスには通常のエイリアスとグローバルエイリアスの二種類があります。通常のエイリアスは、コマンドの最初のトークンにのみ一致します。グローバルエイリアスはコマンド内の全てのトークンが一致の対象です。グローバルエイリアスは POSIX 規格にはない拡張機能です。
通常のエイリアスで置換された部分の最後の文字が空白の場合、特例としてその直後のトークンにも通常のエイリアスの置換が行われます。
エイリアス置換の結果がさらに別のエイリアスに一致して置換される場合もあります。しかし、同じエイリアスに再び一致することはありません。
エイリアスを登録するには alias 組込みコマンドを、登録を削除するには unalias 組込みコマンドを使用します。
最初のトークンが予約語でないコマンドは、単純コマンドです。単純コマンドは単純コマンドと実行のしかたに従って実行されます。
単純コマンドの初めのトークンが 名前=値 の形式になっている場合は、それは変数代入と見なされます。ただしここでの名前は、一文字以上のアルファベット・数字または下線 (_
) で、かつ最初が数字でないものです。変数代入ではない最初のトークンはコマンドの名前と解釈されます。それ以降のトークンは (たとえ変数代入の形式をしていたとしても) コマンドの引数と解釈されます。
名前=(トークン列) の形になっている変数代入は、配列の代入となります。括弧内には任意の個数のトークンを書くことができます。またこれらのトークンは空白・タブだけでなく改行で区切ることもできます。
全てのトークンが変数代入の形式ならば、変数の代入だけが行われ、コマンドは実行されません。
パイプラインは、一つ以上のコマンド (単純コマンド、複合コマンド、または関数定義) を記号 |
で繋いだものです。
パイプラインの実行は、パイプラインに含まれる各コマンドをそれぞれ独立したサブシェルで同時に実行することで行われます。この時、各コマンドの標準出力は次のコマンドの標準入力にパイプで受け渡されます。最初のコマンドの標準入力と最後のコマンドの標準出力は元のままです。最後のコマンドの終了ステータスがパイプラインの終了ステータスになります。
パイプラインの先頭には、記号 !
を付けることができます。この場合、パイプラインの終了ステータスが逆転します。つまり、最後のコマンドの終了ステータスが 0 のときはパイプラインの終了ステータスは 1 になり、それ以外の場合は 0 になります。
注: 最後のコマンドの終了ステータスがパイプラインの終了ステータスになるため、パイプラインの実行が終了するのは少なくとも最後のコマンドの実行が終了した後です。しかしそのとき他のコマンドの実行が終了しているとは限りません。また、最後のコマンドの実行が終了したらすぐにパイプラインの実行が終了するとも限りません。(シェルは、他のコマンドの実行が終わるまで待つ場合があります)
注: POSIX 規格では、パイプライン内の各コマンドはサブシェルではなく現在のシェルで実行してもよいことになっています。
And/or リストは一つ以上のパイプラインを記号 &&
または ||
で繋いだものです。
And/or リストの実行は、and/or リストに含まれる各パイプラインを条件付きで実行することで行われます。最初のパイプラインは常に実行されます。それ以降のパイプラインの実行は、前のパイプラインの終了ステータスによります。
&&
で繋がれている場合、前のパイプラインの終了ステータスが 0 ならば後のパイプラインが実行されます。
||
で繋がれている場合、前のパイプラインの終了ステータスが 0 でなければ後のパイプラインが実行されます。
最後に実行したパイプラインの終了ステータスが and/or リストの終了ステータスになります。
構文上、and/or リストの直後には原則として記号 ;
または &
が必要です (コマンドの区切りと非同期コマンド参照)。
シェルが受け取るコマンドの全体は、and/or リストを ;
または &
で区切ったものです。行末、;;
または )
の直前にある ;
は省略できますが、それ以外の場合は and/or リストの直後には必ず ;
と &
のどちらかが必要です。
And/or リストの直後に ;
がある場合は、その and/or リストは同期的に実行されます。すなわち、その and/or リストの実行が終わった後に次の and/or リストが実行されます。And/or リストの直後に &
がある場合は、その and/or リストは非同期的に実行されます。すなわち、その and/or リストの実行を開始した後、終了を待たずに、すぐさま次の and/or リストの実行に移ります。非同期な and/or リストは常にサブシェルで実行されます。また終了ステータスは常に 0 です。
ジョブ制御を行っていないシェルにおける非同期な and/or リストでは、標準入力が自動的に /dev/null にリダイレクトされるとともに、SIGINT と SIGQUIT を受信したときの動作が無視
に設定されこれらのシグナルを受けてもプログラムが終了しないようにします。(POSIX 準拠モードでは、標準入力を /dev/null にリダイレクトするのはジョブ制御を行っていないシェルではなく対話モードのシェルです。また POSIX 準拠モードではジョブ制御を行っていても SIGINT と SIGQUIT が無視
に設定されます)
ジョブ制御を行っているかどうかにかかわらず、非同期コマンドを実行するとシェルはそのコマンドのプロセス ID を記憶します。特殊パラメータ !
を参照すると非同期コマンドのプロセス ID を知ることができます。非同期コマンドの状態や終了ステータスは jobs や wait 組込みコマンドで知ることができます。
複合コマンドは、より複雑なプログラムの制御を行う手段を提供します。
グルーピングを使うと、複数のコマンドを一つのコマンドとして扱うことができます。
{ コマンド…; }
(コマンド…)
{
と }
は予約語なので、他のコマンドのトークンとくっつけて書いてはいけません。一方 (
と )
は特殊な区切り記号と見なされるので、他のトークンとくっつけて書くことができます。
通常のグルーピング構文 ({
と }
で囲む) では、コマンドは (他のコマンドと同様に) 現在のシェルで実行されます。サブシェルのグルーピング構文 ((
と )
で囲む) では、括弧内のコマンドは新たなサブシェルで実行されます。
グルーピングの終了ステータスは、グルーピングの中で実行された最後のコマンドの終了ステータスです。POSIX 準拠モードでは括弧内に少なくとも一つのコマンドが必要ですが、非 POSIX 準拠モードではコマンドは一つもなくても構いません。この場合、グルーピングの終了ステータスはグルーピングの直前に実行されたコマンドの終了ステータスになります。
If 文は条件分岐を行います。分岐の複雑さに応じていくつか構文のバリエーションがあります。
if 条件コマンド…; then 内容コマンド…; fi
if 条件コマンド…; then 内容コマンド…; else 内容コマンド…; fi
if 条件コマンド…; then 内容コマンド…; elif 条件コマンド…; then 内容コマンド…; fi
if 条件コマンド…; then 内容コマンド…; elif 条件コマンド…; then 内容コマンド…; else 内容コマンド…; fi
If 文の実行では、どの構文の場合でも、if
の直後にある条件コマンドがまず実行されます。条件コマンドの終了ステータスが 0 ならば、条件が真であると見なされて then
の直後にある内容コマンドが実行され、if 文の実行はそれで終了します。終了ステータスが 0 でなければ、条件が偽であると見なされます。ここで else
も elif
もなければ、if 文の実行はこれで終わりです。else
がある場合は、else
の直後の内容コマンドが実行されます。elif
がある場合は、elif
の直後の条件コマンドが実行され、その終了ステータスが 0 であるかどうか判定されます。その後は先程と同様に条件分岐を行います。
elif …; then …;
は一つの if 文内に複数あっても構いません。
If 文全体の終了ステータスは、実行された内容コマンドの終了ステータスです。内容コマンドが実行されなかった場合 (どの条件も偽で、else
がない場合) は 0 です。
While/until ループは単純なループ構文です。
while コマンド1…; do コマンド2…; done
until コマンド1…; do コマンド2…; done
非 POSIX 準拠モードでは コマンド1…;
および コマンド2…;
は省略可能です。
While ループの実行ではまずコマンド1が実行されます。そのコマンドの終了ステータスが 0 ならば、コマンド2が実行されたのち、再びコマンド1の実行に戻ります。この繰り返しはコマンド1の終了ステータスが 0 でなくなるまで続きます。コマンド1の終了ステータスが最初から 0 でないときは、コマンド2は一度も実行されません。
Until ループは、ループを続行する条件が逆になっている以外は while ループと同じです。すなわち、コマンド1の終了ステータスが 0 でなければコマンド2が実行されます。
While/until ループ全体の終了ステータスは、最後に実行したコマンド2の終了ステータスです。(コマンド2が存在しないか、一度も実行されなかったときは 0)
For ループは指定されたそれぞれの単語について同じコマンドを実行します。
for 変数名 in 単語…; do コマンド…; done
for 変数名 do コマンド…; done
in
の直後の単語は一つもなくても構いませんが、do
の直前の ;
(または改行) は必要です。これらの単語トークンは予約語としては認識されませんが、&
などの記号を含めるには適切なクォートが必要です。in …;
を省略する場合は、本来は変数名と do
の間に ;
を入れてはいけませんが、非 POSIX 準拠モードでは ;
があっても許容されます。また非 POSIX 準拠モードでは コマンド…;
がなくても構いません。
For ループの実行ではまず単語が単純コマンド実行時の単語の展開と同様に展開されます (in …;
がない構文を使用している場合は、in "$@";
が省略されているものと見なされます)。続いて、展開で生成されたそれぞれの単語について順番に一度ずつ以下の処理を行います。
単語はローカル変数として代入されます (POSIX 準拠モードのときを除く)。展開の結果単語が一つも生成されなかった場合は、コマンドは一切実行されません。
For ループ全体の終了ステータスは、最後に実行したコマンドの終了ステータスです。コマンドがあるのに一度も実行されなかったときは 0 です。コマンドがない場合、for ループの終了ステータスは for ループの一つ前に実行されたコマンドの終了ステータスになります。
Case 文は単語に対してパターンマッチングを行い、その結果に対応するコマンドを実行します。
case 単語 in caseitem… esac
( パターン ) コマンド… ;;
case
と in
の間の単語はちょうど一トークンでなければなりません。この単語トークンは予約語としては認識されませんが、&
などの記号を含めるには適切なクォートが必要です。in
と esac
の間には任意の個数の caseitem を置きます (0 個でもよい)。Caseitem の最初の (
と esac
の直前の ;;
は省略できます。またコマンドが ;
で終わる場合はその ;
も省略できます。Caseitem の )
と ;;
との間にコマンドが一つもなくても構いません。
Caseitem のパターンにはトークンを指定します。各トークンを |
で区切ることで複数のトークンをパターンとして指定することもできます。
Case 文の実行では、まず単語が四種展開されます。その後、各 caseitem に対して順に以下の動作を行います。
Case 文全体の終了ステータスは、実行したコマンドの終了ステータスです。コマンドが実行されなかった場合 (どのパターンもマッチしなかったか、caseitem が一つもないか、マッチしたパターンの後にコマンドがない場合) は、終了ステータスは 0 です。
関数定義コマンドは、関数を定義します。
関数名 ( ) 複合コマンド
function 関数名 複合コマンド
function 関数名 ( ) 複合コマンド
function
キーワードを用いない一つ目の形式では、関数名には引用符などの特殊な記号を含めることはできません。function
キーワードを用いる二つ目または三つ目の形式では、関数名は実行時に四種展開されます。
関数定義コマンドを実行すると、指定した関数名の関数が複合コマンドを内容として定義されます。
関数定義コマンドに対して直接リダイレクトを行うことはできません。関数定義コマンドの最後にあるリダイレクトは、関数の内容である複合コマンドに対するリダイレクトと見なされます。
関数定義コマンドの終了ステータスは、関数が正しく定義された場合は 0、そうでなければ非 0 です。