シェルはコマンドを一行ずつ読み込んで解釈し、実行します。一行に複数のコマンドがある場合は、それら全てを解釈してから実行します。一つのコマンドが複数行にまたがっている場合は、そのコマンドを解釈し終えるのに必要なだけ後続の行が読み込まれます。コマンドを正しく解釈できない場合は、文法エラーとなり、コマンドは実行されません。

非​対話モードで文法エラーが発生した時は、シェルはコマンドの読み込みを中止するため、それ以降のコマンドは一切読み込まれません。

トークンの解析と予約語

コマンドは、いくつかのトークンによって構成されます。トークンとは、シェルの文法における一つ一つの単語のことを言います。トークンは原則として空白 (空白文字またはタブ文字) によって区切られます。ただしコマンド置換などに含まれる空白はトークンの区切りとは見なしません。

以下の記号は、シェルの文法において特別な意味を持っています。これらの記号も多くの場合他の通常のトークンの区切りとなります。

; & | < > ( ) [newline]

以下の記号はトークンの区切りにはなりませんが、文法上特別な意味を持っています。

$ ` \ " ' * ? [ # ~ = %

以下のトークンは特定の場面において予約語と見なされます。予約語は複合コマンドなどを構成する一部となります。

! { } [[ case do done elif else esac fi
for function if in then until while

これらのトークンは以下の場面において予約語となります。

  • それがコマンドの最初のトークンのとき

  • それが他の予約語 (case, for, in を除く) の直後のトークンのとき

  • それがコマンドの最初のトークンではないが、複合コマンドの中で予約語として扱われるべきトークンであるとき

トークンが # で始まる場合、その # から行末まではコメントと見なされます。コマンドの解釈においてコメントは完全に無視されます。

クォート

空白や上記の区切り記号・予約語などを通常の文字と同じように扱うには、適切な引用符でクォートする必要があります。引用符は、それ自体をクォートしない限り通常の文字としては扱われません。シェルでは以下の三種類の引用符が使えます。

  • バックスラッシュ (\) は直後の一文字をクォートします。
    例外として、バックスラッシュの直後に改行がある場合、それは改行をクォートしているのではなく、行の連結と見なされます。バックスラッシュと改行が削除され、バックスラッシュがあった行とその次の行が元々一つの行であったかのように扱われます。

  • 二つの一重引用符 (') で囲んだ部分では、全ての文字は通常の文字と同じように扱われます。改行を一重引用符で囲むこともできます。ただし、一重引用符を一重引用符で囲むことはできません。

  • 二つの二重引用符 (") で囲んだ部分も一重引用符で囲んだ部分と同様にクォートされますが、いくつか例外があります。二重引用符で囲んだ部分では、パラメータ展開・コマンド置換・数式展開が通常通り解釈されます。またバックスラッシュは $, `, ", \ の直前にある場合および行の連結を行う場合にのみ引用符として扱われ、それ以外のバックスラッシュは通常の文字と同様に扱われます。

エイリアス

コマンドを構成する各トークンは、それが予め登録されたエイリアスの名前に一致するかどうか調べられます。一致するものがあれば、そのトークンはそのエイリアスの内容に置き換えられて、その後コマンドの解析が続けられます。これをエイリアス置換といいます。

エイリアスの名前に引用符を含めることはできないので、引用符を含むトークンはエイリアス置換されません。また、予約語やコマンドを区切る記号もエイリアス置換されません。

エイリアスには通常のエイリアスとグローバルエイリアスの二種類があります。通常のエイリアスは、コマンドの最初のトークンにのみ一致します。グローバルエイリアスはコマンド内の全てのトークンが一致の対象です。グローバルエイリアスは POSIX 規格にはない拡張機能です。

通常のエイリアスで置換された部分の最後の文字が空白の場合、特例としてその直後のトークンにも通常のエイリアスの置換が行われます。

エイリアス置換の結果がさらに別のエイリアスに一致して置換される場合もあります。しかし、同じエイリアスに再び一致することはありません。

エイリアスを登録するには alias 組込みコマンドを、登録を削除するには unalias 組込みコマンドを使用します。

単純コマンド

最初のトークンが予約語でないコマンドは、単純コマンドです。単純コマンドは​単純コマンドの実行のしかたに従って実行されます。

単純コマンドの初めのトークンが 名前= の形式になっている場合は、それは​変数代入と見なされます。ただしここでの名前は、一文字以上のアルファベット・数字または下線 (_) で、かつ最初が数字でないものです。変数代入ではない最初のトークンはコマンドの名前と解釈されます。それ以降のトークンは (たとえ変数代入の形式をしていたとしても) コマンドの引数と解釈されます。

