入門 bash 読書ノート 2/3

著者:

Cameron Newham, Bill Rosenblatt

訳者:

株式会社クイープ

出版社:

オライリー・ジャパン

発行年:

2005 年

ISBN:

978-4-87311-254-1

4 章 基本的なシェルプログラミング

4.1 シェルスクリプトと変数

  • <スクリプト とは、シェルコマンドが含まれたファイル、つまりシェルプログラムのことである。 3 章で説明した .bash_profile や環境定義ファイルもシェルスクリプトである> (p. 85)

  • スクリプト名を入力すると、<サブシェル と呼ばれるシェルの新しいコピーがサブプロセスとして実行される。サブシェルはスクリプトからコマンドを取り出し、それらを実行して終了した後、制御を親シェルに戻す> (p. 86) という一連の処理が発生する。

4.1.1 関数

  • <関数とはスクリプトの中のスクリプトのようなもの> (p. 87) メモリーにシェルコードが格納される。

  • 定義方法は次のどちらかとなる。機能差はない。

    function function-name
    {
      shell-command
    }
    # or
    function-name ()
    {
      shell-command
    }
    
  • declare -F で、存在する関数の名前一覧を表示できる。

  • 組み込みコマンド type でコマンドの種類を確認できる。

4.2 シェル変数

  • <言語の違いを特徴付ける方法として、変数の機能を比較することが重要なほどである> (p. 90)

  • bash は <文字列をことのほか重視する> (p. 90)

4.2.1 位置パラメータ

  • 位置パラメータ は、スクリプトが呼び出されたときに、そのコマンドライン引数を保持する。

  • $1, $2, $3, … で参照できる。

  • $0 はスクリプト自身の名前を含む。

  • $*$1 以降すべての位置パラメータからなる文字列。

    • IFS の 1 文字目で区切られた文字列

  • $@"$1" "$2" "$3" ... "$N" に等しい。二重引用符とスペース文字は固定。

  • $# は、位置パラメータの個数(を示す文字列)。

  • 関数も独自の位置パラメータを持つ。

$*$@ はよくどっちがどっちだか忘れるので注意。

4.2.2 関数のローカル変数

  • <関数の定義に local 文が含まれている場合、その関数の変数は すべて 関数のローカル変数になる> (p. 93)

4.2.3 $@$* での引用

  • $* は出力での活躍が多いらしい。位置パラメータのリストをカンマ区切りで表示したい場合は、IFS=, echo "$*" とする。

4.2.4 変数の構文について

  • 正式には ${変数名} のように中括弧がある。

4.3 文字列演算子

4.3.1 文字列演算子の構文

${variable:-word}

変数が未定義のときにデフォルト値を 返す

${variable:=word}

変数が未定義のときにデフォルト値を 設定する

${variable:+word}

変数が定義されているかどうかを知る。定義されていても word を返すので variable 自身の値は得られない。

${variable:offset:length}

部分文字列を返す(スライス)

  • <位置パラメータの値をわかりやすい名前の変数に代入すれば、変数名を改善することができる> (p. 98)

    filename=$1
    howmany=${2:-10}
    
  • <echo-e オプションは、引数を表示した後に改行しないことを示す> (p. 100)

