web-dev-qa-db-ja.com

変数として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)
13

_"$HideErrors"_を展開してリダイレクトを発生させることができない理由は、_>_のようなシンボルは パラメータ展開 によって生成された後は特別に処理されないためです。このようなシンボルはテキストに表示されるため、実際には非常に優れています。拡張して文字どおりに使用したい場合があります。

これは、_$HideErrors_を引用するかどうかに関係なく保持されます。展開が引用符で囲まれていない場合、パラメーター展開の結果は Word splitting および globbing の影響を受けますが、それだけです。


それについて何をすべきかに関しては、条件付きリダイレクトを達成するための多くの方法があります。非常に単純なコマンドの場合、caseまたはif-else構成の各ブランチに1回ずつ、コマンド全体を2回書き込むのが合理的です。しかし、これはすぐに負担になります。そして、あなたが示したコマンドは確かにそれが理想的ではないケースです。

自分を繰り返さないでください を可能にするアプローチの中には、非常にクリーンで正しく理解しやすいため、特に推奨する2つの方法があります。同じコマンドとリダイレクトに同時に両方ではなく、これらの1つだけを使用する必要があります。

リダイレクトの代わりにコマンドを保存します。リダイレクトを変数に保存してパラメータ展開を適用する代わりに、コマンドを シェル関数 。次に、caseまたはif-elseを記述します。この関数は、1つのブランチでリダイレクトを使用して呼び出され、もう1つのブランチではリダイレクトされません。

コマンドを、一度記述したいが複数の状況下で実行したいコードとして概念化する場合、関数は自然な解決策です。これは私が通常行うことです。 サブシェル も手動の保存も状態のリセットも必要としないという利点があります。

あなたのコードで:

_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はローカル変数であっても、呼び出し元のRobWebAddressおよびDownloadName変数を自動的に使用します。字句スコープの言語。

サブシェルでコマンドを実行し、execへのリダイレクトを条件付きで適用します。これが steeldriverがコメントしたもの 、ただし 効果をローカルに保つために_(_ _)_の内側the exec builtin が引数なしで実行されると、現在のシェルが新しいプロセスで置き換えられるのではなく、そのリダイレクトが現在のシェルに適用されます。

(サブシェルを使用せずに、したがって現在のシェルの環境を変更する機能を犠牲にすることなく、標準エラーが何であったかを追跡してそれを復元することもできます。ただし、詳細については他の回答に任せます。)

あなたのコードで:

_(
    # 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"
)
_

_)_を閉じると、標準エラーは実際には以前の状態に復元されます。これは、親シェルではなくサブシェルでのみリダイレクトされるためです。サブシェルはそれらのコピーを取得するため、これも既存のシェル変数で正常に動作します。私はシェル関数を使用することを好みますが、この方法で必要なコードが少なくなることを認めます。

どちらの方法も、条件付きの動作を含むコードを呼び出すシェル関数に適用されるリダイレクトの場合や、(編集で言及されている)標準エラーが発生する場合を含め、どのファイルまたはデバイスの標準エラーが開始するかに関係なく機能しますスクリプト全体は、以前の_exec 2>&fd_または_exec 2> path_によってすでにリダイレクトされています。パスが プロセス置換 によって作成されたことは問題ありません。

19
Eliah Kagan

ハードコーディングされた引数が機能するが、変数としての引数が機能しないのはなぜですか?

構文項目は拡張された変数値から解釈されないためです。つまり、変数の展開は、コマンドラインで変数参照を変数のテキストに置き換えることと同じではありません。 (;|&&のようなものや引用符なども変数の値に特別なものではありません。)

エイリアスを使用するか、変数を使用してリダイレクトのターゲットのみを保持することができます。

エイリアスareは単なるテキスト置換なので、演算子canは演​​算子やキーワードなどの構文アイテムを保持します。スクリプトでは、非インタラクティブシェルではデフォルトで無効になっているため、shopt expand_aliasesが必要です。したがって、これは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が/proc/<pid>/fdから開いた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は壊れる可能性があります。

4
ilkkachu

質問のタイトル:「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$ 
1
Joshua