添加重定向到 bash 命令数组

Adding a redirection to a bash command array

我正在存储要在 bash 数组中执行的命令,示例:

declare -a cmd=("sudo" "dnf" "update")
"${cmd[@]}"

Last metadata expiration check: 0:24:45 ago on Fri 07 Jan 2022 03:35:34 PM EST.
Dependencies resolved.
Nothing to do.
Complete!

现在,假设我想重定向输出以减少噪音。这有效:

"${cmd[@]}" &>/dev/null

但我更愿意将重定向存储在命令数组中,这样它就可以像数组中的任何其他命令一样added/removed:

declare -a cmd=("sudo" "dnf" "update" "&>/dev/null")
"${cmd[@]}"

Last metadata expiration check: 0:29:14 ago on Fri 07 Jan 2022 03:35:34 PM EST.
No match for argument: &>/dev/null

输出没有被重定向,最后的数组元素只是像普通参数一样被传递。有什么方法可以完成这项工作(即明智地使用 eval)或更好的策略吗?

XY 声明: 我正在尝试使用条件语句使我的程序输出静音。我可以这样做:

silent=true
cmd=("sudo" "dnf" "update")
if silent; then
  "${cmd[@]}" &>/dev/null
else # Be noisy
  "${cmd[@]}"
fi

这导致我的程序运行过程中出现大量重复代码(每个调试操作都需要多个命令执行行)。相反,我更喜欢将重定向附加到数组,例如:

silent=true
cmd=("sudo" "dnf" "update")
$silent && cmd+=("&>/dev/null")
"${cmd[@]}"

此策略适用于函数和参数,但不适用于重定向。虽然我可以对某些程序应用 --quiet 标志来实现这一点,但在某些情况下我想重定向 stderr、重定向到文件等。

您必须使用 eval 来处理变量扩展中的 shell 操作。

declare -a cmd=("sudo" "dnf" "update" "&>/dev/null")
eval "${cmd[@]}"

如果你想避免重复的 "${cmd[@]}" 代码,你可以使用子 shell:

(
    [[ $silent == true ]] && exec &>/dev/null
    exec "${cmd[@]}"
)

我建议使用 exec 如果 cmd 将始终是外部命令。

动态静默重定向

#!/usr/bin/env sh

cmd() {
  silent=
  if [ true = "$silent" ]
    then out=/dev/null
    else out=/dev/stdout
  fi

  sudo dnf update > "$out"
}

如何添加到数组?

# provide a function that wraps the content
silence() { "$@" >/dev/null 2>&1; }

if [ "$silent" = true ]; then
  cmd=( silence "${cmd[@]}" )
fi
"${cmd[@]}"

当然,你可以无条件地使用那个包装器并让它负责工作:

maybe_silence() {
  if [ "$silent" = true ]; then
    "$@" >/dev/null 2>&1
  else
    "$@"
  fi
}

maybe_silence "${cmd[@]}"

如果你真的希望能够支持任意重定向(和其他shell语法),那么有一个只应用一个重定向的包装器是有意义的并保持其他一切不变。

with_redirection() {
  local redirections=     # first argument contains redirections to perform
  shift || return           # remove it from "$@"
  local cmd                 # create a local variable to store our command
  printf -v cmd '%q ' "$@"  # generate a string that evals to our argument list
  eval "$cmd $redirections" # run the string with the redirection following
}

...所以你可以 运行:

cmd=( with_redirection '&>/dev/null' sudo dnf update )
"${cmd[@]}"

...并且只有 &>/dev/null 受到类似 eval 的行为的影响,而其他内容则正常传递。你甚至可以嵌套这个:

testfunc() { echo "this is on stderr" >&2; }
cmd=( with_redirection '>out.txt' with_redirection '2>&1' testfunc

...你最终在 out.txt 中得到 this is on stderr(当然,你也可以 运行 with_redirection '>out.txt 2>&1' testfunc 来获得相同的效果)。