「新しいシェルプログラミングの教科書」を読んだメモ
一時ファイル名を作る
$$
で現在のプロセスのプロセス ID を取得できる。これは一時ファイルの名前生成に利用できる。
$ tmpfile=/tmp/$$ $ echo $tmpfile /tmp/157490
文字列の長さを得る
expr
を使う。
$ expr length "pewpewpew" 9 $ foo=hello; expr length $foo 5
$変数 と "$変数" の違い
$ cat test.sh #!/bin/bash list=("foo" "bar" "hello world") for i in ${list[@]} do echo $i done echo ----- for i in "${list[@]}" do echo $i done $ ./test.sh foo bar hello world ----- foo bar hello world
算術式評価
(( ))
を使う- カッコ内の変数の型(以下の例では
a
)は文字列なことに注意する
$ x=10; y=20; ((a=x+y)) $ echo $a 30 $ a=1+1 $ echo $a 1+1
読み取り専用変数
declare -r
と readonly
は同じ動作になる。
$ declare -r foo=bar $ foo=pew bash: foo: 読み取り専用の変数です $ readonly bark=bow $ bark=wanwan bash: bark: 読み取り専用の変数です
整数型の変数
declare -i
で宣言する。通常の文字列型と違い、右辺に式が書けるsum=x+y
のように式の中で変数を参照する時に$
は不要
$ declare -i x=50 $ declare -i y=10 $ declare -i sum=x+y $ echo $sum 60
配列
( )
を書いて、中に要素を並べると配列になる- 明示的に宣言するには
declare -a mylist
のようにする list=()
のように()
だけだと空の配列になる
- 明示的に宣言するには
- 要素を参照するには
${配列名[インデックス]}
する ${#配列名[@]}
で配列の要素数を得る${配列名[@]}
で全ての要素を得る配列名[インデックス]=値
で要素を変更する- 要素削除には
unset
を使う
$ dogs=(koro sophia bunta) $ echo ${dogs[0]} koro $ echo ${dogs} koro $ echo ${#dogs[@]} 3 $ dogs[0]=KORO $ echo ${dogs[0]} KORO $ echo ${dogs[@]} KORO sophia bunta
- 先頭に要素追加
$ dogs=(louis "${dogs[@]}") $ echo ${dogs[@]} louis KORO sophia bunta
- 末尾に要素追加
$ dogs+=(mona) $ echo ${dogs[@]} louis KORO sophia bunta mona
${配列名[@]:開始位置:終了位置}
で指定の要素を取り出せる
$ echo ${dogs[@]} louis KORO sophia bunta mona foo $ echo ${dogs[@]:1:3} KORO sophia bunta
連想配列
declare -A book
のようにする- 使い方は基本的に配列と同じ
$ declare -A book $ book=([author]=taro [title]=fugafuga) $ echo ${book[title]} fugafuga $ echo ${book[@]} fugafuga taro $ echo ${#book[@]} 2 $ book[publisher]=hogehoge $ echo ${book[@]} hogehoge fugafuga taro
パス名展開
パス名展開はシェルが行っている。一致するファイルが無い場合は、もとの記号がそのまま出力される。
$ ls foo.cpp foo.txt $ echo foo.* foo.cpp foo.txt $ echo boo.* boo.* $ ls foo.* foo.cpp foo.txt $ ls boo.* ls: 'boo.*' にアクセスできません: そのようなファイルやディレクトリはありません
文字列の切り出し
${変数名:開始位置:終了位置}
で切り出せる。
$ path=/etc/systemd/ $ echo ${path:1:3} etc
拡張子を得る
${変数名#パターン}
では 前方一致 したパターン部分を除いて展開される#
だと最短一致、##
だと最長一致になる。拡張子を得るなら##
がよく使われる
$ path=/tmp/foo.tar.gz $ echo ${path#*.} tar.gz $ echo ${path##*.} gz
ファイル名を得る
$ echo ${path##*/} foo.tar.gz
ディレクトリ名を得る
${変数名%パターン}
では 後方一致 したパターン部分を除いて展開される%
で最短一致させる所がポイント
$ echo ${path%/*} /tmp
if 文
- シェルスクリプトには boolean型 が無い。if 文ではコマンド実行結果で条件判定を行う
- コマンド実行結果が
0
なら真と判定される :
は常に0
を返す、組み込みコマンド
$ cat test.sh #!/bin/bash # if の直後にはコマンドが来る # ; が無いと then もコマンドの引数として認識されてしまう if ls > /dev/null; then echo foo fi if [ "foo" = "foo" ]; then # noop する時は : を使う # : は常に 0 を返す、組み込みコマンド : fi # 常に真になる if 文 if : ; then echo true fi $ ./test.sh foo true
test コマンド
- 文字列や数値の比較、ファイルの存在などを判定するコマンド
- if 文と組み合わせることが多い
[
とtest
は基本的に同じ
$ test "foo" = "bar"; echo $? 1 $ [ "foo" = "bar" ]; echo $? 1 $ [ "foo" = "foo" ]; echo $? 0 $ [ "foo" = "foo" -a 100 -ge 10 ]; echo $? 0
標準出力と標準エラー出力をまとめる
&>
を使うと便利。
$ ls foo ls: 'foo' にアクセスできません: そのようなファイルやディレクトリはありません $ ls foo &> result
ヒアストリング
$ cat <<< hello
hello
サブシェル
()
で囲われた処理は、現在のシェルの子プロセスとして新しく起動されたシェルで実行される。
()
内での環境変数などの変更は現在(親)のシェルに影響を与えない。
$ export foo=bar; (foo=pewpewpew); echo $foo bar $ pwd; (cd /); pwd /home/fuga/test /home/fuga/test
コマンドのグループ化
{}
または ()
で複数のコマンドを1つのコマンドとしてまとめることができる。
$ { ls /dev/pts/; echo hello; } > out $ cat out 0 1 ptmx hello
trap によるシグナルの補足
$ cat test.sh #!/bin/bash handler() { echo "catch INT" # exit しないとシェルスクリプトが最後まで実行される exit } trap handler INT echo start sleep 10 echo end $ ./test.sh start ^Ccatch INT
grep コマンド
-v
で検索パターンにマッチしなかった行が出力される
$ cat << EOF | grep -v -E "^a" > aaa > abc > zzz > bbb > baa > EOF zzz bbb baa
-o
で検索パターンにマッチした部分だけを出力する
$ cat << EOF | grep -o -E "foo|bar" > aaafooaaa > bbbddd > aaa > bbbbarbbb > EOF foo bar
env コマンド
環境変数を一時的に変更してコマンドを実行する。bash の場合は 変数名=値 コマンド
だけでも同じ動作になる。
$ cat main.go package main import ( "fmt" "os" ) func main() { fmt.Println(os.Getenv("foo")) } $ foo=hello $ env foo=bar go run main.go bar $ foo=bar go run main.go bar $ echo $foo hello
Bats でのテスト
Bats のインストール。
$ sudo apt install bats $ bats --version Bats 1.2.0-dev
テストを記述するファイルの拡張子は bats
にする。
$ cat mul.sh #!/bin/bash echo $(($1 * $2)) $ cat mul.bats PATH="${BATS_TEST_DIRNAME}:$PATH" @test 'mul test' { run mul.sh 2 3 [[ $status -eq 0 ]] [[ $output == 6 ]] } $ bats mul.bats ✓ mul test 1 test, 0 failures