🛣️

プログラミング言語としての シェルスクリプト チュートリアル (3) 〜 条件分岐編

に公開

シェルスクリプト チュートリアル

シェルスクリプトの分岐の基本

シェルスクリプトで条件分岐を書くときは、次のように書きます。

    if [ "${variable}" == "abc" ]; then
        ...
    fi

JavaScript と比べてみましょう。 ほとんど変わりませんね。

    if (variable == "abc") {
       ...
    }

シェルスクリプトは知らないが、主要なプログラミング言語を知っている人にとって読めるシェルスクリプトの書き方は、こうなります。

未だに以下のようなシェルスクリプトをよく見かけますが、読めるものではありません。(このスクリプトの意味は記事の中に書いてあります)

    [ -n "${variable}" ] || ...

本シリーズでは、シェルスクリプトを他のプログラミング言語と同じように読める形式で書くことを推奨しており、どのように書けばそうなるかを解説しています。

ただし、POSIX に無い仕様も推奨に含まれているので、最後に互換性の表を載せています。 bash 準拠ではなく POSIX に準拠すべきだとよく言われますが、それが必要になるのは実質組み込み用途に限ります(後でも説明しています)。 本記事では POSIX に準拠していても非推奨としているものがありますが、非推奨のサンプルを見れば POSIX に準拠する場合にどのように書けばいいかが分かるような記事になっています。

では、いろいろな条件分岐の書き方を見ていきましょう。

ダウンロード

本書で紹介しているスクリプトのファイルは、GitHub からダウンロードすることができます。Linux のシェルを開くか、Windows の Git bash を開いて、以下のコマンドを実行してください。

$ cd  ${HOME}
$ git clone https://github.com/Takakiriy/shell-script-tutorial

if と else

if のみ

    if [ "${variable}" == "abc" ]; then
        ...
    fi

変数参照 ${variable}、if の右のカッコと空白とセミコロン [ ]; then、ブロックの開始 then、ブロックの終了 fi が独特に感じるでしょうが、許容範囲だと思います。

💡 [ ]内側の空白は必須です。 なぜなら、[ は test コマンドの別名だからです。 ] の右の ; も必須です。

if と elif (else if) と else

    if [ "${variable}" == "abc" ]; then
        ...
    elif [ "${variable}" == "def" ]; then
        ...
    else
        ...
    fi

非推奨

1行 1文の原則に従った書き方ではこうなりますが、行という実装上の都合を表記に影響させることは、良くありません。

    if [ "${variable}" == "abc" ]
    then
        ...
    fi

文字列の比較演算子

推奨

同じ文字列なら true となる演算子 ==、異なる文字列なら true となる演算子 != を書きます。

    if [ "${variable}" == "abc" ]; then
    if [ "${variable}" != "abc" ]; then

文字コードによる比較はこうなります。

    if [[ "${variable}" < "abc" ]]; then  #// 2重カッコであることに注意。<= は書けません
    if [[ "${variable}" > "abc" ]]; then  #// 2重カッコであることに注意。>= は書けません

正規表現にマッチするかどうかは、grep コマンドで判定できます。 なお、「終了コード」によって判定するため、[ ] を書いてはいけません。後で詳しく説明します。

    if echo "${variable}"  |  grep -qE "abc"; then  #// [ ] を書いてはいけません

=~ 演算子

grep の代わりに perl が発祥の =~ 演算子も使えますが、=~ の左が文字列データであるのに対して右がパターンであるというメタレベルに違いがあるのに = を想起させる =~ 演算子では分かりにくいことと、多くの言語で =~ をサポートしていないので基本的には非推奨ですが、grep に比べると速いため、速い処理が必要なときに限りコメントで補足することは推奨とします。 名前から想像できるようにメソッドや関数を使った明示的なアプローチが好まれているから、=~ 演算子は多くの言語でサポートしていないのでしょう。 =~ 演算子のサポート状況については最後にまとめています。

    #// 基本的には非推奨だが、速い処理が必要なときに限りコメントで補足する(ここからコピペする)ことは推奨
    #// 2重カッコであることに注意
    if [[ "${variable}" =~ "abc" ]]; then  #// =~ operator is faster than grep command.
    if ! [[ ${line} =~ ${regularExpression} ]]; then  #// =~ operator is faster than grep command.

    #// マッチした部分の参照はシェルごとに異なります
    BASH_REMATCH[@]   #// bash
    MATCH / match[@]  #// zsh. 要 setopt BASH_REMATCH