名前=(トークン列) の形になっている変数代入は、​配列の代入となります。括弧内には任意の個数のトークンを書くことができます。またこれらのトークンは空白・タブだけでなく改行で区切ることもできます。

パイプライン

パイプラインは、一つ以上のコマンド (単純コマンド複合コマンド、または関数定義) を記号 | で繋いだものです。

二つ以上のコマンドからなるパイプラインの実行は、パイプラインに含まれる各コマンドをそれぞれ独立した​サブシェルで同時に実行することで行われます。この時、各コマンドの標準出力は次のコマンドの標準入力にパイプで受け渡されます。最初のコマンドの標準入力と最後のコマンドの標準出力は元のままです。

Pipe-fail オプションが無効な時は、最後のコマンドの終了ステータスがパイプラインの終了ステータスになります。有効な時は、終了ステータスが 0 でなかった最後のコマンドの終了ステータスがパイプラインの終了ステータスになります。全てのコマンドの終了ステータスが 0 だった時は、パイプラインの終了ステータスも 0 になります。

パイプラインの先頭には、記号 ! を付けることができます。この場合、パイプラインの終了ステータスが逆転します。つまり、最後のコマンドの終了ステータスが 0 のときはパイプラインの終了ステータスは 1 になり、それ以外の場合は 0 になります。

