Bashベースのシェルスクリプトに関する覚書

bash ベースのシェルスクリプトを書く際に覚えておいたら良い点のノート。 いろんな解説サイトを参考にさせて頂いています。

1. シェルの基本

1.1. 標準入出力とファイル

前提は、次のとおり。

  1. コマンドの printf などで出力された文字列を出すところを標準出力(stdout)と言う。
  2. コマンドのエラーを出力するところを標準エラー(stderr)と言う。
  3. コマンドが scanf などで入力するところを標準入力(stdin)と言う。

それぞれの入出力は、何もしない限り、以下のデバイスと関連付けされている。

  1. stdout → ターミナル画面
  2. stderr → ターミナル画面
  3. stdin → キーボード

それぞれの入出力は、簡単に切り替えることが出来る。

  1. 例) あるコマンド A は、標準入力から入力されたデータをそのまま標準出力に出力する処理を行うこととする。
  2. 何もしない限り、キーボードから入力されたデータ(この場合、文字)がそのまま画面に出力される。
  3. 標準出力をファイル、標準入力をキーボードのままにすると、キーボードから入力された文字がファイルに出力される(書き込まれる)。
  4. 標準出力を画面、標準入力をゲームパッドデバイスにすると、ゲームパッドから入力された信号が画面に出力される。

このように入出力を抽象化することにより、どのデバイス/ファイルを使おうと関係なく、一定の処理を行うことができる。

1.2. パイプとリダイレクト、演算

パイプとはコマンド同士の入出力をつないで一連の処理を行う仕組みであり、リダイレクトとは入出力を切り替えることである。

2. リテラル(定数)

リテラルは下記のように用いる。

echo abc

上の例では abc という文字列リテラルが echo コマンドの引数として渡されている。 スペースはシェルによって区切り文字として認識される為、スペースを含む文字列の場合、クォートで囲むかスペース文字をエスケープしてやる必要がある。

echo "abc def" echo 'abc def' echo abc\ def

また、シェルには型の概念がないので、「echo 123」と「echo “123”」はどちらも差すものは同じである。

3. 変数

変数はポインタのように利用する。

  1. 代入を行う際は、変数名のみを指定。例:hoge=123 … ポインタhogeが差す「アドレス」に代入される。
  2. 参照を行う際は、$変数名と指定。 例:echo $hoge … ポインタhogeが差すアドレスの「値」を参照する。

「$変数名」の記法を用いると、実行時にシェル展開が行われて解釈される。シェル展開と変数の遅延展開については後述する。

3.1. 予約済みの変数

環境変数
COLUMNS, LINES キャラクタ端末の桁数と行数。termcap/terminfo/cursesなどで利用される。 他の方法(termcap、sttyなど)によって指定されていない場合に有効
EDITOR エディタ名。エディタを起動するプログラム(mail、lessなど)で参照される。
HOME 現在のユーザのホームディレクトリ。
LANG ロケール。日本語ロケールの場合はjaまたはja_JPなどを指定する。setlocale()関数の呼び出しにより有効となる。
LESSCHARSET lessの入力・出力文字コード。
MAIL メールボックスのパス。例: /var/mail/$USER,/usr/spool/mail/$USER
NAME ユーザ名。メール送信時などに使われる。
PAGER ページャ(more、less、pgなど)。ページャを起動するプログラム(manなど)で使われる。
PATH コマンド検索パス(コロンで区切り)。シェルが参照する。
PWD カレントディレクトリ。シェルスクリプトでpwdコマンドの代わりに$PWDを参照することがある。
SHELL 現在のシェルの起動パス。シェルを確認するのに利用できる。例:/bin/sh
TERM 端末種別。この値をキーとしてtermcap/terminfoデータベースが検索される。例: vt100
TERMCAP termcapデータベースファイルのパス、あるいは検索されたデータベースエントリの内容。例: /etc/termcap
TERMINFO terminfoデータベースディレクトリのパス。例:/usr/share/misc/terminfo
TZ タイムゾーン情報。標準Cライブラリの日時関連の関数で参照される。例: JST-9
USER ログイン名。
VISUAL スクリーンエディタ名。EDITORと同様。
IFS 区切り文字。デフォルトでは空白および改行文字だが、ISF=”,” すると CSV ファイルなどの区切り文字に対応する事が出来る。
特殊シェル変数
$n n 番目の引数。10 番目以降は ${nn} と表記
$# 与えられた引数の数
$0 実行中のスクリプトファイル名
$@ $0 を含まない全引数
$* $0 を含まない全引数
$? 直前のコマンドの終了コード(0 以外はエラー)
$! 直前のバックグラウンドコマンドの PID
$$ このシェルの PID
$- 現在のオプションフラグ

$@ と $* の違い

  • “$@” => “$1” “$2” “$3” … 個別の要素として展開される
  • “$*” => “$1 $2 $3” … 区切り文字で連結された一つの要素として展開される

多くの場合、for文でリストとして用いたい場合は$@を用いることになる。

3.2. 配列

  • a=(a b c d), files=(`ls`), a[0]=a で代入
  • ${a[0]} で要素にアクセス
  • ${a[@]} で全ての要素にアクセス

4. 演算

変数や展開を用いた演算は次のとおり。

$ $(echo date)
$ `echo date`

両者とも意図するところは同じである。”echo date”コマンドが評価され、$()内、もしくは“内は「date」という文字列リテラルになる。 さらに「date」という文字列リテラルをコマンドとして評価し、結果、得られるのはdateコマンドの出力である。exec関数のような挙動をすると考えれば良い。

4.1. 変数内演算

4.2. bash 独自の演算記法

4.3. expr コマンドでの算術演算

