例

ファイルに何らかのデータが含まれているかどうかを確認する関数を呼び出して、含まれていない場合はメイン スクリプトを失敗させる、次のような、要点を押さえて簡略化した 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つ(半)の解決策を考えています:

  1. 使用してset -e、(「テストされていない」)失敗したコマンド(またはサブシェル)がすぐにメインスクリプトを終了するようにします(これはやり過ぎか、他のトラブルを引き起こす可能性があります)。
  2. 関数からシグナルを送信し、それをキャッチするtrap
  3. || exit $?それぞれをVALUEn=$(...)このように使用しますVALUE=$(require_line "myKey") || exit $?
  4. (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、ファイル名をパラメータとして受け入れるように変更できます。$2local 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."
  ""

最後に、一致する値が何であるかを知らなくても、すべてのキーが存在することを確認するだけでよい場合は、上記の配列指向のコードを簡素化して、すべてのキーが見つかるまで単純にループするか、欠落している最初のキーが見つかった時点で中止することができます。

関連情報