Korn シェルでは構文 !(…) は POSIX で定義されていない独自のパス名展開パターンと見做されます。​POSIX 準拠モードでは !( の二つのトークンは一つ以上の空白で区切る必要があります。

最後のコマンドの終了ステータスがパイプラインの終了ステータスになるため、パイプラインの実行が終了するのは少なくとも最後のコマンドの実行が終了した後です。しかしそのとき他のコマンドの実行が終了しているとは限りません。また、最後のコマンドの実行が終了したらすぐにパイプラインの実行が終了するとも限りません。(シェルは、他のコマンドの実行が終わるまで待つ場合があります)
POSIX 規格では、パイプライン内の各コマンドはサブシェルではなく現在のシェルで実行してもよいことになっています。

And/or リスト

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 リストの実行に移ります。非同期な and/or リストは常に​サブシェルで実行されます。また終了ステータスは常に 0 です。

ジョブ制御を行っていないシェルにおける非同期な and/or リストでは、標準入力が自動的に /dev/null にリダイレクトされるとともに、SIGINT と SIGQUIT を受信したときの動作が 『無視』 に設定されこれらのシグナルを受けてもプログラムが終了しないようにします。

ジョブ制御を行っているかどうかにかかわらず、非同期コマンドを実行するとシェルはそのコマンドのプロセス ID を記憶します。​特殊パラメータ ! を参照すると非同期コマンドのプロセス ID を知ることができます。非同期コマンドの状態や終了ステータスは jobswait 組込みコマンドで知ることができます。

複合コマンド

複合コマンドは、より複雑なプログラムの制御を行う手段を提供します。

グルーピング

グルーピングを使うと、複数のコマンドを一つのコマンドとして扱うことができます。

通常のグルーピングの構文

{ コマンド…; }

サブシェルのグルーピングの構文

(コマンド…)

{} は予約語なので、他のコマンドのトークンとくっつけて書いてはいけません。一方 () は特殊な区切り記号と見なされるので、他のトークンとくっつけて書くことができます。

通常のグルーピング構文 ({} で囲む) では、コマンドは (他のコマンドと同様に) 現在のシェルで実行されます。サブシェルのグルーピング構文 (() で囲む) では、括弧内のコマンドは新たな​サブシェルで実行されます。

POSIX 準拠モードでは括弧内に少なくとも一つのコマンドが必要ですが、非 POSIX 準拠モードではコマンドは一つもなくても構いません。

グルーピングの終了ステータスは、グルーピングの中で実行された最後のコマンドの終了ステータスです。グルーピング内にコマンドが一つもない場合、グルーピングの終了ステータスはグルーピングの直前に実行されたコマンドの終了ステータスになります。

If 文

If 文は条件分岐を行います。分岐の複雑さに応じていくつか構文のバリエーションがあります。

If 文の基本構文

if 条件コマンド…; then 内容コマンド…; fi

Else がある場合

if 条件コマンド…; then 内容コマンド…; else 内容コマンド…; fi

Elif がある場合

if 条件コマンド…; then 内容コマンド…; elif 条件コマンド…; then 内容コマンド…; fi

Elif と else がある場合

if 条件コマンド…; then 内容コマンド…; elif 条件コマンド…; then 内容コマンド…; else 内容コマンド…; fi

If 文の実行では、どの構文の場合でも、if の直後にある条件コマンドがまず実行されます。条件コマンドの終了ステータスが 0 ならば、条件が真であると見なされて then の直後にある内容コマンドが実行され、if 文の実行はそれで終了します。終了ステータスが 0 でなければ、条件が偽であると見なされます。ここで elseelif もなければ、if 文の実行はこれで終わりです。else がある場合は、else の直後の内容コマンドが実行されます。elif がある場合は、elif の直後の条件コマンドが実行され、その終了ステータスが 0 であるかどうか判定されます。その後は先程と同様に条件分岐を行います。

elif …; then …; は一つの if 文内に複数あっても構いません。

If 文全体の終了ステータスは、実行された内容コマンドの終了ステータスです。内容コマンドが実行されなかった場合 (どの条件も偽で、else がない場合) は 0 です。

While および until ループ

While ループと until ループは単純なループ構文です。

While ループの構文

while 条件コマンド…; do 内容コマンド…; done

Until ループの構文

until 条件コマンド…; do 内容コマンド…; done

POSIX 準拠モードでは 条件コマンド…; および 内容コマンド…; は省略可能です。

While ループの実行ではまず条件コマンドが実行されます。そのコマンドの終了ステータスが 0 ならば、内容コマンドが実行されたのち、再び条件コマンドの実行に戻ります。この繰り返しは条件コマンドの終了ステータスが 0 でなくなるまで続きます。

条件コマンドの終了ステータスが最初から 0 でないときは、内容コマンドは一度も実行されません。

Until ループは、ループを続行する条件が逆になっている以外は while ループと同じです。すなわち、条件コマンドの終了ステータスが 0 でなければ内容コマンドが実行されます。

While/until ループ全体の終了ステータスは、最後に実行した内容コマンドの終了ステータスです。(内容コマンドが存在しないか、一度も実行されなかったときは 0)

For ループ

For ループは指定されたそれぞれの単語について同じコマンドを実行します。

For ループの構文

for 変数名 in 単語…; do コマンド…; done
for 変数名 do コマンド…; done

in の直後の単語は一つもなくても構いませんが、do の直前の ; (または改行) は必要です。これらの単語トークンは予約語としては認識されませんが、& などの記号を含めるには適切なクォートが必要です。非 POSIX 準拠モードでは コマンド…; がなくても構いません。

POSIX 準拠モードでは、変数名はポータブルな (すなわち ASCII 文字のみからなる) 名前でなければなりません。

For ループの実行ではまず単語単純コマンド実行時の単語の展開と同様に展開されます (in …; がない構文を使用している場合は、in "$@"; が省略されているものと見なされます)。続いて、展開で生成されたそれぞれの単語について順番に一度ずつ以下の処理を行います。

  1. 単語を変数名で指定した変数に代入する

  2. コマンドを実行する

単語は​ローカル変数として代入されます (POSIX 準拠モードが有効なときまたは for-local オプションが無効なときを除く)。

展開の結果単語が一つも生成されなかった場合は、変数は作られずコマンドも一切実行されません。

For ループ全体の終了ステータスは、最後に実行したコマンドの終了ステータスです。コマンドがあるのに一度も実行されなかったときは 0 です。コマンドがない場合、for ループの終了ステータスは for ループの一つ前に実行されたコマンドの終了ステータスになります。

変数が読み取り専用の場合、for ループの実行は 0 でない終了ステータスで中断されます。

Case 文

Case 文は単語に対してパターンマッチングを行い、その結果に対応するコマンドを実行します。

Case 文の構文

case 単語 in caseitem… esac

Caseitem の構文

(パターン) コマンド…;;

casein の間の単語はちょうど一トークンでなければなりません。この単語トークンは予約語としては認識されませんが、& などの記号を含めるには適切なクォートが必要です。inesac の間には任意の個数の caseitem を置きます (0 個でもよい)。Caseitem の最初の (esac の直前の ;; は省略できます。またコマンド; で終わる場合はその ; も省略できます。Caseitem の );; との間にコマンドが一つもなくても構いません。

Caseitem のパターンにはトークンを指定します。各トークンを | で区切ることで複数のトークンをパターンとして指定することもできます。

Case 文の実行では、まず単語が​四種展開されます。その後、各 caseitem に対して順に以下の動作を行います。

  1. パターントークンを単語と同様に展開し、展開したパターンが展開した単語にマッチするかどうか調べます (パターンマッチング記法参照)。パターンとして指定されたトークンが複数ある場合はそれら各トークンに対してマッチするかどうか調べます (どれかのパターントークンがマッチしたらそれ以降のパターントークンは展開されません。Yash はトークンが書かれている順番にマッチするかどうかを調べますが、他のシェルもこの順序で調べるとは限りません)。

  2. マッチした場合は、直後のコマンドを実行し、それでこの case 文の実行は終了です。マッチしなかった場合は、次の caseitem の処理に移ります。

Case 文全体の終了ステータスは、実行したコマンドの終了ステータスです。コマンドが実行されなかった場合 (どのパターンもマッチしなかったか、caseitem が一つもないか、マッチしたパターンの後にコマンドがない場合) は、終了ステータスは 0 です。

POSIX 準拠モードでは、(| で区切られた最初の) パターントークンを esac にすることはできません。

二重ブラケットコマンド

二重ブラケットコマンドtest コマンドに近い動作をする構文です。ブラケットに囲まれた式を展開して評価します。

二重ブラケットコマンド構文

[[ ]]

は単一の原子式とすることも原子式や演算子の組み合わせとすることもできます。式の構文解析は、コマンドの実行時ではなく構文解析時に行われます。演算子 (原子式の演算子もそうでないものも) は​クォートしてはなりません。クォートすると通常の単語と見なされます。

コマンドが実行されるとき、被演算子となる単語は​四種展開されます。ブレース展開・単語分割・パス名展開は行われません。

二重ブラケットコマンドでは test コマンドと同様に以下の原子式が使用できます:

単項原子式

-b, -c, -d, -e, -f, -G, -g, -h, -k, -L, -N, -n, -O, -o, -p, -r, -S, -s, -t, -u, -w, -x, -z

二項原子式

-ef, -eq, -ge, -gt, -le, -lt, -ne, -nt, -ot, -veq, -vge, -vgt, -vle, -vlt, -vne, ===, !==, =~, <, >

さらに、文字列を比較するための三つの二項原子式が使用できますが、test コマンドとは動作が異なります: = および == 原子式は右辺を​パターンとして扱い、左辺がそれにマッチするかどうかを判定します。 != 原子式は同様の判定を行い、逆の結果を返します。

原子式の被演算子となる単語が ]] であるか他の演算子と紛らわしい場合は、クォートする必要があります。

