如何在“&&”或“||”之后对命令进行分组分支而不调用子shell变量限制?

如何在“&&”或“||”之后对命令进行分组分支而不调用子shell变量限制?

该脚本只是尝试将文件复制foobar/,如果不成功,则再重试 2 次。

编辑:事实证明,第二个示例有效而第一个示例无效的原因是,在or分支(...)之后使用 运行一组命令被视为子 shell,并且不会影响外部变量。&&||

所以修改后的问题:如何将一些命令(如第一个示例)分组,但不使用子 shell 来执行此操作?

编辑2:答案是替换( ... ){ ...; }

#!/usr/bin/env bash
#

# Be safe
set -euo pipefail

# Init variables
__file="foo"
output_dir="bar"
logfile="test.log"
errorlevel=8
count=0

# Try three times to copy a file
until (( errorlevel == 0 || count == 3 )); do

  cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile" \
    && (errorlevel=$?; echo "zero return, $errorlevel") \
    || (errorlevel=$?; echo "non-zero return, $errorlevel")

  echo "errorlevel $errorlevel"

  (( ++count ))

  echo "count $count"

done

unset errorlevel
unset count

问题:

当复制失败或成功时,||&&分支会正确捕获并将变量分别设置errorlevel10,如echo $errorlevel分支中包含的 所证明的那样。

但下一个命令(也是回显)返回初始变量值8!我不明白为什么会发生这种情况。

一些注意事项:

  1. 我明确使用&&and||是因为我必须set -e使这个脚本尽可能安全(这实际上是 1500 行脚本中的一个小例程)。如果我不使用||捕获非零返回,则脚本退出。或者,我可以set +e执行复制例程,而不必使用&&and ||(通过捕获错误级别一次),然后set -e再次使用,但我宁愿不这样做,因为它与我的脚本其余部分的风格不匹配。
  2. 我用来set -o pipefail在管道中传递一个非零错误级别,这样我tee -a "$logfile"就不会总是覆盖我的 exit errorlevel
  3. 我使用++count而不是count++因为前者返回零错误级别,而后者返回非零错误级别(并且(( count++ )) || true由于set -e注释#1中的解释,我将不得不做一个丑陋的事情)。

我现在已经通过将errorlevel变量显式设置为1or来解决这个问题0,因为我实际上并不关心非零errorlevel是什么,但我仍然很想知道为什么上面的方法没有按我的预期工作。

这是解决方法:

#!/usr/bin/env bash
#

set -euo pipefail

__file="foo"
output_dir="bar"
logfile="test.log"

errorlevel=1
count=0
until (( errorlevel == 0 || count == 5 )); do
  cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile" \
    && errorlevel=0 \
    || errorlevel=1
  (( ++count ))
  sleep 1
done

unset errorlevel
unset count

任何见解都将不胜感激!

答案1

我更喜欢使用if块:

errorlevel=0
if cp -Lv --no-preserve=mode \
    "$__file" \
    "$output_dir/" \
    | tee -a "$logfile"
then
    echo "zero return, $errorlevel"
else
    errorlevel=$?
    echo "non-zero return, $errorlevel"
fi

防止if非零退出状态触发-e错误退出操作。

(当然,最初的问题是( )创建一个子 shell,因此变量赋值永远不会影响父 shell。)

相关内容