関数の呼び出し履歴をトレースして記録するサンプルスクリプトfuncname.shです。
#!/usr/bin/bash
# Trace Log
XTRACEFILE=~/logs/`basename $0`.log
exec {fd}> $XTRACEFILE
if [ $? == 0 ]; then
BASH_XTRACEFD=$fd # set -xのトレース情報をファイルに出力
echo "Opened $fd"
set -ex # set -eはエラー発生時にexit
else
set -e # set -eはエラー発生時にexit
fi
set -o errtrace # ERRトラップを継承
trap catch ERR # ERRトラップの関数
trap finally EXIT # EXITトラップの関数
# Sub Function A
function func_A () {
# func_A:21 is called from main:76
echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
func_B B1 B2
return 0
}
# Sub Function B
function func_B () {
# func_B:29 is called from func_A:22
echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
func_C C1 C2 C3 C4
return 0
}
# Sub Function C
function func_C () {
# func_C:37 is called from func_B:30
echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
# エラーを発生させる(実行するコマンドが存在しない)
cmdcmd
return 0
}
# トレースの表示
function catch () {
echo "##### Call Trace #####" 1>&2
for ((i=0, k=0; i<${#BASH_LINENO[*]}; i++))
do
if [ $i == 0 ]; then
echo $((${#BASH_LINENO[*]}-$i)): ${BASH_SOURCE[$i]}: \
${FUNCNAME[$i]}: "Current" 1>&2
else
echo $((${#BASH_LINENO[*]}-$i)): ${BASH_SOURCE[$i]}: \
${FUNCNAME[$i]}: ${BASH_LINENO[$(($i-1))]} 1>&2
fi
# 引き数の表示
for ((l=0, j=${BASH_ARGC[$i]}-1; j>=0; l++, j--))
do
echo "ARGV["$l"]: " ${BASH_ARGV[$((k+j))]}
done
x=$((k+=${BASH_ARGC[$i]-0}))
done
return 0
}
# 終了処理を記述
function finally () {
echo "Exiting at ${FUNCNAME[1]}" # exitする関数名を出力
echo "Closing $fd"
exec {fd}>&- # トレースファイルのクローズ
}
# メインの処理
shopt -s extdebug
func_A A1 A2 A3
echo "#### `basename $0` end ####"
|
- スクリプトの最初にトレース情報を出力するファイル名をXTRACEFILEに設定しています。
- execコマンドによるリダイレクションでトレース情報を出力するファイルをオープンしています。
- set -xでスクリプトの実行をトレースするように設定しています。
- set -eでスクリプトの実行でエラーが発生した場合にexitするように設定しています。
- set -o errtraceでERRを継承できるように設定しています。
- trapコマンドでERR、およびEXITをトラップする関数を設定しています。
- func_A、func_B、func_C関数をネストして呼び出ししています。
- func_C関数内で存在しないコマンド"cmdcmd"を実行しようとしてエラーを発生させています。
- ERRが発生することで、catch関数を呼び出し、関数の呼び出し履歴と引き数を出力しています。
- エラーによりスクリプトの実行を強制終了させています。
- EXITシグナルが発生することで、finally関数を呼び出し、終了処理としてトレースファイルをクローズしています。
|
実行例:
$ ./funcname.sh
Opened 10
func_A:21 is called from main:76
func_B:29 is called from func_A:22
func_C:37 is called from func_B:30
./funcname.sh: 行 40: cmdcmd: コマンドが見つかりません
##### Call Trace #####
5: ./funcname.sh: catch: Current
4: ./funcname.sh: func_C: 40
ARGV[0]: C1
ARGV[1]: C2
ARGV[2]: C3
ARGV[3]: C4
3: ./funcname.sh: func_B: 30
ARGV[0]: B1
ARGV[1]: B2
2: ./funcname.sh: func_A: 22
ARGV[0]: A1
ARGV[1]: A2
ARGV[2]: A3
1: ./funcname.sh: main: 67
Exiting at func_C
Closing 10
$ cat ~/logs/funcname.sh.log
+ set -o errtrace
+ trap catch ERR
+ trap finally EXIT
+ shopt -s extdebug
+ func_A A1 A2 A3
+ echo func_A:21 is called from main:76
+ func_B B1 B2
+ echo func_B:29 is called from func_A:22
+ func_C C1 C2 C3 C4
+ echo func_C:37 is called from func_B:30
+ cmdcmd
++ catch
++ echo '##### Call Trace #####'
++ (( i=0, k=0 ))
++ (( i<5 ))
++ '[' 0 == 0 ']'
++ echo 5: ./funcname.sh: catch: Current
++ (( l=0, j=0-1 ))
++ (( j>=0 ))
++ x=0
++ (( i++ ))
++ (( i<5 ))
++ '[' 1 == 0 ']'
++ echo 4: ./funcname.sh: func_C: 40
++ (( l=0, j=4-1 ))
++ (( j>=0 ))
++ echo 'ARGV[0]: ' C1
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[1]: ' C2
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[2]: ' C3
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[3]: ' C4
++ (( l++, j-- ))
++ (( j>=0 ))
++ x=4
++ (( i++ ))
++ (( i<5 ))
++ '[' 2 == 0 ']'
++ echo 3: ./funcname.sh: func_B: 30
++ (( l=0, j=2-1 ))
++ (( j>=0 ))
++ echo 'ARGV[0]: ' B1
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[1]: ' B2
++ (( l++, j-- ))
++ (( j>=0 ))
++ x=6
++ (( i++ ))
++ (( i<5 ))
++ '[' 3 == 0 ']'
++ echo 2: ./funcname.sh: func_A: 22
++ (( l=0, j=3-1 ))
++ (( j>=0 ))
++ echo 'ARGV[0]: ' A1
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[1]: ' A2
++ (( l++, j-- ))
++ (( j>=0 ))
++ echo 'ARGV[2]: ' A3
++ (( l++, j-- ))
++ (( j>=0 ))
++ x=9
++ (( i++ ))
++ (( i<5 ))
++ '[' 4 == 0 ']'
++ echo 1: ./funcname.sh: main: 76
++ (( l=0, j=-1 ))
++ (( j>=0 ))
++ x=9
++ (( i++ ))
++ (( i<5 ))
++ return 0
+ finally
+ echo 'Exiting at func_C'
+ echo 'Closing 10'
+ exec
|
- func_A、func_B、func_Cを呼び出した時に、呼び出し元の関数名と行数が出力されています。
- func_C内でエラーが発生したことで関数の呼び出し履歴と引き数が出力されています。
- 終了処理が呼び出されてトレースファイルがクローズされています。
- トレースファイルにはスクリプトが実行したコマンドとその引き数が記録されています。
- set -eでスクリプトの実行でエラーが発生した場合にexitするように設定されているため、最後のechoコマンドは実行されていないことにご注意ください。
|
DEBUGのトラップを利用した関数の呼び出し履歴をトレースして出力するサンプルスクリプトdebug.shです。
実行例:
$ ./debug.sh
line 37: func_A
line 7: func_A
line 9: echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
func_A:9 is called from main:37
line 10: func_B
line 15: func_B
line 17: echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
func_B:17 is called from func_A:10
line 18: func_C
line 23: func_C
line 25: echo ${FUNCNAME[0]}:${LINENO} is called from ${FUNCNAME[1]}:${BASH_LINENO[0]}
func_C:25 is called from func_B:18
line 28: cmdcmd
./debug.sh: 行 28: cmdcmd: コマンドが見つかりません
line 29: return 0
line 19: return 0
line 11: return 0
line 38: echo "#### `basename $0` end ####"
#### line 38: basename $0
debug.sh end ####
|
- メインの処理からfunc_A、func_B、func_C関数をネストして呼び出ししています。
- 関数の呼び出し時(関数内の最初のコマンドの実行前)、コマンドの実行前にdebug関数が呼び出されて実行しようとしている行番号とコマンド文字列を標準出力に出力しています。
|
getoptsコマンドによるオプションと引き数の処理を行うサンプルスクリプトopt.shです。
#!/usr/bin/bash
while getopts ":ab:c:h" OPT
do
case "$OPT" in
"a" ) OPT_A="TRUE";;
"b" ) OPT_B="TRUE"; VAL_B="$OPTARG";;
"c" ) OPT_C="TRUE"; VAL_C="$OPTARG";;
"?" ) echo "無効なオプション $OPTARG が指定されました";
echo "Usage: `basename $0` [-a] [-b VAL] [-c VAL] [-h]" 1>&2;
exit 1;;
"h" ) echo "Usage: `basename $0` [-a] [-b VAL] [-c VAL] [-h]" 1>&2;
exit 1;;
esac
done
if [ "$OPT_A" = "TRUE" ]; then
echo '"-a"オプションが指定されました。'
fi
if [ "$OPT_B" = "TRUE" ]; then
echo '"-b"オプションが指定されました。'
echo "値は $VAL_B です。"
fi
if [ "$OPT_C" = "TRUE" ]; then
echo '"-c"オプションが指定されました。'
echo "値は $VAL_C です。"
fi
# getoptsで処理したオプションと引き数の数だけ位置パラメータをシフト
shift $(($OPTIND - 1))
|
- オプションabchがあり、そのうちbcには引き数があることを指定して1つずつ変数OPTに設定します。
先頭に:を記述しているため、無効な引き数の処理はスクリプト内で記述します。
- それぞれのオプションの処理を実施します。
オプションaは、変数OPT_Aの値を"TRUE"に設定します。
オプションbは、変数OPT_Bの値を"TRUE"に設定し、変数VAL_Bにその引き数を設定します。
オプションcは、変数OPT_Cの値を"TRUE"に設定し、変数VAL_Cにその引き数を設定します。
オプション?は、無効なオプションの指定を意味し、シェル変数OPTARGに指定されたオプションが設定されます。エラーメッセージとUsageを出力します。
オプションhは、Usageを出力します。
- 指定されたオプションとその引き数があれば出力します。
- getoptsで処理したオプションと引き数の数だけ位置パラメータをシフトします。
|
実行例:
$ ./opt.sh -abこんにちは
"-a"オプションが指定されました。
"-b"オプションが指定されました。
値は こんにちは です。
$ ./opt.sh -a -b こんにちは -c おはようございます
"-a"オプションが指定されました。
"-b"オプションが指定されました。
値は こんにちは です。
"-c"オプションが指定されました。
値は おはようございます です。
$ ./opt.sh -a -h
Usage: opt.sh [-a] [-b VAL] [-c VAL] [-h]
$ ./opt.sh -a -x
Usage: opt.sh [-a] [-b VAL] [-c VAL] [-h]
- getoptsは複数のオプションを空白文字で区切って指定することも続けて指定することもできます。
- この例のスクリプトではオプションhまたは、未知のオプションが指定された場合、Usageを出力するようにしています。
|
|
排他的ロックを利用して処理の実行をシリアライズ(同時に実行できないように排他制御)するサンプルスクリプトlock.shです。
#!/usr/bin/bash
lockfile=~/`basename $0`.lock
echo "Locking $lockfile"
exec {fd}> $lockfile # ロック制御ファイルをオープン
flock -x ${fd} # 排他的ロック
echo "Locked $lockfile"
sleep 15
flock -u ${fd} # ロック解除
echo "Unlocked $lockfile"
exec {fd}>&- # ロック制御ファイルをクローズ
|
|
実行例:
$ ./lock.sh
Locking /home/user1/lock.sh.lock
Locked /home/user1/lock.sh.lock
Unlocked /home/user1/lock.sh.lock
|
- 複数の端末を開いてこのスクリプトを実行してみてください。
あとから実行したスクリプトがflockコマンドの排他的ロックで待たされることを確認できます。
|
coprocコマンドを利用してコプロセスと標準入出力のパイプを接続するサンプルスクリプトsub.shです。
#!/usr/bin/bash
echo "### start ###"
while read line
do
if [ "$line" == "end" ]; then
exit
else
eval $line
fi
done
|
- 実行開始のメッセージ"### start ###"を標準出力に書き込みます。
- 標準入力から文字列を1行読み込みます。
- 読み込んだ文字列が"end"の場合、実行を終了します。
それ以外の文字列の場合、読み込んだ文字列をevalコマンドによって評価(実行)します。
- 上記2から3を繰り返します。
|
実行例:
$ coproc ./sub.sh
[1] 21059
$ read -u ${COPROC[0]} msg; echo $msg
### start ###
$ echo "date" >&${COPROC[1]}
$ read -u ${COPROC[0]} msg; echo $msg
2022年 2月 11日 金曜日 18:26:10 JST
$ echo "pwd" >&${COPROC[1]}
$ read -u ${COPROC[0]} msg; echo $msg
/home/user1
$ echo "end" >&${COPROC[1]}
[1]+ 終了 coproc COPROC ./sub.sh
|
|
ANSIのエスケープシーケンスにはカーソル位置を変更するCSI(Control Sequence Introducer)とSGR (Select Graphic Rendition)があります。
以下のサンプルはSGR (Select Graphic Rendition)を使って端末の文字色、背景色、点滅などを変化させるサンプルスクリプトsgr.shです。
#!/usr/bin/bash
for ((i=0; i<11; i++)) {
for ((j=0; j<10; j++)) {
v=$(($i * 10 + $j));
printf "\e[%dm%03d\e[0m " $v $v;
}
printf "\n";
}
|
$ ./sgr.sh
000
001
002
003
004
005
006
007
008
009 |
010
011
012
013
014
015
016
017
018
019 |
020
021
022
023
024
025
026
027
028
029 |
030
031
032
033
034
035
036
037
038
039 |
040
041
042
043
044
045
046
047
048
049 |
050
051
052
053
054
055
056
057
058
059 |
060
061
062
063
064
065
066
067
068
069 |
070
071
072
073
074
075
076
077
078
079 |
080
081
082
083
084
085
086
087
088
089 |
090
091
092
093
094
095
096
097
038
039 |
100
101
102
103
104
105
106
107
108
109 |
|
SGR (Select Graphic Rendition)の概要を下表に示します。
エスケープシーケンス | 名称 | 説明 |
\e[0m \e[m | リセット | 全ての設定を初期化します。 |
\e[1m | ボールド | フォントが対応していれば太字にします。 |
\e[2m | 細字 | フォントが対応していれば細い字体にします。 |
\e[3m | 斜体 | 文字を斜体にします。 |
\e[4m | 下線 | 文字に下線を付加します。 |
\e[5m | 点滅 | 1分間に150回未満で点滅します。 |
\e[6m | 高速点滅 | 1分間に150回以上で点滅します。(環境依存) |
\e[7m | 反転 | 前景色と背景色を反転します。 |
\e[8m | コンシール | 文字を見えないようにします。 コピー&ペーストすると文字がコピーされます。 |
\e[9m | クロスアウト | 文字に取り消し線を付加します。(環境依存) |
\e[21m | ボールドの解除 | 太字を解除します。 |
\e[22m | 細字の解除 | 細い字体を解除します。 |
\e[23m | 斜体の解除 | 斜体を解除します。 |
\e[24m | 下線の解除 | 下線を解除します。 |
\e[25m | 点滅の解除 | 点滅を解除します。 |
\e[26m | 高速点滅の解除 | 高速点滅を解除します。 |
\e[27m | 反転の解除 | 前景色と背景色の反転を解除します。 |
\e[28m | コンシールの解除 | コンシールを解除します。 |
\e[29m | クロスアウトの解除 | 取り消し線を解除します。 |
\e[30m | 前景色: 黒 | 前景色を黒に変更します。 |
\e[31m | 前景色: 赤 | 前景色を赤に変更します。 |
\e[32m | 前景色: 緑 | 前景色を緑に変更します。 |
\e[33m | 前景色: 黄 | 前景色を黄に変更します。 |
\e[34m | 前景色: 青 | 前景色を青に変更します。 |
\e[35m | 前景色: マゼンタ | 前景色をマゼンタに変更します。 |
\e[36m | 前景色: シアン | 前景色をシアンに変更します。 |
\e[37m | 前景色: 白 | 前景色を白に変更します。 |
\e[38m | 前景色: 拡張指定 | RGB値または、0から255までのカラーインデックス値を指定して前景色を変更します。 指定形式: \e[38:R:G:Bm または、\e[38:05:xm :の代わりに;を指定することもできます。 |
\e[39m | 前景色: デフォルト | 前景色をデフォルトに戻します。 |
\e[40m | 背景色: 黒 | 背景色を黒に変更します。 |
\e[41m | 背景色: 赤 | 背景色を赤に変更します。 |
\e[42m | 背景色: 緑 | 背景色を緑に変更します。 |
\e[43m | 背景色: 黄 | 背景色を黄に変更します。 |
\e[44m | 背景色: 青 | 背景色を青に変更します。 |
\e[45m | 背景色: マゼンタ | 背景色をマゼンタに変更します。 |
\e[46m | 背景色: シアン | 背景色をシアンに変更します。 |
\e[47m | 背景色: 白 | 背景色を白に変更します。 |
\e[48m | 背景色: 拡張指定 | RGB値または、0から255までのカラーインデックス値を指定して背景色を変更します。 指定形式: \e[48:R:G:Bm または、\e[48:05:xm :の代わりに;を指定することもできます。 |
\e[49m | 背景色: デフォルト | 背景色をデフォルトに戻します。 |
\e[90m | 前景色: 暗い灰 | 前景色を暗い灰色(色番号8)に変更します。 |
\e[91m | 前景色: 明るい赤 | 前景色を明るい赤(色番号9)に変更します。 |
\e[92m | 前景色: 明るい緑 | 前景色を明るい緑(色番号10)に変更します。 |
\e[93m | 前景色: 明るい黄 | 前景色を明るい黄(色番号11)に変更します。 |
\e[94m | 前景色: 明るい青 | 前景色を明るい青(色番号12)に変更します。 |
\e[95m | 前景色: 明るいマゼンタ | 前景色を明るいマゼンタ(色番号13)に変更します。 |
\e[96m | 前景色: 明るいシアン | 前景色を明るいシアン(色番号14)に変更します。 |
\e[97m | 前景色: 明るい白 | 前景色を明るい白(色番号15)に変更します。 |
\e[100m | 背景色: 暗い灰 | 背景色を暗い灰色(色番号8)に変更します。 |
\e[101m | 背景色: 明るい赤 | 背景色を明るい赤(色番号9)に変更します。 |
\e[102m | 背景色: 明るい緑 | 背景色を明るい緑(色番号10)に変更します。 |
\e[103m | 背景色: 明るい黄 | 背景色を明るい黄(色番号11)に変更します。 |
\e[104m | 背景色: 明るい青 | 背景色を明るい青(色番号12)に変更します。 |
\e[105m | 背景色: 明るいマゼンタ | 背景色を明るいマゼンタ(色番号13)に変更します。 |
\e[106m | 背景色: 明るいシアン | 背景色を明るいシアン(色番号14)に変更します。 |
\e[107m | 背景色: 明るい白 | 背景色を明るい白(色番号15)に変更します。 |
\e[1
;32m のようにセミコロンで区切って2つ以上のエスケープシーケンスを一度に指定することも可能です。