非推奨

次のように書いても動きますが、他の多くの言語は = を代入としているので、非推奨とします。

    if [ "${variable}" = "abc" ]; then    #// 1つのイコールは代入と見間違えやすい

次のように書いても動きません。 数値として比較するときは -eq などを書くと覚えている方もいると思いますが、後で説明するように非推奨です。

    if [ "${variable}" -eq "abc" ]; then  #// == と同じ
    if [ "${variable}" -ne "abc" ]; then  #// != と同じ
    if [ "${variable}" -le "abc" ]; then  #// < と同じ
    if [ "${variable}" -gt "abc" ]; then  #// > と同じ

-z 演算子や -n 演算子を未だに使うのは、POSIX 準拠だとしても、ハッキリ言ってアホです。 読めるなら他の言語でも採用されています。

    if [ -z "${variable}" ]; then  #// "${variable}" == "" に書き換える
    if [ -n "${variable}" ]; then  #// "${variable}" != "" に書き換える

数値の比較演算子

推奨

2重カッコの算術式 (( )) を使って書きます。 同じ整数値なら true となる演算子 ==、異なる文字列なら true となる演算子 !=、数値の大小を比較する演算子 <, >, <=, >= を書きます。

    if (( ${variable} == 5 )); then  #// 2重カッコであることに注意
    if (( ${variable} != 5 )); then  #// 2重カッコであることに注意
    if (( ${variable} < 5 )); then   #// 2重カッコであることに注意
    if (( ${variable} > 5 )); then   #// 2重カッコであることに注意
    if (( ${variable} <= 5 )); then  #// 2重カッコであることに注意
    if (( ${variable} >= 5 )); then  #// 2重カッコであることに注意
    if (( "${variable}" == 5 )); then  #// 2重カッコであることに注意
    if (( "${variable}" != 5 )); then  #// 2重カッコであることに注意
    if (( "${variable}" < 5 )); then   #// 2重カッコであることに注意
    if (( "${variable}" > 5 )); then   #// 2重カッコであることに注意
    if (( "${variable}" <= 5 )); then  #// 2重カッコであることに注意
    if (( "${variable}" >= 5 )); then  #// 2重カッコであることに注意

変数に英文字入っているときは、その英文字が名前の変数の値になります。 ${ } なし、${ } あり、"${ }" ありのどれも同じ動きです。

💡 "${ }" で囲まなくても動く仕様にしてしまったのが原因でしょう。

  • 変数が未定義なら、${ } または "${ }" で囲んである場合、エラーになりますが、偽として処理は継続します(set -u がオフの場合)
  • 変数が未定義なら、"${ }" などで「囲まない」変数名のみ場合、0 になります(set -u がオフの場合)

数値の計算をしてから比較することもできます。+, -, *, /, (, ) などが使えます。マイナスは使えますが浮動小数は使えません。 64ビット符号付き整数です(記事の最後に精度の比較表があります)。 zsh なら数値に小数点を付ければ浮動小数が使えますが、bash では使えないため、bc または Python を使った方法を推奨します。

    if (( "${variable}" + 2 == 5 )); then  #// 2重カッコであることに注意

非推奨

