bash:シェルスクリプト †
ShellCheck: シェルスクリプトの分析 †
拡張子でファイル数を集計 †
FUNCNAME: 関数名の取得 †
- ${FUNCNAME[0]}: 実行中の関数名
- ${FUNCNAME[1]}: 呼び出し元の関数名
trap: シグナルに応じた処理を実行 †
awkでの変換 †
"[[" と "[" の違い †
- bashの場合 "[]"も"[[]]"もビルドインコマンド
- testと"[]" は同じ
- "[[]]" の方が機能強化されているので、基本これを使う
jsonに変数を埋め込む †
- printfが使えるので
--ip-permissions "$(printf '[{"IpProtocol": "tcp", "FromPort": 24224, "ToPort": 24224, "IpRanges": [{"CidrIp": "%s", "Description": "%s"}]}]' "${AWS_EC2_DEVLOG_PUBLIC_CIDR}" "${AWS_EC2_DEVLOG_DESCRIPTION}")"
TSV: タブでの分割 †
tsvで文字列を分割して取り出したいとき
- read
- read -a var で配列として読める
- IFS=$'\t' でタブでの分割はできる。連続タブ、空のカラムが省略されてしまう
- 行末に改行が無いと無視されるため、「 || [ -n "${line}" ]」を追加
- readを使って複数の変数を指定できる。値の無いカラムが省略される
cat "data.tsv" | while IFS=$'\t' read a b c d e || [ -n "${a}" ]; do
echo "a: ${a}, b: ${b}, c: ${c}, d: ${d}, e: ${e}"
done
入力プロンプト †
URIの分解 †
tmpwatchで一定時間以上経過したファイルを削除 †
コマンドを探す(which, hash, type) †
- whichはコマンド
- hash, typeはbash組み込みコマンド
0埋め †
文法(syntax)チェック †
- bash, shも "-n"でチェックできる
bash -n example.sh
バッググラウトプロセスの終了待ちと終了ステータス †
- 「$!」でバックグラウンドプロセスのPIDを取得できる
- 「wait PID」 でプロセスの終了まで待ち、「$?」で終了ステータスを取得
- 子プロセスから「$PPID」親PIDを取得できるが、バックグラウンドだと全て「1」になる。
- サンプル:待ち時間と終了コードを同じにしている。
waiting-for-parallel-tasks.zip
./parent.sh
parent.sh::PID: 6307
run: 1, wait: 4, pid: 6310
run: 2, wait: 9, pid: 6311
run: 3, wait: 3, pid: 6312
wait child pid: 6310, exit: 4
wait child pid: 6311, exit: 9
wait child pid: 6312, exit: 3
パスからファイル名/拡張子等の切り出し †
tmp_path="/etc/httpd/conf/httpd.conf"
tmp_file=${tmp_path##*/} # httpd.conf
echo $tmp_file # httpd.conf
echo ${tmp_file%.*} # httpd
echo ${tmp_path%/*} # /etc/httpd/conf
echo ${tmp_path##*.} # conf
デーモンを作る †
select: 選択式メニュー †
選択式メニューを表示し、数字を入力するタイプ。
expect: 対話的なコマンド(ssh, telnet, ftp等)を自動実行 †
タイムアウト処理 †
浮動小数点演算 †
パイプで複数繋げたコマンドの終了ステータスを取得 †
cron実行時に多重起動の防止 †
- PIDのロックファイルを作って防止する方法
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
readonly SCRIPT_NAME=$(basename $0)
readonly LOCK_FILE=/tmp/${SCRIPT_NAME}.lock
lock() {
if [ -f $LOCK_FILE ]; then
PID=$(cat $LOCK_FILE)
if (ps -e | grep -P "^\s*$PID" >/dev/null); then
echo $SCRIPT_NAME' is already running.' >&2
exit 1
fi
fi
echo $$ > $LOCK_FILE
}
unlock() {
rm -f $LOCK_FILE
}
main() {
lock
for i in $(seq 1 7); do
echo "PID=$$, seq:$i"
sleep 10
done
unlock
}
main
- サンプルとしてよくある、pgrepの方はcronからの実行時に常に二重起動と判定されるため使えない。
日付の範囲指定 †
$()演算子 †
netstatで接続中のコネクション数を数える †
# get_netstat_established_count
# @param int port : tcp port
# @return int count
get_netstat_established_count() {
local port=$1
local established_count=`netstat -nt | grep ":${port} " | grep "ESTABLISHED" | wc -l`
echo $established_count
}
エラー処理をシンプルに書く †
バッチ処理 †
bashのバージョン情報の取得 †
bash --version | perl -ane 'if($.==1 && /version (\d+)\./){ print $1;}'
# 3
a-z等の文字列が欲しい †
var=`echo {0..10}` # 0 1 2 3 4 5 6 7 8 9 10
var=`echo {a..z}` # a b c d e f g h i j k l m n o p q r s t u v w x y z
空ディレクトリチェック †
if [ -z "$(ls -A ./dirname/)" ]; then
echo "empty"
else
echo "not empty"
fi
連想配列/ハッシュ †
- v4.0で新しく追加された。v3系ではちょっとわかりにくい
- bash v4.x
#/bin/bash
# bash v4. "-A":連想配列を宣言 / "-a":数値配列
unset ary
declare -A ary
ary=(
["key1"]="value1"
["key2"]="value2"
["key3"]="value3"
)
# 要素の追加
ary+=(
["key4"]="value4"
)
# 要素数
echo num: ${#ary[@]} # 4
# valueだけ欲しい
for i in "${ary[@]}"; do
echo $i
done
# key, valueも欲しい
for i in "${!ary[@]}"; do
echo $i, ${ary[$i]}
done
ヒアドキュメント †
まとめて標準入力、標準エラー、ファイル等に出せる
- 「cat << EOS」のままだと、変数展開される。「cat << 'EOS'」にすると変数展開されない。
テンポラリファイル名 †
- $$はPIDなので一意。同時実行される可能性のある場合は便利
TMP_FILE=$$.tmp
bashで秒、ミリ秒の取得 †
- 秒:$SECONDS
- ミリ秒:printf '%.3f' `date '+%s.%N'`
#!/bin/bash
loop(){
for i in {1..10}; do
sleep 1
done
}
START_SEC=$SECONDS
START_MSEC=`printf '%.3f' \`date '+%s.%N'\``
loop
END_SEC=$SECONDS
END_MSEC=`printf '%.3f' \`date '+%s.%N'\``
echo $(($END_SEC - $START_SEC))" s."
echo `echo "scale=3;($END_MSEC - $START_MSEC)" | bc`" s."
bash最短関数 †
bashで関数名だけ用意したい事があるが、空の関数は書くとエラーになる。
- bashにコロン":"が何もしない組み込み関数として用意されている
dummy(){
:
}
dummy
タイムゾーンを日本に変更 †
パイプの先は別シェルなので変数取得やexitが無効 †
- シェルスクリプト内でパイプは別シェルが起動するためループ内で以下は無効になる
文字色の変更 †
IPアドレスの取得 †
関数 †
sudo実行したユーザ、グループ取得 †
HOME_USER=${SUDO_USER:-"$USER"}
HOME_GROUP=`id $HOME_USER -g -n`
ファイル名の一括変更 †
JSONパーサー †
算術式 †
ランダムな数値と文字列 †
- $RANDOM: 0-32767 の値を返す
- /dev/urandom から任意の長さの乱数文字列を取得
- /dev/urandom を使って、指定範囲の乱数を取り出すスクリプト。/dev/urandom は非常に長いため遅い。mkpasswdも遅い
#/bin/bash
# rand
# @param int min : default 0
# @param int max : default 32767, max 32767
# @return int :
rand()
{
local min=0
local max=32767
if [ $1 ]; then
min=$1
fi
if [ $2 ]; then
max=$2
fi
echo `echo $(( $min + $RANDOM % ( $max - $min ) ))`
}
# rand_str
# @param int len : default 8
# @return int :
rand_str()
{
local len=8
if [ $1 ]; then
len=$1
fi
echo `</dev/urandom tr -dc A-Za-z0-9 | head -c $len`
}
# rand_str_fast
# @param int len : default 8, max 1000
# @return int :
RANDSTRING=`</dev/urandom tr -dc A-Za-z0-9 | head -c 1000` # 1000文字まで作成しておく
rand_str_fast()
{
local len=8
if [ $1 ]; then
len=$1
fi
local max=`echo $(( 1000 - $len ))`
local start=`rand 1 $max`
local end=`echo $(( $start + $len -1 ))`
echo `echo -n "$RANDSTRING" | cut -c$start-$end`
}
for i in {1..10}; do
echo `rand 1 10`,`rand_str_fast 8`
done
- 実行結果
2,8NOLYUIK
3,j7y74euW
3,5XG20fJ1
3,Cn5NGZwM
4,MKwbgRJO
7,BpeOCLZM
9,O9zbPunH
8,0j3cSLWd
6,yoExCkEX
8,OHEyoqeJ
指定回数ループ †
OSの判別 †
複数行コメント †
バージョン番号の比較 †
正規表現 †
- Stray Penguin - Linux Memo (BASH)
- `[[`、比較演算子`=~` を使うと、右辺は拡張正規表現とみなされる。
- 右辺はシングル/ダブルコーテーションは動作しない
- 右辺にスペースが入るとうまく動作しないので、変数に入れる
- 左辺はダブルコーテーションで囲ってもOK
- マッチ部分は BASH_REMATCH という配列に入る。[0]が全体。[1]〜が()で括った部分
デバッグ †
# -x:実行されたコマンドを表示する。変数も展開される。
bash -x test.sh
# -v:実行されるコマンドを表示する。変数は展開されない。
bash -v test.sh
エラーの際に処理を止める/未定義変数を参照した時にエラーとする †
- -x: コマンドの表示。変数展開される。デバッグ時に便利
- -v: コマンドの表示。変数展開しない。
- -n: 構文チェック。実行しない。
文字列操作 †
bash変数 †
- local変数。関数内のみ有効なスコープ
test() {
local var1;
}
bash特殊変数 †
位置パラメータ
$0 シェルスクリプト名を表示
$1〜$9 シェルスクリプトのオプションを表示(数値は位置を現す。10以降は${10}、${11})
引数(オプション)関連
$# 引数の数を表示
$@ $0以外の全ての引数を表示("$@"にした場合は "$1" "$2" "…" のように展開)
$* $0以外の全ての引数を表示("$*"にした場合は "$1 $2 ..." のように展開)
$- シェルを起動する際に指定されていたオプションを表示
PID(プロセスID)関連
$? 直前のコマンドの終了ステータスを表示
0 : 正常
!0 : 失敗
2 : 誤った使用
126 : 実行できない
127 : 存在しないコマンドの実行
$$ 現在のシェルのプロセスIDを表示
$! 最期にバックグラウンドで実行されたコマンドのプロセスIDを表示
$- 現在のオプションフラグ
その他、シェルスクリプトで使いそうなもの
IFS フィールドの区切り文字
PWD カレントディレクトリを表示
PPID 親プロセスのプロセスIDを表示
HOME ホームディレクトリを表示
$SECONDS 秒
$RANDOM ランダム:0-32767
$LINENO 行番号
${PIPESTATUS[@]} パイプで連結された各コマンドの終了ステータスの配列
区切り文字変更 IFS変数 †
- デフォルト: 半角スペース、タブ、改行
IFS=$' \t\n'
- タブのみ: tsvの読み込み等
IFS=$'\t'
- カンマ区切り: csvの読み込み等。
#!/bin/bash
IFS=',' read a b c d <<< 'a,b,,d'
echo -e "a: $a\nb: $b\nc: $c\nd: $d"
変数にデフォルト値をセット †
NAME=$1
NAME2=${2:-"DEFAULT"}
# NAMEが未定議 or nullの時、デフォルト値を指定。NAMEに代入されない。
echo ${NAME:-"DEFAULT"}
echo "NAME:- $NAME"
# NAMEが未定議 or nullの時、デフォルト値を指定。NAMEに代入される。
echo ${NAME:="DEFAULT"}
echo "NAME:= $NAME"
# NAMEが未定議 or nullの時、stderrに出力し、終了。
echo ${NAME:?"Error"}
# NAMEが定義されている場合、変数に代入。空文字、未設定は空文字
echo ${NAME:+"word"}
echo "NAME:+ $NAME"
高度 †
tputでメニュー †
- 10 Tools To Add Some Spice To Your UNIX Shell Scripts
#!/bin/bash
# clear the screen
tput clear
# Move cursor to screen location X,Y (top left is 0,0)
tput cup 3 15
# Set a foreground colour using ANSI escape
tput setaf 3
echo "XYX Corp LTD."
tput sgr0
tput cup 5 17
# Set reverse video mode
tput rev
echo "M A I N - M E N U"
tput sgr0
tput cup 7 15
echo "1. User Management"
tput cup 8 15
echo "2. Service Management"
tput cup 9 15
echo "3. Process Management"
tput cup 10 15
echo "4. Backup"
# Set bold mode
tput bold
tput cup 12 15
read -p "Enter your choice [1-4] " choice
tput clear
tput sgr0
tput rc
getopt,getopts †
- getopt : 外部コマンド。ロングオプション(--)も対応。後ろにオプションがあっても大丈夫。MacOSX非対応
- getopts : bashの内部関数。getoptよりすっきり書ける。オプションが最後にあるような構文は非対応。
- getopts版。引数を処理する - UNIX & Linux コマンド・シェルスクリプト リファレンス
#!/bin/bash
export LANG=C
CMDNAME=`basename $0`
usage_exit() {
echo "Usage: $CMDNAME [-a] [-b VALUE] [-c VALUE]" 1>&2
exit 1
}
echo "[DEBUG GLOBAL] \$# : $#"
echo "[DEBUG GLOBAL] \$@ : $@"
echo_debug() {
echo "[DEBUG LOCAL] \$FLG_A : $FLG_A"
echo "[DEBUG LOCAL] \$FLG_B : $FLG_B / $VALUE_B"
echo "[DEBUG LOCAL] \$FLG_C : $FLG_C / $VALUE_C"
}
while getopts "a,b:,c:" OPT
do
case $OPT in
"a" )
FLG_A="TRUE"
;;
"b" )
FLG_B="TRUE"
VALUE_B="$OPTARG"
;;
"c" )
FLG_C="TRUE"
VALUE_C="$OPTARG"
;;
* )
usage_exit
;;
esac
done
# オプション部分を切り捨てる
shift `expr $OPTIND - 1`
echo "[DEBUG GLOBAL] \$1 : $1"
echo "[DEBUG GLOBAL] \$2 : $2"
echo_debug
- getopt版。シェルスクリプトを書くときに地味に便利なgetopt - Kirikaの非人間的生活
#!/bin/bash
export LANG=C
CMDNAME=`basename $0`
usage_exit() {
echo >&2 "Usage: $CMDNAME [-h|--hostname hostname] [-u|--hostuser username] [-s] [--longonly]"
exit 1
}
# check command args.
if [ $# -lt 1 ]; then
usage_exit
fi
echo "[DEBUG GLOBAL] \$# : $#"
echo "[DEBUG GLOBAL] \$@ : $@"
echo_debug() {
echo "[DEBUG LOCAL] hostname : $hostname"
echo "[DEBUG LOCAL] hostuser : $hostuser"
echo "[DEBUG LOCAL] shortonly : $shortonly"
echo "[DEBUG LOCAL] longonly : $longonly"
}
OPT=`getopt -q -o "h:,u:,s" -l "hostname:,hostuser:,longonly" -- "$@"`
if [ $? != 0 ]; then
usage_exit
fi
eval set -- "$OPT"
while [ -n "$1" ]; do
case $1 in
-h|--hostname) hostname=$2; shift 2;;
-u|--hostuser) hostuser=$2; shift 2;;
-s) shortonly=1; shift 1;;
--longonly) longonly=1; shift 1;;
--) shift; break;;
*) echo "Unknown option($1) used."; exit 1;;
esac
done
echo "[DEBUG GLOBAL] \$1 : $1"
echo "[DEBUG GLOBAL] \$2 : $2"
echo_debug
rootやsudoの判別 †
全引数をダブルクォート付きで渡す †
- ダブルクォートで囲んだ場合の挙動が違う
- "$*"
"$1 $2 ..."
- "$@"
"$1" "$2" ...
- よって、別コマンドへスペースも含んだコマンドを渡すには
#/bin/bash
hoge.php "$@"
配列変数を使う †
- 要素数(添え字が連続している場合のみ。不連続の場合は意図した結果にならない)
${#ARRAY[*]}
シェルスクリプトでカレントディレクトリの取得 †
CUR_DIR=$(cd $(dirname $0);pwd)
シェルスクリプトの実行ログを記録 †
搭載メモリをMBで取得 †
MEM_TOTAL=`perl -e '$m=readpipe("free -m");$m=~/Mem:\s+(\d+)/i;print int($1)'`
MEM_FREE=`perl -e '$m=readpipe("free -m");$m=~/cache:\s+(\d+)\s+(\d+)/i;print int($2)'`
日付形式をチェック †
ホスト名にアンダーバーが含まれていたらエラー †
match=`echo "$1" | egrep '^([0-9a-z\-]+)$' -`
if [ $? != 0 ]; then
echo "Error : Invalid character. allow [0-9, a-z, -]"
exit
fi