我想要一個 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 個(半)解決方案:
- 使用
set -e
這樣任何(“未經測試的”)失敗的命令(或子shell)將立即退出主腳本(這可能是矯枉過正或導致其他問題), - 從函數發送一個信號並用 a 捕獲它
trap
|| exit $?
每次使用後VALUEn=$(...)
都像這樣VALUE=$(require_line "myKey") || exit $?
,- 使用 . 將 (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."
""
最後,如果您確實只需要驗證所有鍵是否都存在,而不需要知道匹配值是什麼,則上面的面向數組的程式碼可以簡化為簡單地循環,直到找到所有鍵,或者在第一個鍵處中止被發現失蹤了。