我想要一个 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."
""
最后,如果您确实只需要验证所有键是否都存在,而不需要知道匹配值是什么,则上面的面向数组的代码可以简化为简单地循环,直到找到所有键,或者在第一个键处中止被发现失踪了。