bash ベースのシェルスクリプトを書く際に覚えておいたら良い点のノート。 いろんな解説サイトを参考にさせて頂いています。
1. シェルの基本
1.1. 標準入出力とファイル
前提は、次のとおり。
- コマンドの printf などで出力された文字列を出すところを標準出力(stdout)と言う。
- コマンドのエラーを出力するところを標準エラー(stderr)と言う。
- コマンドが scanf などで入力するところを標準入力(stdin)と言う。
それぞれの入出力は、何もしない限り、以下のデバイスと関連付けされている。
- stdout → ターミナル画面
- stderr → ターミナル画面
- stdin → キーボード
それぞれの入出力は、簡単に切り替えることが出来る。
- 例) あるコマンド A は、標準入力から入力されたデータをそのまま標準出力に出力する処理を行うこととする。
- 何もしない限り、キーボードから入力されたデータ(この場合、文字)がそのまま画面に出力される。
- 標準出力をファイル、標準入力をキーボードのままにすると、キーボードから入力された文字がファイルに出力される(書き込まれる)。
- 標準出力を画面、標準入力をゲームパッドデバイスにすると、ゲームパッドから入力された信号が画面に出力される。
このように入出力を抽象化することにより、どのデバイス/ファイルを使おうと関係なく、一定の処理を行うことができる。
1.2. パイプとリダイレクト、演算
パイプとはコマンド同士の入出力をつないで一連の処理を行う仕組みであり、リダイレクトとは入出力を切り替えることである。
2. リテラル(定数)
リテラルは下記のように用いる。
echo abc
上の例では abc という文字列リテラルが echo コマンドの引数として渡されている。 スペースはシェルによって区切り文字として認識される為、スペースを含む文字列の場合、クォートで囲むかスペース文字をエスケープしてやる必要がある。
echo "abc def" echo 'abc def' echo abc\ def
また、シェルには型の概念がないので、「echo 123」と「echo “123”」はどちらも差すものは同じである。
3. 変数
変数はポインタのように利用する。
- 代入を行う際は、変数名のみを指定。例:hoge=123 … ポインタhogeが差す「アドレス」に代入される。
- 参照を行う際は、$変数名と指定。 例:echo $hoge … ポインタhogeが差すアドレスの「値」を参照する。
「$変数名」の記法を用いると、実行時にシェル展開が行われて解釈される。シェル展開と変数の遅延展開については後述する。
3.1. 予約済みの変数
環境変数 | |
---|---|
COLUMNS, LINES | キャラクタ端末の桁数と行数。termcap/terminfo/cursesなどで利用される。 他の方法(termcap、sttyなど)によって指定されていない場合に有効 |
EDITOR | エディタ名。エディタを起動するプログラム(mail、lessなど)で参照される。 |
HOME | 現在のユーザのホームディレクトリ。 |
LANG | ロケール。日本語ロケールの場合はjaまたはja_JPなどを指定する。setlocale()関数の呼び出しにより有効となる。 |
LESSCHARSET | lessの入力・出力文字コード。 |
メールボックスのパス。例: /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
文が存在する。これと while
、case
文を組み合せ、全てのオプションを正しく受け取る事が出来る。
while getopts l:t: opt
do
case $opt in
l) LIST=$OPTARG ;;
t) TYPE=$OPTARG ;;
\?) exit 1 ;;
esac
done
getopts
の第一引数は、オプションとして受け取る引数を表すが、オプション文字の直後に :
を書くことにより、そのオプションはオプション引数を取ることを意味する。また、getopts
は Bash の組み込みコマンドであるのに対し、独立したコマンド getopt
も存在するので注意が必要。