4.3.2 パターンとパターン照合

  • ${variable#pattern} 等を照合演算子という。

    • # は始めの部分を照合し、% は終わりの部分を照合する。

    • 一文字が最短一致で、二文字が最長一致。

  • 置換は ${variable/pattern/string}${variable//pattern/string} で行う。

    outfile=${filename%.pcx}.jpg
    
  • $PATH を読みやすくするには echo -e ${PATH//:/'n'} がおすすめ。

4.3.4 高度なパターン照合

  • <shoptextglob オプションをオンにした場合に使用できるパターン照合演算子がいくつかある> (p. 104)

    *(pattern-list)

    0 個以上検出

    +(pattern-list)

    1 個以上検出

    ?(pattern-list)

    0 or 1 個検出

    @(pattern-list)

    1 個検出

    !(pattern-list)

    一致しないものを検出

    bash$ shopt -s extglob
    bash$ echo *.+(txt|html)
    <ファイル名が .txt または .html で終わるものすべて>
    bash$ echo !(*Makefile)
    <ファイル名が Makefile なんとか以外すべて>
    

4.4 コマンド置換

  • $(<コマンド>) とすると、<コマンドの標準出力を変数の値として使用することができる> (p. 105)

  • 昔はバッククォートで囲んでいたようだが、読みにくいうえに入れ子にできない。ドルカッコのほうを使うべし。

    ls -l $(type -path -all command-name)
    
  • <関数の名前に他意はない> (p. 108) とか小ネタで笑わせてくる。

  • タスク 4-7 を読んでいて思うのだが、ls -l の結果を加工する種のスクリプトは、どうしても可搬性に難のあるものにはなるまいか。

5 章 フロー制御

  • この章では ifelse, for などの導入をする。

  • <一から説明されることにうんざりしているプログラマの気持ちもわからないではない> (p. 113)

5.1 if/else

if <条件>
then
    <文ブロック>
elif <条件>
    then <文ブロック> ...
else
    <文ブロック>
fi

5.1.1 終了ステータス

  • コマンドや関数は終了時に呼び出し元に整数コードを返す。これを 終了ステータス という。

  • <通常は 0 が正常終了、それ以外 (1 から 255) が異常終了を示す> (p. 115)

5.1.2 return

  • return N 文が含まれている関数は、終了ステータス N で終了する。 N を省略することもでき、その場合は最後のコマンドの終了ステータスが設定される。

5.1.3 終了ステータスの組み合わせ

  • bashif 文において、&&, || は short-circuit rule が適用される。

5.1.4 条件の評価

  • [...][[...]] の二つがある。ここでは一つ目の構文を使用する。

  • [...] 構文を使用すれば、次のことができる。

    • ファイルの属性をテストする

    • 二つのファイルの新しさを比較する

    • 文字列同士を比較する (str1 = str2, str1 != str2, str1 < str2, etc.)

  • 文字列変数をテストする際は、二重引用符で囲むのが望ましい。

  • <コード全体が if-then-else で囲まれているほうがよいプログラミング作法であるという考え方もあるが、エラーを確認しながらいくつかに分岐するような長いスクリプトを書くのは混乱のもとである> (pp. 121-122)

  • ファイル属性演算子。よく使いそうなのをノートしておく。-x 演算子の意味だけ注意がいる。

    -a file

    file が存在する

    -d file

    file が存在し、かつディレクトリである

    -e file

    -a と同じ

    -r file

    file を読み取れる

    -w file

    file を上書きできる

    -x file

    file がファイルの場合、それが実行可能である。file がディレクトリの場合、その中を検索できる。

    file1 -nt file2

    file1file2 よりも新しい

    file1 -ot file2

    file1file2 よりも古い

5.1.5 整数の条件式

  • 整数を比較する演算子が存在するが、整数値だけを扱う条件式の構文が別に存在するのでそちらを使うこと。

5.2 for

  • <for ループはコマンドラインの引数や一連のファイルを処理するのに最適である> (p. 129)

    for name [in list]
    do
        <$name を使用する文ブロック>
    done
    
  • in list の部分を省略すると、デフォルトでは $@ となる。

  • <for ループの使用法としては、コマンドライン引数を 1 つずつ処理するほうが一般的である> (p. 130)

  • タスクで紹介している再帰処理で、ディレクトリ階層を下がるたびに出力文字列をタブでインデントしている。tab=$tab$singletab でタブ文字を伸ばしているのが面白い。階層を上がるときは tab=${tab%"$singletab"} としている。

5.3 case

  • Pascal の case 文に相当する。

  • ワイルドカードを使ったパターンと文字列の照合が可能。

    case <> in
        <パターン> )
            <文ブロック> ;;
        <パターン> )
            <文ブロック> ;;
        ...
    esac
    
  • C 言語の default のような処理をするならば * ) を使える。

5.4 select

使いそうにないので飛ばす。

