例子

例子

我想要一个 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."
  ""

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

相关内容