例子

例子

我想要一個 bash 腳本,它允許我呼叫一個函數來查看文件中是否包含某些數據,如果沒有失敗,主腳本就會失敗,類似於下面的內容,它被簡化為保留這一點。

這不起作用(當子 shell 失敗時不會退出主腳本)。

我怎麼能寫這個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

退出子 shell 不會像您經歷的那樣退出主腳本。

我想到了 3 個(半)解決方案:

  1. 使用set -e這樣任何(“未經測試的”)失敗的命令(或子shell)將立即退出主腳本(這可能是矯枉過正或導致其他問題),
  2. 從函數發送一個信號並用 a 捕獲它trap
  3. || exit $?每次使用後VALUEn=$(...)都像這樣VALUE=$(require_line "myKey") || exit $?
  4. 使用 . 將 (3.) 與一個(不太優雅的)循環結合eval

第三個並不完全“需要每個周圍都有一個 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所有測試放在一起。一旦其中任何一個失敗,整個測試就會失敗。

假設您確實需要實際的匹配值,那麼冗長的方法是:

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."
  ""

最後,如果您確實只需要驗證所有鍵是否都存在,而不需要知道匹配值是什麼,則上面的面向數組的程式碼可以簡化為簡單地循環,直到找到所有鍵,或者在第一個鍵處中止被發現失蹤了。

相關內容