5.5 whileuntil

  • 構文は共に以下の通りで、whileuntil は条件式を扱う方法の違いしかない。

    while <条件>
    do
        <文ブロック>
    done
    
  • <本書の見解では、until が必要になることはまれである> (p. 143)

6 章 コマンドラインオプションと型を持つ変数

6.1 コマンドラインオプション

6.1.1 shift

  • shift コマンドで、位置パラメータを前にずらすことができる。

    • shift 3 とすると、位置パラメータが 3 個ずれる。

  • <通常の UNIX 構文では、オプションが引数の前にある> (p. 147)

6.1.2 引数付きのオプション

  • <多くのコマンドに 独自に 引数をとるオプションがあることを思い出そう> (p. 148) そういう場合は追加の shift が要る。

6.1.3 getopts

  • shift の利用だけでは <-a -b -c ではなく -abc のように、ハイフンを 1 つで組み合わされた引数には対応できない。また、-b arg ではなく -barg のように、スペースを要れずに引数を指定することもできない> (p. 149)

    while getopts ":ab:c" opt; do
      case $opt in
        -a ) <オプション -a の処理> ;;
        -b ) <オプション -b の処理>
             <$OPTARG はオプション固有の引数> ;;
        -c ) <オプション -c の処理> ;;
        -? ) echo 'usage: alice [-a] [-b barg] [-c] args...'
             exit 1
        esac
    done
    
    shift $(($OPTIND - 1))
    
    <通常の引数処理>
    
  • <オプションが引数をとる場合、getopts はそれを OPTARG 変数に設定する> (p. 150)

6.2 型を持つ変数

  • 変数には「読み取り専用」や「整数型」といった属性を設定することができる。それには、組み込みコマンド declare を使用する。

    -a

    配列

    -f

    関数名

    -i

    整数値

    -r

    読み取り専用

    -x

    変数をエクスポート

  • <関数において declare で宣言された変数は、関数のローカル変数となる> (p. 155)

