ファイルに何らかのデータが含まれているかどうかを確認する関数を呼び出して、含まれていない場合はメイン スクリプトを失敗させる、次のような、要点を押さえて簡略化した bash スクリプトを作成したいと思います。
これは機能しません (サブシェルが失敗してもメイン スクリプトは終了しません)。
require_line
次のようにファイルに20個以上の関数を記述するにはどうすればよいでしょうか。
VALUE1=$(require_line "myKey1")
VALUE2=$(require_line "myKey2")
...
それぞれをifで囲む必要はありませんか?
#!/bin/bash
set -eo pipefail
VALUE=$(require_line "myKey")
require_line(){
local KEY=$1
local DATA=$(cat /tmp/myfile)
local INFO=$(echo "$DATA" | grep "$KEY")
if [ ! -z "$INFO" ]
then
echo "Key not found in $DATA, key: $KEY"
exit 1;
fi
echo "$INFO"
}
答え1
サブシェルを終了しても、メイン スクリプトは終了しません。
私は3つ(半)の解決策を考えています:
- 使用して
set -e
、(「テストされていない」)失敗したコマンド(またはサブシェル)がすぐにメインスクリプトを終了するようにします(これはやり過ぎか、他のトラブルを引き起こす可能性があります)。 - 関数からシグナルを送信し、それをキャッチする
trap
|| exit $?
それぞれをVALUEn=$(...)
このように使用しますVALUE=$(require_line "myKey") || exit $?
。- (3.) を (それほどエレガントではない) ループと組み合わせます
eval
。
3 番目は、厳密には「それぞれを if で囲む必要がある」わけではなく、私見では、それでもかなりコンパクトな構文になります。
ところで、この行
echo "Key not found in $DATA, key: $KEY"
$VALUEn
...この直後にスクリプト全体を終了した場合、文は表示されない変数に格納されるため、実際には役に立ちません。
次のように印刷することをお勧めしますstderr
:
echo "my error" 1>&2
例
ソリューション1の例
#!/bin/sh
set -e
myfunc(){
echo $1
if [ "$1" != "OK" ] ; then exit 1 ; fi
}
VALUE1=$(myfunc "OK")
echo $VALUE1
VALUE2=$(myfunc "NO WAY")
echo $VALUE2
echo "main script did not exit"
$ ./test.sh
OK
zsh: exit 1 ./test.sh
set -e
しかし、最初から削除すると、次のようになります。
$ ./test.sh
OK
NO WAY
main script did not exit
ソリューション2の例
#!/bin/sh
trap "exit $?" USR1
myfunc(){
echo $1
if [ "$1" != "OK" ] ; then kill -USR1 $$ ; fi
}
VALUE1=$(myfunc "OK")
echo $VALUE1
VALUE2=$(myfunc "NO WAY")
echo $VALUE2
echo "main script did not exit"
$ ./test.sh
OK
ソリューション3の例
#!/bin/sh
myfunc(){
echo $1
if [ "$1" != "OK" ] ; then exit 1 ; fi
}
VALUE1=$(myfunc "OK") || exit $?
echo $VALUE1
VALUE2=$(myfunc "NO WAY") || exit $?
echo $VALUE2
echo "main script did not exit"
$ ./test.sh
OK
zsh: exit 1 ./test.sh
ソリューション4の例
#!/bin/sh
myfunc(){
echo $1
if [ "$1" != "OK" ] ; then exit 1 ; fi
}
I=1
for key in "OK" "OK" "NO WAY": ; do
eval "VALUE$I=\$(myfunc \"$key\")" || exit $?
eval "echo \$VALUE$I"
I=$(($I+1))
done
echo "main script did not exit"
$ ./test.sh
OK
OK
zsh: exit 1 ./test.sh
答え2
require_line
役立つようにスクリプトを少し再構成してみましょう。
まず、役に立たない を取り除くことができますcat | grep
。次に、
grep
の固有の動作を使用して の検索の成功または失敗を示すこと ができKEY
、また、見つかった場合は のキーを に出力することもできますstdout
。
require_line(){
local KEY="$1"
local FILE="/tmp/myfile"
if grep "$KEY" "$FILE"
then
return 0
else
printf 'Key not found in:\n\n"%s"\n\nKey: "%s"\n' "$(cat "$FILE")" "$KEY" >&2
return 1
fi
}
これは の組み込み動作を活用しますgrep
。キーが見つかった場合、grep
一致する行を出力し、成功を返します。それ以外の場合はelse
分岐が実行され、キーが見つからなかったことを示すメッセージが出力されます。さらに、 が失敗した場合grep
、エラー メッセージが に出力されるstderr
ため、エラー メッセージが で見つかった有効な一致と間違われることはありません$FILE
。
さらに、読み取る行を変更し、呼び出すたびに目的のファイル名を渡すことでrequire_line
、ファイル名をパラメータとして受け入れるように変更できます。$2
local FILE="$2"
require_line
さて、それでは....
KEYn の各 VALUEn を本当に保存する必要がありますか、それともすべてが存在していることを確認するだけでよいのでしょうか?
require_line
成功または失敗の値を明確に返す関数ができたので、AND
すべてのテストをまとめて実行できます。テストの 1 つでも失敗すると、テスト全体が失敗します。
実際の一致値が必要であると仮定すると、それを実行する長い方法は次のとおりです。
if value1=$(require_line "key1") &&
value2=$(require_line "key2") &&
value3=$(require_line "key3")
then
printf "%s\n" "$value1" "$value2" "$value3"
else
printf "One or more keys failed.\n" >&2
fi
チェックするキーが多数ある場合は面倒になります。配列を使用する方がよい場合があります。
#!/usr/bin/env bash
require_line(){
local KEY="$1"
local FILE="/tmp/myfile" # or perhaps "$2"
if grep "$KEY" "$FILE"
then
return 0
else
printf 'Key not found in:\n\n"%s"\n\nKey: "%s"\n' "$(cat "$FILE")" "$KEY" >&2
return 1
fi
}
declare keys=("this" "is" "a" "test" "\." "keyN")
N=${#keys[@]}
declare values=()
j=0
while [ $j -lt $N ] && values[$j]="$(require_line "${keys[j]}")"
do
j=$(($j+1))
done
if [ $j -lt $N ]
then
printf 'error: found only %d keys out of %d:\n' $j $N
printf ' "%s"\n' "${values[@]}"
fi
サンプルデータを使用してコードを実行します。
$ cat /tmp/myfile
this is a test.
$ ./test.sh
Key not found in:
"this is a test."
Key: "keyN"
error: found only 5 keys out of 6:
"this is a test."
"this is a test."
"this is a test."
"this is a test."
"this is a test."
""
最後に、一致する値が何であるかを知らなくても、すべてのキーが存在することを確認するだけでよい場合は、上記の配列指向のコードを簡素化して、すべてのキーが見つかるまで単純にループするか、欠落している最初のキーが見つかった時点で中止することができます。