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


13

我有这段代码可以工作:

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

您可以[[ $fCron == true ]] && exec 2>/dev/null改用
steeldriver '19

..粗略地说,这是因为我认为shell在扩展变量之前先设置了重定向。例如,请参见bash:使用变量存储stderr | stdout重定向
Steeldriver

Answers:


19

您无法通过扩展引起重定向的原因"$HideErrors"是,>在通过参数扩展产生类似符号之类的符号后,并没有对其进行特殊处理。这实际上非常好,因为这样的符号会出现在文本中,您可能需要扩展并按字面使用。

无论您是否报价,这都成立$HideErrors。当不使用扩展名时,参数扩展的结果将受到单词拆分通配符的限制,仅此而已。


至于如何处理,有许多方法可以实现条件重定向。对于一个非常简单的命令,可以合理地将整个命令编写两次,一次在a caseif- 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如果愿意,可以应用- 。请注意,因为Bash是动态作用域的,所以它会launch自动使用调用方RobWebAddressDownloadName变量,即使它们是局部变量也是如此,这与大多数编程语言是词法​​作用域的不同。

在子shell中运行命令,并有条件地将重定向应用于exec这是钢铁司机的评论,但为了使效果局部化在内部( )进行了评论。当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


FYI SteelDriver提到了一些exec不知道他是否正在计划答案的问题……
WinEunuuchs2Unix

@ WinEunuuchs2Unix我希望这样的答案仍然发布。尽管我主要建议使用函数,但我还提供了一种涉及重定向的方法exec。但是,正如我在括号内提到的那样,我没有涉及较复杂的应用程序,在这些应用程序中,没有子shell即可保留和还原旧文件描述符。我也没有介绍不太复杂的应用程序,例如仅在脚本结束时才保留重定向。如果发布了另一个答案,则可能涵盖这两个方面,甚至可能涵盖更多。
伊莱亚·卡根

我已经用一个现有的问题更新了我的问题,exec它根本不会影响您的答案。
WinEunuuchs2Unix

@ WinEunuuchs2Unix是的,那应该没问题。我在答案的末尾添加了一段有关它的内容。
伊莱亚·卡根

有趣的启示阅读您的答案,RobWebAddress绝对是全球背景。DownloadName被定义为本地,但应为全局。出于某种原因DownloadNameDownloadAsHTML ()UpdateOne ()
子函数

4

为什么硬编码的参数有效,但参数作为变量无效?

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

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

别名只是一个文本替换,所以他们可以持有的语法项目,如运算符和关键字。在脚本中,您需要使用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可以打破。


哈哈哈,从现在开始,我只会在工作中使用芬兰ifs。:>
甜点

我喜欢其他建议。这个想法expand_aliases很可怕,因为~/.bashrc我认为您的程序可以被扣为人质。
WinEunuuchs2Unix

1
@ WinEunuuchs2Unix,是的,expand_aliases有点吓人。但这~/.bashrc不是问题,因为它只能由交互式外壳程序读取,并且.profile可以调用它的朋友只能由登录外壳程序读取。诸如脚本之类的非交互式非登录外壳程序不应运行其中任何一个。(但是然后有$BASH_ENV,并且.bashrc如果stdin连接到网络套接字,显然可以读取。它有多复杂……)
ilkkachu

好吧,我很理解您的替代建议,我今晚会尝试:)
WinEunuuchs2Unix

老实说,我不确定如果必须要采用哪种方式来实现。我可能会将命令存储在函数或数组中,然后分支以决定是否将重定向放在那里(在另一个答案中显示了使用函数)。或那个2>> "$dst"技巧,但我只是意识到它在一般情况下不起作用,因此最好小心使用。
ilkkachu

1

问题标题:“如何将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$ 

@EliahKagan:问题标题:“如何将2> / dev / null作为变量传递?”
约书亚

好的,它不起作用。我已经解决了。
约书亚

现在,该文件始终被命名为DownloadName-并且文字文本RobWebAddress始终用于该URL。您正在使用$" "引号。我认为这可能是无意的,您可能想$在中加入" ",但是您在两个地方都这样做了,所以我不确定。我认为> "$DownloadName"应该修复它。但我了解您可能不喜欢这种方式,因为不小心将参数和非参数混合使用eval是一个原因,因此使用eval的串联行为是如此危险且广泛不鼓励使用。
伊莱亚·卡根

@EliahKagan:哦。我首选的脚本外壳程序没有$“”引用。
约书亚

1
如果您解决了该问题,它应该可以工作。而且我总是以为它在任意文本上粘贴了引号始终是错误的!它建立在评估之前会串联的文字参数eval。但是我认为另一种表达方式是,它是一种混淆的写法eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors",类似于OP代码的外观。通常,将其eval用于不需要的任务是很糟糕的(这些借口都没有,甚至也没有解释,这是我的旧答复的错误和敌意。)
Eliah Kagan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.