6.3 整数型の変数と算術演算

  • <$(()) で囲まれた文字列は、算術演算式として評価される> (p. 155)

  • 表 6-2 によると、算術演算子は C 言語のそれとほぼ同じ。べき乗演算子があるのが面白い。** と書けばよいようだ。

  • 関係演算子と論理演算子もある。

  • 基数もサポート。例えば $((2#1001)) は二進数の 1001 のことだ。

6.3.2 数値変数と代入

  • <let 文を使用すれば、算術演算子を評価した後、その結果を変数に代入することができる> (p. 158)

    let <整数型の変数>=<>
    

6.4 配列

  • 配列の定義方法はいくつかある。とりあえず次の方法だけ覚える。

    # 方法 1
    names[2]=alice
    names[0]=hatter
    names[1]=duchess
    
    # 方法 2
    names=([2]=alice [0]=hatter [1]=duchess)
    
    # 方法 3
    names=(hatter duchess alice)
    
  • 配列の要素を参照するには ${names[0]} のようにする。

  • 位置パラメータのそれと同様に、${names[@]}, ${names[*]} が使用できる。

    • for ループで配列の要素を順番に参照することができる。

    • 値が設定されている要素のインデックスを知るには、${!names[@]} とする。

    • 配列の長さを ${#names[@]} とする。

  • 配列の特定の要素を削除するには unset names[1] のようにする。

  • 配列全体を削除するには unset names とする。

  • /etc/passwd のユーザー名とユーザー ID から配列を作成する例。 cut で切り出したコロン区切りの文字列を、文字列演算子を利用して split して、上述方法 1 のやり方で配列要素を順次追加している。

7 章 入出力とコマンドラインの処理

7.1 入出力リダイレクタ

  • 表 7-1 にまとまっている。いつも & が付くリダイレクタの意味がわからなくなるのだが、& はコピー、&- は停止と憶えておけばよい?

  • <set -o noclobber と入力すると、> file によるファイルの上書きを阻止することができる> (p. 173)

7.1.1 ヒアドキュメント

  • <ヒアドキュメントは、コマンドプロンプトから使用してもあまり意味がない> (p. 173)

  • <<< リダイレクタは 2 種類に分かれる。まず、label を単一引用符または二重引用符で囲むと、パラメータ置換とコマンド置換は実行されなくなる> (p. 175)

  • <<<- リダイレクタを使用すると、ヒアドキュメントとラベル行からの先頭のタブを削除することができる(それ以外の空白は残る)> (p. 175) ので、ヒアドキュメントのテキストを読みやすくするためにインデントできる。

7.1.2 ファイルデスクリプタ

  • エラーメッセージをファイルに出力するには COMMAND 2> file とする。

  • かつ、標準出力も同じように処理するには コマンド > file1 2> file とする。

  • 標準出力と標準エラーの両方をファイルに出力するには COMMAND > file 2>&1 とする。

    • パイプに出力するには COMMAND 2>&1 | とする。

7.2 文字列の入出力

7.2.1 echo

  • -e, -n オプションを憶える。

  • エスケープシーケンス はあまり憶えなくても済む。使うときは -e と組み合わせることになると思う。

7.2.2 printf

  • ザッと見る限り、C 言語のそれと同じように使えるようだ。

7.2.3 read

  • シェル変数に値を取り込むためのコマンド。read var1 var2 ... のような構文をとる。

  • <ワードよりも変数の方が多い場合、余分なワードは最後の変数に代入される。変数を 1 つも指定しないと、入力行はまとめて REPLY 変数に代入される> (p. 183)

  • read は行単位の処理を指向している。が、そういうのはパイプラインが行う仕事だろうから<行単位での処理を行いたいのであれば、シェルスクリプトを使用する理由はまったくない> (p. 183)。

  • 関数は標準入出力デスクリプタを独自に持つ。関数呼び出しの右側にリダイレクタを書いたり、関数定義の終了直後にリダイレクタを書いたりできる。

    findterm () {
       # ...関数定義
    }
    
    findterm < /etc/terms
    
    findterm () {
      # ...関数定義
    } < /etc/terms
    
  • ループや iffi, caseesac, selectdone 等の定義直後でも同様に可能。

  • <コマンドを {} で囲むと、そのコードは名前のない関数のように機能する> (p. 186) 本書ではこれを コマンドブロック と呼んでいる。このブロックの終了直後も、リダイレクタを置ける。

  • ユーザーへのプロンプトの出し方が参考になる。下のコード片だが、echo -n で改行を抑制していることと、>&2 で標準出力を標準エラー出力に切り替えていることがポイント。

    echo -n 'terminal? ' >&2
    

7.3 コマンドラインの処理

  • 図 7-1 の「コマンドライン処理の流れ」の要点がよくわからない。

  • 小ネタだが、~+~- はそれぞれカレントディレクトリと、直前のディレクトリに置換されるらしい。p. 192 の脚注より。

7.3.2 command, builtin, enable

  • <コマンドは、関数、組み込みコマンド、スクリプト、実行可能ファイルの順に検索される。この順序は、command, builtin, enable の 3 つの組み込みコマンドを使って、変更することができる> (p. 194)

    • command は組み込みコマンドと検索パス上にあるコマンドだけに実行候補を絞る。

    • builtin は組み込みコマンドだけ。builtin printf のように使う。

    • enable は<組み込みコマンドを有効または無効にする> (p. 195)

      • enable -n enableenable 自身を無効にできる。元に戻せない?

  • <test という名前はプログラムに向いていないようだ> (p. 196) は至言。

7.3.3 eval

  • <スクリプトを実行しながらコマンド文字列をその場で生成し、シェルにそれらを実行させることができる> (p. 197)

  • <変数名の先頭のドル記号をバックスラッシュエスケープしたのは、変数の値に >| といった特殊記号が含まれていると、思わぬ結果を招くからだ。バックスラッシュには、eval コマンド自体が実行されるまでの変数の評価を先送りするという働きがある> (p. 198)

    eval sort -nr \$1 ${2:+"| head -\$2"}
    
    eval "$@" > logfile 2>&1 &