将来新しい種類の原子式が導入される可能性があります。ハイフンで始まる単語は全てクォートすることをお勧めします。
<= および >= 二項原子式は二重ブラケットコマンド内においては正しく構文解析できないため使用できません。

以下の演算子を使用して原子式を組み合わせることができます (ここでは演算子の結合順位が高い順に示します):

( )

式を括弧で囲むと演算子の優先順位を変更できます。

!

感嘆符は式の結果を反転します。

&&

二重アンパサンドは連言 (論理積) を表します。両辺が共に真である時、全体も真となります。左辺が先に展開・判定されます。左辺が真である場合のみ右辺が展開・判定されます。

||

二重縦棒は選言 (論理和) を表します。両辺が共に偽である時、全体も偽となります。左辺が先に展開・判定されます。左辺が偽である場合のみ右辺が展開・判定されます。

二重ブラケットコマンドでは、test コマンドの様に -a および -o を連言・選言演算子として使用することはできません。

二重ブラケットコマンドの終了ステータスは、が真ならば 0、偽ならば 1、展開エラーやその他の理由で判定が行えない場合は 2 です。

二重ブラケットコマンドは bash, ksh, mksh, zsh にもありますが、POSIX にはない拡張機能です。シェルによって多少動作が異なります。移植性を高めるには二重ブラケットコマンドよりも test コマンドを使用することをお勧めします。

関数定義

関数定義コマンドは、​関数を定義します。

関数定義コマンドの構文

関数名 ( ) 複合コマンド
function 関数名 複合コマンド
function 関数名 ( ) 複合コマンド

予約語 function を用いない一つ目の形式では、関数名には引用符などの特殊な記号を含めることはできません。予約語 function を用いる二つ目または三つ目の形式では、関数名は実行時に​四種展開されます。(POSIX 準拠モードでは、予約語 function を用いる形式の関数定義は使えません。また関数名はポータブルな (すなわち ASCII 文字のみからなる) 名前でなければなりません。)

関数定義コマンドを実行すると、指定した関数名の関数が複合コマンドを内容として定義されます。

関数定義コマンドに対して直接​リダイレクトを行うことはできません。関数定義コマンドの最後にあるリダイレクトは、関数の内容である複合コマンドに対するリダイレクトと見なされます。例えば func() { cat; } >/dev/null と書いた場合、リダイレクトされるのは func() { cat; } ではなく { cat; } です。

関数定義コマンドの終了ステータスは、関数が正しく定義された場合は 0、そうでなければ非 0 です。