【问题标题】:Adding a redirection to a bash command array添加重定向到 bash 命令数组
【发布时间】:2022-01-07 21:22:02
【问题描述】:

我将要执行的命令存储在 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

但我更喜欢使用命令数组存储重定向,以便可以像数组中的任何其他命令一样添加/删除它:

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、重定向到文件等。

【问题讨论】:

  • 如果你动态执行shell操作,你必须使用eval
  • 您只能扩展参数。重定向发生在参数扩展之前。
  • 为什么不用函数而不是用数组复制函数呢? cmd(){ sudo dnf update;}
  • 顺便说一句,我真的反对silent=true;当 truefalse 是真正的底层命令时,将它们视为布尔值意味着在值未初始化或被注入的情况下,您可以进行任意命令替换。更安全地使用silent=0 / silent=1,然后使用(( silent )) && ... 以避免将数据视为代码。
  • @LéaGris 但是我将如何附加到函数?

标签: arrays bash shell io-redirection


【解决方案1】:

预置到数组怎么样?

# 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=$1     # 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/nulleval 类似行为的约束,而其他内容正常传递。你甚至可以嵌套这个:

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 来获得相同的效果)。

【讨论】:

    【解决方案2】:

    动态静默重定向

    #!/usr/bin/env sh
    
    cmd() {
      silent=$1
      if [ true = "$silent" ]
        then out=/dev/null
        else out=/dev/stdout
      fi
    
      sudo dnf update > "$out"
    }
    

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 看起来不像预期的那样工作:arr=(printf '%s\n' foo 'bar baz'); eval "${arr[@]}"foonbarnbazn
        • 不幸的是,shell 引用规则很难概括这一点。
        • 这在行为上与eval "${cmd[*]}" 完全相同,只是其编写方式使其看起来应该尊重数组的原始参数边界(而实际上它确实不是)。我认为这会使其具有误导性。
        • 是的。问题是eval 将其所有参数连接到一个字符串中,从而丢失了单词边界。
        • 我不确定你是如何解决这个问题的,同时还能处理 shell 元字符。
        猜你喜欢
        • 2020-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-14
        相关资源
        最近更新 更多