次のようにハイフンから始まるオプションの形式で書いても動きますが、可読性が低く、2重カッコの算術式ではエラーになる(bash 等で採用されなかった)ので非推奨とします。

    if [ "${variable}" -eq 5 ]; then  #// == と同じ
    if [ "${variable}" = 5 ]; then    #// 1つのイコールは代入と見間違えやすい
    if [ "${variable}" -ne 5 ]; then  #// != と同じ
    if [ "${variable}" -lt 5 ]; then  #// < と同じ
    if [ "${variable}" -le 5 ]; then  #// <= と同じ
    if [ "${variable}" -gt 5 ]; then  #// > と同じ
    if [ "${variable}" -ge 5 ]; then  #// >= と同じ
    if (( "${variable}" -eq 5 )); then  #// エラー。(( )) の中では == で書く必要がある
    if (( "${variable}" -ne 5 )); then  #// エラー。(( )) の中では != で書く必要がある
    if (( "${variable}" -le 5 )); then  #// エラー。(( )) の中では < で書く必要がある
    if (( "${variable}" -lt 5 )); then  #// エラー。(( )) の中では <= で書く必要がある
    if (( "${variable}" -gt 5 )); then  #// エラー。(( )) の中では > で書く必要がある
    if (( "${variable}" -ge 5 )); then  #// エラー。(( )) の中では >= で書く必要がある
    if (( variable == 5 )); then  #// ${ } で囲まないのは非推奨

(( )) の仕様の説明では ${ } で囲まないことが基本と書かれていますが、本記事では変数の値を参照をしている他の部分と同じ書き方を推奨します。

💡 ハイフンから始まるオプションの形式 -eq などで書かれているシェルスクリプトがそこそこ多い理由に、== などが POSIX に定義されていないからという理由を挙げる人がいますが、ksh,bash,zsh 等の2重カッコの算術式では -eq などは採用されていません。互換性がある書き方はではあるものの、レガシーな言語仕様であるのは間違いないです。

標準出力との比較

$( ) で囲めば、その中に書いたコマンドの標準出力が比較対象の値になります。 これを使って、変数の値の比較だけでなく、関数やコマンドを呼び出して、その出力値(標準出力など)を比較できます。 変数に一旦入れる必要はありません。

    if [ "$( echo "abc" )" == "abc" ]; then
    if (( "$( echo "3" )" < 5 )); then

終了コード(返り値)による条件分岐、true/false 条件分岐

条件式や標準出力で判定するのではなく、終了コードで判定するときは [ ] を書かないようにします。

    if echo "abc"  |  grep -qE "abc"; then  #// [ ] を書かない

このスクリプトは、grep コマンドの終了コードで条件分岐しています。 grep コマンドは、マッチすると終了コードが 0 になり、マッチしなければ終了コードが 1 になるため、終了コードによって条件分岐しています。 終了コードは 0 が成功なので if が真になるのも終了コードが 0 のときです。

💡 -q-qEq)はマッチしたときに表示させないようにします。> /dev/null と同じです。
-E-qEE)は拡張正規表現を使うことを指示しています。
パイプ | を使っている場合、最も右のコマンドの終了コードが返ります。

逆に、終了コードが 0 以外のときに if を真にするなら、次のように否定を書きます。 ただし、( ) を書くとサブシェルになるため、( ) の中で設定や変更した環境変数の値や カレント フォルダー は ( ) の外に返ると元に戻るので注意してください。 それが許されない場合は、else ブロックに書くか標準出力で判定するようにしてください。何もしないブロックを書く場合、注意が必要です。後で説明します。

    if ! ( echo "abc"  |  grep -qE "abc" ); then

if は機能的には次に続くコマンドの終了コードで分岐する機能です。 詳細に見ていきましょう。

true は終了コードを 0 にする何もしないコマンドです。
false は終了コードを 1 にする何もしないコマンドです。

$ true
$ echo $?
0

$ false
$ echo $?
1

常に真や常に偽は次のように書きます。

if true; then  #// [ ] を書かない
    ...
fi
if false; then  #// [ ] を書かない
    ...
fi

以上から、if は終了コードで判定していることが言えます。

