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

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

我有这个有效的代码:

# 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

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

# 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"

我收到错误消息:

[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 ...)

为什么硬编码参数有效,而变量参数无效?


编辑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"

编辑1:

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

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

答案1

不能通过扩展来引起重定向的原因是"$HideErrors",像>参数扩展这其实非常好,因为这样的符号出现在您可能想要扩展并按字面意思使用的文本中。

无论你是否引用 ,这都成立$HideErrors。参数扩展的结果取决于单词拆分通配符当扩展名不加引号时,就是这样。


至于该怎么做,有很多方法可以实现条件重定向。对于一个非常简单的命令,将整个命令写两次可能是合理的,在或case构造if的每个分支中各写一次else。然而,这很快就会变得繁重,而您展示的命令肯定不是理想的情况。

让你避免重复,我特别推荐其中两个,因为它们非常简洁,而且很容易正确使用。对于同一个命令和重定向,您只需要使用其中一个,而不是同时使用两个。

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

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

使用您的代码:

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

您可以应用任何您喜欢的间距,或者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"
)

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

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

答案2

为什么硬编码参数有效,而变量参数无效?

因为语法项不是从扩展的变量值中解释的。也就是说,变量扩展与在命令行中用变量的文本替换变量引用不同。(诸如;|&&引号等内容在变量值中也不特殊。)

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

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

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(然后你也可以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"

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

答案3

问题标题:“如何将 2>/dev/null 作为变量传递?”实际上可以使用以下方法完成eval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

所以我们可以重写为

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

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

间接变量访问可防止在命令行的其余部分过早发生扩展。

变量中的双引号工作正常。

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 

相关内容