目次
ShellScript
bash
シェルスクリプトは基本的にはbashをターゲットに作成する。
#!/bin/bash -eu ...
shebangは#!/bin/sh
にはしない
dockerで起動する場合、alpineにはbashが入ってないため、ubuntu-slimをベースイメージにする。
shの実行オプション
- -e 実行時コマンドが失敗したら(ステータス0以外を返したら)以降の処理を打ち切る
- -x 変数を展開した状態で実際に実行されるコマンドを標準エラー出力に書き出す
- -u 未定義の変数があれば処理を打ち切る
- -C noclobberオプションを有効にする(リダイレクトでファイルを上書きしようとするとエラーになる、追記はOK)
shebangに追加されることが多い
#!/bin/bash -uex ...
testコマンド
test -f ./hoge.txt && echo "hoge.txtあるよ" if [ -f ./hoge.txt ]; then echo "hoge.txtあるよ" fi if [[ -f ./hoge.txt ]]; then echo "hoge.txtあるよ" fi
- -e ファイルが存在すればtrue
- -f 通常ファイルならtrue
- -d ディレクトリならtrue
- -L シンボリックリンクならtrue
- -r 読み込み可能ならtrue
- -w 書き込み可能ならtrue
- -x 実行可能ファイルならtrue
- -z 文字列長が0ならtrue
- -n 文字列長が0でなければtrue
- -t 指定した番号のファイルディスクリプタが開かれていればtrue
[[ ]]
testコマンドのbash拡張
単語分割やパス展開しないので、変数をクオートで囲む必要ない
$ HOGE="a b c" $ [ $HOGE == "a b c" ] && echo "一致" bash: [: too many arguments $ [[ $HOGE == "a b c" ]] && echo "一致" 一致 $ HOGE=hoge.* $ [ $HOGE == "hoge.*" ] && echo "一致" || echo "不一致" 不一致 $ [[ $HOGE == "hoge.*" ]] && echo "一致" || echo "不一致" 一致
read
標準入力から1行読み取る
read line echo $line
プロンプトを出して入力を促す
echo -n "User Name: " read username echo $username
zshでは以下のようにかける
read "username?User Name: " echo $username
入力値をマスクしたい場合はread -s(決定時の改行も出力されなくなるのでechoを挟むとよい)
read -s "password?Password: " tty -s && echo echo $password | shasum -a 256
標準入力が尽きるまで行を読み取る(Ctrl-DでEOFを送信)
while read line do echo $line done
ファイルから行を読み込みする
while read line do echo $line done < hoge.txt
ヒアドキュメントから行を読み込みする
while read line do echo $line done << EOS 1 2 3 EOS
trap
シグナルのハンドリング
trap "echo trap sigint" INT while :; do sleep 1; done
SIGINTをトラップすると、Ctrl-cで終了できなくなるので注意。またSIGKILLはトラップできない。
EXITで終了時処理
基本はシグナルのハンドリングだが、EXITとERRという疑似シグナルが利用できる。例えばmktempで作成した一時ファイルをスクリプト終了時に削除するなどの処理をEXITと組合わせて書ける。
hoge=/tmp/hogehoge mktemp $hoge trap "rm $hoge" EXIT
テクニック
スクリプトファイル名を取得
FILENAME=$(basename $0) FILENAME=${0##*/}
スクリプトのディレクトリを取得
カレントディレクトリにかかわらず、スクリプトが存在するディレクトリを取得する
DIR=$(cd $(dirname $0); pwd)
コマンドの存在チェック
「コマンド hoge が PATH にあれば…」の条件分岐
if [ -x "$(which hoge)" ]; then echo "hogeあるよ" fi
ランダム文字列を生成
0-9A-Za-zのランダムな文字列を作成する。mac用
$ cat /dev/random | base64 | fold -w 16 | egrep -v "[+/]" | head -n 1 $ head -c 100 /dev/random | base64 | tr -d "+/" | head -c 16
セキュアな必要がないならば
$ shasum <(ls -al) | cut -d " " -f 1
変数参照
${HOGE:-wang} # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangが展開される ${HOGE:+wang} # HOGEに値がある場合はwangが展開される。HOGEが空の場合は何もしない ${HOGE:=wang} # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangと展開し、さらにHOGEにwangを代入 ${HOGE:?wang} # HOGEに値がある場合はその値が展開される。HOGEが空の場合は、wangを標準エラー出力に書き出して exit 1
シェル変数の加工
指定位置で切り取り
x=abcdefg echo ${x:1:4} # => bcde
変数の値の先頭・末尾でマッチした部分を削除する
x=hogehogefugafuga echo ${x#hoge} # => hogefugafuga 前方一致削除 echo ${x%fuga} # => hogehogefuga 後方一致削除 echo ${x#h*e} # => hogefugafuga * はワイルドカード echo ${x##h*e} # => fugafuga ##はワイルドカードを前方最長一致にする echo ${x%f*a} # => hogehogefuga * はワイルドカード echo ${x%%f*a} # => hogehoge %%はワイルドカードを後方最長一致にする
文字列置換
echo ${var/hoge/HOGE} # => HOGEhogefugafuga echo ${var//hoge/HOGE} # => HOGEHOGEfugafuga グローバルマッチ
使用例:まとめてリネーム、拡張子まとめて変更
$ ls a.txt b.txt c.txt $ for i in `ls`; do mv $i ${i%.txt}.php; done $ls a.php b.php c.php
パラメータ展開
例
HOGE="hoge.fuga.piyo" echo ${HOGE#*.} # 最短前方一致削除 => fuga.piyo echo ${HOGE##*.} # 最長前方一致削除 => piyo echo ${HOGE%.*} # 最短後方一致削除 => hoge.fuga echo ${HOGE%%.*} # 最長後方一致削除 => hoge
ファイル名から拡張子を削除したい場合は ${filename%*.}
、拡張子だけ取り出したい場合は ${filename##*.}
何もしないコマンド
コロンは何もしないビルトインコマンド
: hogehoge # 何も起こらない
変数HOGEが空だった場合、nyanを代入する
: ${HOGE:=nyan}
変数HOGEが空だった場合、Error HOGE is emptyと出力して終了
: ${HOGE:?"Error HOGE is empty"}
可読性を考えると普通にifで書いた方がいいかも…
if [ -z "${HOGE}" ]; then echo "Error HOGE is empty" >&2 fi
標準入力がパイプ/リダイレクトされているか調べる
test -p /dev/stdin
/dev/stdinが名前付きパイプである(=stdinがパイプで接続されている)
test -t 0
ファイルディスクリプタ0番がterminalに紐づいている(=パイプやリダイレクトによるデータの入力がされていない)
if [ -t 0 ]; then # パイプやリダイレクトされてない # ./this_script elif [ -p /dev/stdin ]; then # シェルスクリプトが | で接続されている # cat hoge.txt | ./this_script else # 標準入力がリダイレクトされている # this_script < hoge.txt fi
シェルスクリプトの引数
$1
$2
で引数を取得できる。引数の数は $#
で取得できる。引数の数が2桁になる場合は ${10}
のように {} で括る。
# arg1.sh echo $# echo $1 echo $2
$ ./arg1.sh a b c d 4 a b
$@
または $*
でシェルスクリプトの引数全体を取得できる。
# arg2.sh echo $@ echo $*
$ ./arg2.sh 1 2 3 1 2 3 1 2 3
$@
と $*
はダブルクオートで括った場合の動作が異なる。“$@”
は“1” “2” “3”
、“$*”
は“1 2 3”
と展開される。
$*
は引数が一つの値にまとめられてしまう。ほとんどの場合、$@
の挙動が求められるので、$@
を使っておけば間違いない
# arg3.sh node -p -e 'process.argv.slice(1).join()' -- "$*" node -p -e 'process.argv.slice(1).length' -- "$*" node -p -e 'process.argv.slice(1).join()' -- "$@" node -p -e 'process.argv.slice(1).length' -- "$@"
$ ./arg3.sh 1 2 3 1 2 3 1 1,2,3 3
$*
をダブルクオートで括った場合、空白スペースで結合される。結合文字は環境変数IFSで変更できるが影響範囲が大きいのであまり変えない方が良いだろう。
# arg4.sh echo "$*" export IFS=":" echo "$*"
$ ./arg4.sh 1 2 3 1 2 3 1:2:3
これらの挙動はシェル関数の引数でも同様である。
ヒアドキュメント
変数に格納
val=$(cat << 'EOS' hoge fuga piyo EOS )
標準入力に渡す
while read -r line do echo $line done << 'EOS' hoge fuga piyo EOS )
一度変数を経由する。«<
はヒアストリングと呼ばれるものでPOSIXには定義されておらずshでは使用できない。bashやzshで使用できる
val=$(cat << 'EOS' hoge fuga piyo EOS ) while read -r line do echo $line done <<< $val
クオートの有無は変数展開するかしないか
val='test' while read -r line do echo $line done << "EOF" ${val} ${val} ${val} EOF
クオートありの場合は変数展開されずに以下のように出力される
${val} ${val} ${val}