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:

現在、私は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

..大まかに言えば、変数を展開する前にシェルがリダイレクトを設定するためだと思います。bashの
steeldriver

回答:


19

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

これは、引用するかどうかに関係ありません$HideErrors。パラメータ展開の結果は、展開が引用符で囲まれていない場合、単語の分割グロビングの影響を受けますが、それだけです。


対処方法については、条件付きリダイレクトを実現する方法が多数あります。非常にシンプルなコマンドについては、それがかつての各支店では、二回、コマンド全体の合理的な書き込みもありcaseif- else構築物。しかし、これはすぐに面倒になり、あなたが示したコマンドは確かにそれが理想的ではない場合です。

自分自身を繰り返すことを回避できるアプローチのうち、特に推奨されるのは2つあります。これらは非常にクリーンで、簡単に正しく実行できるからです。同じコマンドとリダイレクトで一度に両方を使用するのではなく、これらの1つだけを使用する必要があります。

リダイレクトの代わりにコマンドを保存します。リダイレクトを変数に保存してパラメーター展開を適用する代わりに、コマンドをシェル関数に保存します。次に、caseまたはif-を記述します。この場合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変数は自動的に使用されることに注意してください。

サブシェルでコマンドを実行し、条件付きでリダイレクトを適用しますexecこれはsteeldriverがコメントしたことですが、内部で( )効果をローカルに保つためです。とき組み込みを引数なしで実行され、それが新しいプロセスと現在のシェルを交換するが、代わりに現在のシェルにそのリダイレクトのいずれかを適用しません。exec

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

あなたのコードで:

(
    # 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>&fdexec 2> path


参考までに、SteelDriverは、それについてexecの回答を計画しているかどうかわからないことについて言及しました
...-WinEunuuchs2Unix

@ WinEunuuchs2Unixそのような答えがまだ投稿されることを願っています。主に関数を使用することをお勧めしますが、リダイレクトを含むメソッドも含めましたexec。しかし、括弧で述べたように、古いファイル記述子がサブシェルなしで保持および復元される、より洗練されたアプリケーションはカバーしませんでした。また、これがスクリプトの最後である場合にリダイレクトを維持するなど、あまり洗練されていないアプリケーションについても取り上げませんでした。別の回答が投稿された場​​合、その両方をカバーする可能性があります。
エリアケイガン

私は、execあなたの答えにまったく影響しないと思われる既存のもので質問を更新しました。
WinEunuuchs2Unix

@ WinEunuuchs2Unixはい、それは問題ないはずです。私はそれについての段落を回答の最後に追加しました。
エリアケイガン

あなたの答えを読む興味深い啓示RobWebAddressは、間違いなくグローバルな文脈です。DownloadNameローカルに定義されていましたが、グローバルコンテキストである必要があります。子関数は、ウィットに(両親のローカル定義を継承し、いくつかの理由のためDownloadNameに見えたDownloadAsHTML ()ことで呼び出される関数UpdateOne ()。ローカルとして定義された関数それは:(ラフ1日だった
WinEunuuchs2Unix

4

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

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

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

エイリアス単なるテキスト置換であるため、演算子やキーワードなどの構文項目保持できます。スクリプトでは、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破ることができます。


ハハハ、私はこれから仕事でフィンランドのifsのみを使用します。:>
デザート

私は別の提案が好きです。expand_aliasesあなたのプログラムが人質にされる可能性があるので、という考えは怖い~/.bashrcです。
WinEunuuchs2Unix

1
@ WinEunuuchs2Unix、はい、expand_aliases少し怖いです。ただし~/.bashrc、インタラクティブシェルによってのみ読み取られるため、問題になることはありません。また、.profileそれを呼び出す可能性のある友人は、ログインシェルによってのみ読み取られます。スクリプトなどの非対話型の非ログインシェルでは、これらのいずれも実行しないでください。(しかし、それがあり$BASH_ENV、どうやら.bashrcstdinがネットワークソケットに接続されている場合に読み取られます。どのように複雑になりますか...)
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を変数として渡す方法」
ジョシュア

うまくいきませんでした。私はそれを修正しました。
ジョシュア

これで、ファイルには常に名前が付けられ、URLには常にDownloadNameリテラルテキストRobWebAddressが使用されます。$" "引用符を使用しています。これは意図的ではないかもしれませんし、$の内部にsが必要かもしれません" "が、両方の場所でそのようにしたので、わかりません。> "$DownloadName"修正すべきだと思う。しかし、私はあなたがそれを好きではないかもしれないことを理解しています。なぜなら、誤って引数と非引数を混在させるevalことが、危険であり、evalの連結動作を使用することを非常に落胆させている理由の1つです。
エリアケイガン

@EliahKagan:ああ。スクリプト用の私の好みのシェルには$ ""引用符がありません。
ジョシュア

1
それを修正すれば、動作するはずです。そして、私はそれが任意のテキストに引用符を貼り付けたと考えるのは常に間違っていました!評価する前に連結するリテラル引数を作成しますeval。しかし、別の言い方をすれば、OPのコードの外観eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"似た難読化された記述方法だと思います。そして一般的に、それを必要としないタスク使用するのは悪いことです(この言い訳のどれも-説明もしない-私の古い返事の誤りと敵意。)eval
エリアカガン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.