如何将 2>/dev/null 作为变量传递?

Win*_*nix 16 command-line bash redirect

我有这个有效的代码:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi
Run Code Online (Sandbox Code Playgroud)

如果我尝试像这样缩短它:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"
Run Code Online (Sandbox Code Playgroud)

我收到错误消息:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)
Run Code Online (Sandbox Code Playgroud)

为什么硬编码的参数可以工作,但不能将参数作为变量?


编辑2:

目前我发现第二个答案的替代建议取得了成功:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"
Run Code Online (Sandbox Code Playgroud)

编辑1:

基于第一个答案,我应该指出程序头已经包含:

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
Run Code Online (Sandbox Code Playgroud)

Eli*_*gan 20

您不能通过扩展导致重定向发生的原因"$HideErrors"是像这样的符号>在由参数扩展生成后没有被特殊处理。这实际上非常好,因为这样的符号出现在您可能想要扩展和按字面使用的文本中。

无论您是否引用,这都适用$HideErrors。当扩展不带引号时,参数扩展的结果会受到分通配符的影响,仅此而已。


至于如何处理,有很多方法可以实现条件重定向。对于一个非常简单的命令,将整个命令写两次可能是合理的,一次在caseor if-else结构的每个分支中。然而,这很快就会变得繁重,并且您显示的命令肯定是不理想的情况。

在让您避免重复自己的方法中,我特别推荐两种方法,因为它们非常干净且易于正确使用。对于相同的命令和重定向,您只想使用其中之一,而不是同时使用两者。

存储命令而不是重定向。与其尝试将重定向存储在变量中并应用参数扩展,不如将命令存储在shell 函数中。然后写一个caseor if- else,其中在一个分支上使用重定向调用该函数,而在另一个分支上不使用它。

如果您将命令概念化为您想编写一次但在多种情况下运行的代码,那么函数是自然的解决方案。这是我通常做的。它的优点是既不需要子shell,也不需要手动存储和重置状态。

使用您的代码:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac
Run Code Online (Sandbox Code Playgroud)

您可以应用任何您喜欢的间距,或者if-else如果您愿意。请注意,即使它们是局部变量,也会launch自动使用调用者RobWebAddressDownloadName变量,因为 Bash 是动态范围的,这与大多数词法范围的编程语言不同。

在子 shell 中运行命令并有条件地将重定向应用到exec. 这是steeldriver 所评论的,但在内部( )保持局部效果。当exec内置不带参数运行,它不会取代一个新的进程当前shell,而是适用于任何其重定向到当前shell。

(也可以跟踪标准错误是什么并恢复它,而不使用子shell,从而不牺牲修改当前shell环境的能力。不过,我会将其细节留给其他答案。)

使用您的代码:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)
Run Code Online (Sandbox Code Playgroud)

在结束之后),标准错误实际上恢复到之前的状态,因为它只是在子shell 中被重定向,而不是在父shell 中。这也适用于现有的 shell 变量,因为子 shell 获得了这些变量的副本。虽然我更喜欢使用 shell 函数,但我承认这种方法可能需要更少的代码。

无论文件或设备标准错误以什么开始,这两种方法都有效,包括应用于调用包含条件行为的代码的 shell 函数的重定向的情况,以及标准错误的情况(在您的编辑中提到)整个脚本已经被前一个或. 路径是由进程替换产生的,这是没有问题的。exec 2>&fdexec 2> path


ilk*_*chu 5

为什么硬编码的参数可以工作,但不能将参数作为变量?

因为语法项不是从扩展变量值解释的。也就是说,变量扩展与在命令行中用变量的文本替换变量引用不同。(东西一样;|&&和报价等也都是在变量的值不是特别的。)

你可以做的是使用别名,或者使用变量来保存重定向的目标。

别名只是一个文本替换,所以他们可以持有的语法项目,如运算符和关键字。在脚本中,您需要shopt expand_aliases,因为默认情况下它们在非交互式 shell 中被禁用。所以,这打印2(仅):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2
Run Code Online (Sandbox Code Playgroud)

(你也可以alias jos=if niin=then soj=fi然后用芬兰语写出你所有的 if 语句。我相信任何阅读剧本的人都会喜欢你。)

或者,始终编写重定向,但仅使用变量控制目标。对于您不想更改输出位置的情况,您将需要一个无操作目标,/dev/stderr应该在这种情况下工作。实际上,添加2> /dev/stderr并不是无操作,因为 Linux 将打开的 fd/proc/<pid>/fd视为独立于原始文件的方式。这会影响写入位置的定位,并且如果它转到常规文件,则会弄乱输出。

但是,它应该在附加模式下工作(或者如果 stderr 进入管道或终端):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"
Run Code Online (Sandbox Code Playgroud)

所以重复一遍:2> /dev/stderr可以打破。