数値演算
n1 + n2 n1 と n2 の和
n1 – n2 n1 と n2 の差
n1 * n2 n1 と n2 の積( アスタリスクのシェル展開に注意 )
n1 / n2 n1 と n2 の商
n1 % n2 n1 と n2 の剰余
関係演算
n1 & n2 n1 及び n2 が共に 0 または NULL 以外なら n1 を返す。それ以外は 0 を返す。
n1 | n2 n1 が 0 または NULL 以外なら n1 を返す。0 なら n2 を返す。
n1 = n2 n1 と n2 が一致なら 1 を返す。それ以外は 0 を返す。
n1 > n2 n1 が n2 より大きいなら 1 を返す。それ以外は 0 を返す。
n1 < n2 n1 が n2 未満なら 1 を返す。それ以外は 0 を返す。
n1 >= n2 n1 が n2 以上なら 1 を返す。それ以外は 0 を返す。
n1 <= n2 n1 が n2 以下なら 1 を返す。それ以外は 0 を返す。
n1 != n2 n1 と n2 が不一致なら 1 を返す。それ以外は 0 を返す。
文字列演算
str : regex str と regex が一致し、regex に”\(” と “\)”が使われていればマッチした文字列を返す。そうでないならマッチした文字列の長さを返す。 また、一致しなかった場合”\(” と “\)”が使われていればNULLを返し、そうでないなら 0 を返す。
index str1 str2 str1 から str2 のいずれかの文字が最初に見つかった位置を返す。見つからなければ 0 を返す。
length str 文字列の長さを返す。
match str regex “str : regex” と同じ。
quote str 文字列に演算子やキーワードを含んでも通常の文字列として扱う。
substr str position length position から始まり length 長の部分文字列を返す。position や length が正の数値でない場合は NULL を返す。

str : regex について

どうやら前方から検出していっている模様。

$ expr fuga : 'f'
1
$ expr fuga : 'u'
0
$ expr fuga : '.*u'
2
$ expr fuga : 'g'
0
$ expr fuga : 'a'
0
$ expr fuga : 'a$'
0
$ expr fuga : '.*a$'
4

4.4. test コマンドで評価される条件式

ファイル形式のチェック
-b path ブロックデバイスファイルなら真。
-c path キャラクタデバイスファイルなら真。
-d path ディレクトリなら真。
-f path 通常ファイルなら真。
-L path シンボリックリンクなら真。
-p path 名前付きパイプなら真。
-S path ソケットなら真。
ファイルパーミッションのチェック
-g path SGID がセットされていれば真。
-k path スティッキービットがセットされていれば真。
-r path 読み取り可能なら真。
-u path SUID がセットされていれば真。
-w path 書き込み可能なら真。
-x path 実行可能なら真。
その他のファイルのチェック
-e path 存在すれば真。
-s path ファイルサイズが 0 より大きければ真。
文字列のチェック
-n str 長さが 0 より大きければ真。
-z str 長さが 0 であれば真。
str1 = str2 等しければ真。
str1 != str2 等しくなければ真。
数値のチェック
n1 -eq n2 等しければ真。
n1 -ge n2 n1 が n2 以上であれば真。
n1 -gt n2 n1 が n2 より大きいのであれば真。
n1 -le n2 n1 が n2 以下であれば真。
n1 -lt n2 n1 が n2 未満であれば真。
n1 -ne n2 等しくなければ真。
論理結合
!exp 条件が偽であれば真。
exp1 -a exp2 両方が真であれば真。
exp1 -o exp2 どちらかが真であれば真。

5. 制御構文

5.1. 分岐

  • if文

    if [ exp ] then foo else bar fi

他のスクリプト言語同様、else 文は省略することが可能。

  • case文

pattern には、正規表現を含む論理式を記述する事が出来る。

case var in
  pattern1)
    foo
    ;;
  pattern2)
    baz
    ;;
  *)
    # No match
    ;;
esac

;; が C でいう break 文に相当する。*)default: に相当する。

5.2. ループ

  • for文

他の言語で言う foreach 文と同じ挙動をする。

for var in array
do
  foo
done

array は、シェル展開が行なわれた直後にスペースで区切られた文字列になっていればよい。空文字列の場合は何もせずに抜ける。

  • while文

    while read line
    do
    echo $line
    done < file

  • until文

    untile [ exp ]
    do
    hoge
    done

  • break/continue文

    break / continue [n]

5.3. 関数

関数を定義すると、コマンドと同じような呼び出しを行う事が出来る。return 文で返すのは結果ではなく、終了コードであり、関数の実行結果などは標準出力に印字するのが一般的である。

function funcname()
{
  foo
}

Bash では宣言の function または () は省略可能だが、どちらとも省略することはできない。また、Bash 以外のシェルでは { を次行に書くとシェル関数として認識することができない場合があるため、注意が必要。

呼び出す方法は

funcname

でOK。通常のコマンドのように使うことができる。

引数を渡す場合は、定義を次のようにする。

function funcname()
{
   age=$1
   name=$2
   echo "$name is $age years old."
}

呼び出し元は

funcname 30 "dyama"

となる。

5.4. オプション

*./script -l list -t type* のように、コマンドラインからオプションを指定してスクリプトを実行させる際に有用な getopts 文が存在する。これと whilecase 文を組み合せ、全てのオプションを正しく受け取る事が出来る。

while getopts l:t: opt
do
  case $opt in
    l) LIST=$OPTARG ;;
    t) TYPE=$OPTARG ;;
    \?) exit 1 ;;
  esac
done

getopts の第一引数は、オプションとして受け取る引数を表すが、オプション文字の直後に : を書くことにより、そのオプションはオプション引数を取ることを意味する。また、getopts は Bash の組み込みコマンドであるのに対し、独立したコマンド getopt も存在するので注意が必要。

コメントを残す

メールアドレスが公開されることはありません。