Perl とシェルスクリプトで逆になっていると思えるほど違う部分の理由を考える

  • 13
    Like
  • 0
    Comment

Perl は C言語の文法を参考にしつつも、Bash シェルスクリプトで使われる sed や awk の置き換えも考慮したグルー言語でもあります。そういった複数の影響のためか、Perl とシェルスクリプトで似たような書き方なのに書き方が違うといった場合があります。

シェルスクリプトもシェルの数に応じた文法がありますが、ここでは Bash を取り上げます。

数値と文字、それぞれの比較演算子

  • Perl の場合は数値比較が == で、文字比較が eq
  • Bash の場合は数値比較が -eq で、文字比較が =

細かい違いはあるにしろ、数値比較と文字比較が事実上逆の演算子になっています。

if ( $i == 1 ) {
    print "i is one\n";
}
if [ $i -eq 1 ] ; then
    echo "i is one"
fi

Bash では test としても知られる [...] と、後に導入された [[...]] の違いはありますが、今回は古くからお馴染みの [...] を取り上げます。

Perl はシェルスクリプトの文法を参考にしているのではなかったのか…と一瞬混乱してしまいます。どうしてこうなったかの説明は色々あるかと思いますが、私は以下のように考えています。

  • Perl の等号は C言語を参考にした。C言語は文字列というものよりも数値の扱いが基本的であり、それにならって数値が == の比較となった
  • シェルスクリプトは文字列を受け取ることを主眼に置いたもの。= も文字列比較として自然に定義されるようになった。

むしろC言語は数値以外の比較演算子は無いと考えるべきでしょう。char型の1文字の比較もコードポイントの数値での比較ですし、char型へのポインタとして表現された文字列を比較するには strcmp といった関数の力を借りる必要があります。

真偽値の扱い

Perl における真偽値は若干複雑と言われますが、非常に大雑把に言えば数値の 0 や文字列の空文字列 "" が偽で、後の多くは真として扱われます。Perl の真偽値の詳細については別の記事で触れることにします。

Bash における真偽値とは終了コード(ステータスコード)であって、プログラミング言語としての真偽値とは若干趣が異なるともいえますが、 if と組み合わせて使われる [...] として知られる test コマンドや true false といった名前自体が真偽値を体現しているコマンドの終了コードは真偽値と受け取るのが自然でしょう。

  • Perl の場合は数値の 0 が偽で、それ以外の数値は真
  • Bash の場合は数値の 0 が真(正常終了)で、それ以外の数値(整数)は偽(異常終了でエラーコードとなっている)
#!/usr/bin/perl

use strict;
use warnings;

my $false_condition = 0;
my $true_condition = 1; # 2 でも 3 でも
if ( !$false_condition ) {
    print "false\n";
}
if ( $true_condition ) {
    print "true\n";
}
#!/bin/bash

function false_condition {
    return 1; # 2 でも 3 でも
}
function true_condition {
    return 0;
}

if ! false_condition ; then
    echo "false"
fi
if true_condtion ; then
    echo "true"
fi

Perl の真偽値は、C言語のそれを踏襲したものになっていることはすぐにわかります。Perlにおける真偽値の複雑さというのは、C言語にはない文字列(C言語のようなchar型へのポインタといったものではなくプリミティブなもの)やリストや未定義状態といったものに対するPerlの考えといったものが入ったからだと思えます。元々のC言語が真偽値型を導入せず整数型(int型)で真偽値型を代用した功罪をPerlも受け継いでいるといえるのではないでしょうか。

対して Bash の真偽値というのはエラーの有無であって、エラーがある=偽=処理を中断すべきものという意味付けをされているように見えるのは面白い特徴です。また、偽として評価されるコマンドの返り値の1以上の整数値はエラーコードとなっており、どういうエラーなのかを実行元に伝える役割も持ちます。正常終了の時の終了コードは0のみであり、正常な場合には多くを語らないというUNIXの哲学を体現しているようです。

ごく一部のコマンドでは、エラーとまでは言えない状況を伝えるために1以上の終了コードを使っている例があります。有名なものは diff コマンドで、終了コード 0 が差分なし、1 が差分あり、2 が比較できなかったエラーとなっています。差分ありの状況をエラーと解釈するのは多くの場合自然ではないので、終了コードを真偽値と解釈してコマンド連結をする &&|| といった演算子で繋ぐ場合や set -e (set -o errexit)といった設定でシェルスクリプトを書く場合には終了コードが1以上の場合でエラーとは言えない状況を独自にハンドリングする必要はあるでしょう。