[test コマンドの別名なので、if [ ] も機能的に言えば test コマンドで終了コードで判定していることになります。[ というさりげない別名の表現をすることで、演算子で判定していることが主として読めるようになります。

    if test "${variable}" == "abc"; then

は、下記のコードを同じ動作をします。

    if [ "${variable}" == "abc" ]; then

何もしないブロックには : (コロン)

if で何も実行しないで else を書く場合など、ブロックの中に何も書かないと文法エラーになります。:(とコメント)を書いてください。Python の pass と同様です。

    if echo "abc"  |  grep -qE "abc"; then
        :  #// DoNothing
    else
        echo  "Exit code is not 0."
    fi

制御演算子 && || によるワンライナーの条件分岐とエラー分岐

&& は、前のコマンドが成功したら(終了コード 0 なら)、次のコマンドを実行します。
|| は、前のコマンドが失敗したら(終了コード 0 以外なら)、次のコマンドを実行します。

true   &&  echo "true &&"   #// 表示されます
false  &&  echo "false &&"  #// 表示されません
true   ||  echo "true ||"   #// 表示されません
false  ||  echo "false ||"  #// 表示されます

true コマンドや false コマンドについては前述しています。

test コマンドを使うとスマートに事前条件チェックが書けます。

test  "${variable}" != ""  ||  echo "ERROR" >&2

コマンドのエラー分岐も簡単に書けます。

$ cd  "."  ||  echo "ERROR" >&2
$ cd  "unknown_path"  ||  echo "ERROR" >&2
cd: no such file or directory: unknown_path
ERROR

実際は、echo を直通書かず、次のように Error 関数を定義して呼び出し、異常終了させます。 Error を使わずに || exit と書くと、そのコマンドを CLI で入力したときにシェルが突然閉じてしまうことがあって危険です。

function  Main() {
    cd  "."  ||  Error
    cd  "unknown_path"  ||  Error
    echo  "Pass."
}

function  Error() {
    local  errorMessage="$1"
    if [ "${errorMessage}" == "" ]; then
        errorMessage="ERROR"
    fi

    echo  "${errorMessage}"  >&2
    exit 2
}
Main

💡 ここで説明している書き方は set -e を実行しない場合の書き方です

|| を使った書き方は、Go言語に比べて書きやすいです。 Go言語ではエラー分岐を書くと強制的に 1行が 4行に増えてしまうため、その点ではシェルスクリプトのほうが表現力が高いと言えます。

Go 言語でひたすら書くエラー分岐の 3行:

err = json.NewDecoder(body).Decode(responseBody)
if err != nil {
    return err
}

&& || を使った複数条件

推奨

複数の条件を and 条件 && や or 条件 || で並べて書くことができます。

if [ "${variableA}" == "abc" ] && [ "${variableB}" == "def" ]; then
if [ "${variableA}" == "abc" ] || [ "${variableB}" == "def" ]; then

これは、前述の通り、&& は、前のコマンドが成功したら(終了コード 0 なら)、次のコマンドを実行し、|| は、前のコマンドが失敗したら(終了コード 0 以外なら)、次のコマンドを実行する機能を応用したものです。

この書き方は、以前からできましたが、あまり知られていませんでした。

非推奨

以前は、&&|| を書かず、test コマンドのハイフンから始まるオプションの形式がよく使われていました。 しかし、最近発表された POSIX.1-2024 で正式にこの形式は非推奨になりました。

if [ "${variableA}" == "abc"  -a  "${variableB}" == "def" ]; then
if [ "${variableA}" == "abc"  -o  "${variableB}" == "def" ]; then

2重カッコ [[ ]] を使えば、かっこの中に &&|| を書くこともできますが、上記の推奨される書き方とほぼ変わらず、POSIX では定義されていないので本記事としては非推奨です。

if [[ "${variableA}" == "abc"  &&  "${variableB}" == "def" ]]; then
if [[ "${variableA}" == "abc"  ||  "${variableB}" == "def" ]]; then

未定義の変数、Optional な型

推奨

未定義の変数は、空文字列(デフォルト)で初期化された変数であるとしてスクリプトを書くことが基本です。

未定義の場合、以下の条件文は真になります。 空文字の場合でも真になりますが、未定義の変数の値は空文字である型であると定義すれば細かい判定文が不要になって安全で扱いやすくなります。 詳細は、下記「null/undefined 許容型」に関する説明を参照してください。

if [[ "${variable}" == "" ]]; then  #// 未定義の場合は空文字列である型とし、空文字列なら真

set -u (上級者向け)

bash では、set -u を実行したら、未定義の変数を参照したときにエラーになります。 配列番号が範囲外の場合や、連想配列のキーが無い場合もエラーになります。 エラーの判定は実行開始時ではなく、参照を実行するときです(動的です)。 エラーになるとすぐにスクリプトが終了します。

example.sh: line 101: variable1: unbound variable
__ScriptPath__: line __Num__: __Variable__: unbound variable

グローバル変数

set -u を実行すると、未定義のときに参照されないようにデフォルト値で初期化するコードが必須になります。 set -u がオンでもオフでも共通ですが、しかし、初期化するコードを書くことで、Main 関数が始まる前に 未定義の状態を無くすことができます。 未定義の状態が無ければコードがシンプルに書けますし、未定義だったときにエラーになるので、問題が明確になります。 なお、"! -v" の意味を説明するコメントを書くこと(ここからコピペすること)を必須とします。 1文字の X(旧twitte)と同様に -v だけ書いても知識として定着しないからです。

bash/zsh 用オプション解析テンプレートの直後:

set -u

...

#// このコメント↓を書くこと
#// Set default values. "! -v" means that variable is not defined.
if ! [[ -v Options_SearchPath ]]; then  Options_SearchPath=""  ;fi
if ! [[ -v Options_Flag ]]; then  Options_Flag="false"  ;fi
if ! [[ -v Options_Help ]]; then  Options_Help="false"  ;fi

if [ "${Options_SearchPath}" != "" ]; then
    ParentPath="../${Options_SearchPath}"
else
    ParentPath=".."
fi

Main "$@"

💡 シェルスクリプト チュートリアル (2) 〜 データ編 で書いた「既定は偽」(yes) については、上記を踏まえた記事の改良をする予定です。

デフォルト引数

関数の引数をデフォルト引数にする場合:

set -u

function  DefaultArgumentSupport() {
    local  target="$1"
    local  option="${2-""}"  #// "${1-""}" means that "$1" default is "".
        #// デフォルト引数。 コメント↑を書くこと。 コメントの中は $1 を $2 に変える必要はありません

    if [ "${option}" == "" ]; then
        option="--sort"
    fi
}

DefaultArgumentSupport  "TargetA"

デフォルト引数は、上記のように "" で初期化することを推奨します。 途中の引数を省略するときは "" を書くからです。

非推奨

  • 未定義かもしれないからと、定義済みの場合や未定義の場合のエラー分岐をとりあえず書くことは非推奨です。

  • unset を書いて未定義にすることは非推奨です。 空文字を null 相当にするか、boolean 値を入れる別の変数を定義するか、関連する別の変数の値に置き換えることを推奨します。

  • if を書かないで [[ ]] を三項演算子のように書くのは非推奨です

  • "${1:-"value"}" 形式(:-)のデフォルト引数

未定義を扱うのは、TypeScript では null/undefined 許容型、Python や Swift などでは Optional 型(以後、どちらも Optional と呼ぶ)に限り、毎回 null チェックを行う必要が出てきます

一方、Optional でない型なら null チェックをしなくても安全に動作します。 そのため、スクリプトを呼び出す側で環境変数の設定がオプションである場合は、Main 関数を呼び出す前にデフォルト値を設定します。

bash 4.2 以降, zsh 5.3 以降, ksh93 では、[[ -v variable ]] で空文字列が入った変数と未定義の変数を識別して判定することができるようになりました。これにより、未定義であることが、JavaScript でいう undefined を持っていることと同様になり、毎回 undefined チェックする必要が出てきます。

if [[ -v variable ]]; then  #// 二重カッコ

if [[ -v $variable ]]; then  #// NG。$ を付けると variable 変数の「値」と同じ名前の変数について判定をしてしまいます

未定義の変数を参照したときの各種言語がどういう動作をするかは、大きく分けると以下のように分類されます。後で詳細な表も示します。 また、それらの動作に対応する bash の設定や、デメリットも一覧します。

動作 bash での実現方法 デメリット
空文字列 または 0 デフォルトの展開 未定義は論理エラーで対処
実行時エラー set -u デフォルトの指定が必要
undefined 相当 [[ -v ]] で判定可能 毎回 undefined チェックが必要
コンパイル時エラー 不可 -

[ -n "${var+x}" ] のように if が無いカッコは POSIX 準拠ですが、全く読めないので非推奨です。 構造的に三項演算子のように書くこともできますが、? ではないため他の言語をメインで使う人が三項演算子であることには気づかないでしょう。 きちんと if を書きましょう。

#// 非推奨
[[ -v variable ]] && trueCaseCommand
[[ -v variable ]] || falseCaseCommand
[[ -v variable ]] && trueCaseCommand || falseCaseCommand  #// 三項演算子のような配置だが、三項演算子と同じであるとは気づきにくい

#// 推奨
if ! [[ -v variable ]]; then  variable=""  ;fi  #// "! -v" means that variable is not defined.
if [ "${variable}" != "" ]; then
    trueCaseCommand
else
    falseCaseCommand
fi

以下のように "${1:-"value"}" 形式のデフォルト引数を書くと、"" も未定義として扱うためコード量が少なくなりますが、ローカル変数の無条件の初期化と混同しがちであることと、"" が有効な値として渡される可能性を見落としがちになることから非推奨です。

"" が有効な値になる場合は、引数を省略できない仕様にすべきです。

また、"${1:-value}" のようにデフォルト値の前後に " を書かないことも可読性の点で非推奨です。

    #// 非推奨
    local  option="${1:-"--sort"}"  #// "${1:-"default"}" means that "$1" default is "default". Also, "" value is treadted as unset.
    local  option="${1:-value}"     #// "${1:-"default"}" means that "$1" default is "default". Also, "" value is treadted as unset.
    local  option="${1-value}"      #// "${1-default}" means that "$1" default is "default".

    #// 以下のようなローカル変数の初期化と混同しがち
    local  option="--sort"

    #// 以下のコードが常に偽になるため、option はデフォルト引数(省略できる引数)ではなくす
    if [ "${option}" == "" ]; then

ちなみに他の言語で、初期化と同じ書き方でありながら、初期化と混同しにくい理由は、引数を書く場所にデフォルト値を書いているからです。

    function  foo(option = "--sort") {

ファイルの有無

    if [ -f "path" ]; then  #// file exists
        ...
    elif [ ! -f "path" ]; then  #// file does not exist
        ...
    elif [ "path1" -nt "path2" ]; then  #// is newer than
        ...
    fi

オプションの意味を説明するコメントを書くこと(ここからコピペすること)を推奨します。 必須としない理由は、値や変数名がファイル名であることからある程度は予想ができるためです。

  • -f: ファイル #// file exists
  • -d: フォルダー #// directory exists
  • -e: ファイルまたはフォルダー #// exists
  • -r: 読み取り可能
  • -w: 書き込み可能 #// is not read only
  • -x: 実行可能 #// is executable
  • -s: サイズが0より大きい #// file size is larger than 0
  • -L: シンボリックリンク #// is symbolic link
  • -nt: newer than (*XSI拡張) #// is newer than
  • -ot: older than (*XSI拡張) #// is older than
  • -ef: 同じファイル (*XSI拡張) #// is same file pointed by symbolic link

XSI拡張は POSIX の仕様上に存在しないだけで、すべてのシェルで使えます。

POSIX 標準に準拠する意味

POSIX だけを支持する人の中には、bash の仕様を bashism だと揶揄して呼ぶ人がいます。 -ism という言葉には、思想という意味がありますが、否定的な意味で使われる場面では、根拠がない単なる好み、相手の能力や判断力がないという、レッテル貼り(Derogatory labeling)に使われるため良くありません。

bash がある環境とない環境では「ある」環境のほうが多いため、ない環境のために苦労して POSIX 準拠する必要はありません。 bash が無い環境は、BusyBox ベースの組み込みシステムなど、意図的に最小化された環境に限られます。 BusyBox ベースの Alpine Linux(のコンテナー)でも、bash をインストールすれば使えます。 以下のコマンドを実行すれば bash が使えるようになります。

apk add bash

POSIX だけに準拠した環境の必要性については長くなるので、別の機会があれば話したいと思います。

シェル機能互換性表

mac ではシェバン(1行目)に #!/bin/bash を書くと、zsh ではなく bash でスクリプトが動きます。

文字列比較演算子

演算子 POSIX bash dash zsh ksh93 mksh busybox sh 説明
== 等しい
!= 等しくない
=~ 正規表現
< ✅* ✅* 辞書順で前 (*[ ]内で要エスケープ)
> ✅* ✅* 辞書順で後 (*[ ]内で要エスケープ)
= 等しい

算術演算

機能 POSIX bash dash zsh ksh93 mksh busybox sh 説明
$(( )) 算術展開
(( )) (条件) ksh由来の構文
let 算術評価コマンド
++, -- インクリメント/デクリメント
** (べき乗) べき乗演算子
, (カンマ) 複数式の評価

数値比較演算子

演算子 POSIX bash dash zsh ksh93 mksh busybox sh 説明
-eq equal (等しい)
-ne not equal (等しくない)
-lt less than (未満)
-le less than or equal (以下)
-gt greater than (より大きい)
-ge greater than or equal (以上)

論理演算子

演算子 POSIX bash dash zsh ksh93 mksh busybox sh 説明
-a (AND) ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ 非推奨 (obsolescent)
-o (OR) ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ 非推奨 (obsolescent)
&& (コマンド間) AND (推奨)
|| (コマンド間) OR (推奨)

テスト構文

構文 POSIX bash dash zsh ksh93 mksh busybox sh 説明
test 元のコマンド
[ ] test の別名
[[ ]] ✅** ksh由来 (**機能制限あり)
-v 定義済み判定、bash 4.2+, zsh 5.3+

文字列テスト演算子(非推奨)

演算子 POSIX bash dash zsh ksh93 mksh busybox sh 説明
-z 文字列が空
-n 文字列が非空

ファイルテスト演算子

演算子 POSIX bash dash zsh ksh93 mksh busybox sh 説明
-f 通常ファイル
-d ディレクトリ
-e 存在する
-r 読み取り可能
-w 書き込み可能
-x 実行可能
-s サイズが0より大きい
-L シンボリックリンク
-nt ❌* newer than (*XSI拡張)
-ot ❌* older than (*XSI拡張)
-ef ❌* 同じファイル (*XSI拡張)

その他の機能

機能 POSIX bash dash zsh ksh93 mksh busybox sh 説明
配列 インデックス配列
連想配列 ハッシュテーブル
ブレース展開 {a,b} 複数パターン生成
範囲展開 {1..10} 連続値生成
$RANDOM 乱数変数
プロセス置換 <() コマンド出力をファイルとして扱う
source . の別名
function キーワード 関数定義の別構文
local ❌* ❌** ローカル変数 (*dash拡張, **typeset使用)

シェルの整数演算のビット数と範囲

シェル ビット数 最小値 最大値 備考
bash 64ビット long long (符号付き) -9,223,372,036,854,775,808 9,223,372,036,854,775,807 bash 2.05b+
zsh 64ビット long (符号付き) -9,223,372,036,854,775,808 9,223,372,036,854,775,807 浮動小数点数もサポート
ksh93 任意精度 arbitrary precision 制限なし 制限なし メモリが許す限り
mksh 32ビット int (符号付き) -2,147,483,648 2,147,483,647 R59以降は64ビット対応
dash 64ビット intmax_t (符号付き) -9,223,372,036,854,775,808 9,223,372,036,854,775,807 0.5.11+
busybox sh 64ビット* long long (符号付き) -9,223,372,036,854,775,808 9,223,372,036,854,775,807 *ビルド設定による
POSIX 未規定 最低 long 実装依存 実装依存 最低32ビットを保証

浮動小数点数のサポート

シェル 浮動小数点数 精度 備考
bash - bcawk を使用
zsh 倍精度 (64ビット) setopt FORCE_FLOAT で有効化
ksh93 倍精度 (64ビット) typeset -E または typeset -F で宣言
mksh - 整数演算のみ
dash - 整数演算のみ
busybox sh - 整数演算のみ

=~ 正規表現演算子

言語/シェル =~ サポート !~ サポート 使用場所 キャプチャ変数 備考
Perl どこでも $1, $2, ... 発祥・最も統合
Ruby どこでも $1, $2, ... Perlから借用
bash [[ ]] のみ BASH_REMATCH[@] bash 3.0+
zsh [[ ]][ ] MATCH, match[@] 設定必要
ksh93 [[ ]] のみ なし キャプチャ取得不可
mksh - - サポートなし
dash - - サポートなし
Python - - re.search() 使用
JavaScript - - .test(), .match()
Java - - .matches()
PHP - - preg_match()
Go - - regexp パッケージ

未定義の変数を参照したときの各種言語の動作

言語 設定/モード 動作 エラー/機能
TypeScript - 未定義変数の使用を検出 コンパイルエラー
Rust - 未初期化変数の使用を検出 コンパイルエラー
Swift - 初期化前の使用を検出 コンパイルエラー
Java - ローカル変数の未初期化を検出 コンパイルエラー
C# - 未割り当て変数の使用を検出 コンパイルエラー
Go - 未使用・未初期化を検出 コンパイルエラー
bash デフォルト 未定義変数は空文字列として扱う エラーなし
bash set -u 参照時に未定義を検出 実行時エラー
Perl デフォルト 未定義変数は空文字列/0として扱う エラーなし
Perl use strict 'vars' 参照時に未定義を検出 実行時エラー
PHP エラー報告低 未定義変数を空値として扱う エラーなし
PHP エラー報告高 参照時に警告/エラー Warning/Error
JavaScript var 巻き上げで undefined エラーなし
JavaScript let/const 参照時に未定義を検出 ReferenceError
Python - 参照時に未定義を検出 NameError
Ruby - 参照時に未定義を検出 NameError
Lua - 未定義変数は nil を返す エラーなし
AWK - 未定義変数は空文字列/0 エラーなし

凡例

  • ✅: サポートあり
  • ❌: サポートなし
  • ⚠️: サポートされているが非推奨
  • *: 条件付きサポートまたは注意事項あり
  • **: 機能制限あり

参考

bashマニュアル >> BashとPOSIX
https://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html#Bash-POSIX-Mode

POSIX.1-2024 公式
https://pubs.opengroup.org/onlinepubs/9799919799/

How to make bash scripts work in dash (POSIX への移植方法)
https://mywiki.wooledge.org/Bashism

POSIX.1-2024 (Issue 8) 改定!16年ぶりの大幅改定でシェルスクリプトはどう新しくなるのか?
https://qiita.com/ko1nksm/items/b8dbf6aab82cb03eec51

Discussion