環境変数を指定して同じコマンドラインにエコーできないのはなぜですか?


90

このスニペットを考えてみましょう:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

ここでは、1行目にを設定$SOMEVARAAAています。2行目にエコーすると、AAA期待どおりの内容が表示されます。

しかし、その後、同じコマンドラインで変数を指定しようとすると、次のようになりますecho

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... BBB期待どおりに取得できません-古い値(AAA)を取得します。

これはどのようになっているはずですか?もしそうなら、どうしてあなたは変数を指定してそれを機能させることができますLD_PRELOAD=/... program args ...か?何が欠けていますか?


2
これは、割り当てを別のステートメントにする場合、または独自の環境でスクリプトを呼び出す場合に機能しますが、現在の環境でコマンドを前置する場合には機能しません。面白い!
トッドA.ジェイコブス

1
理由LD_PRELOADは、変数がプログラムの環境で設定されているためです - コマンドラインでは設定されていません
追って通知があるまで一時停止。

回答:


102

あなたが見るのは期待される行動です。問題は$SOMEVAR、変更された環境でコマンドを呼び出す前に、親シェルがコマンドラインで評価することです。$SOMEVAR環境が設定されるまで据え置きの評価を取得する必要があります。

直接的なオプションは次のとおりです。

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'

どちらも一重引用符を使用して、親シェルが評価されないようにし$SOMEVARます。環境に設定された後でのみ評価されます(一時的に、単一のコマンドの期間中)。

別のオプションは、サブシェル表記を使用することです(これもMarcus Kuhn回答で示唆されています)。

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

変数はサブシェルでのみ設定されます


すばらしい、@ JonathanLeffler-説明ありがとうございました。乾杯!
sdaau

@ markus-kuhnからの加算を過大評価することは困難です。
Alex Che

37

問題、再訪

率直に言って、マニュアルはこの点で混乱しています。GNU bashのマニュアルは言います:

シェルのパラメーターで説明されているように、単純なコマンドまたは関数の環境[組み込みのものは除外されることに注意してください]は、パラメーターの割り当てを前に付けることによって一時的に拡張できます。これらの割り当てステートメントは、そのコマンドが認識する環境にのみ影響します。

文を実際に解析すると、コマンド/関数の環境は変更されますが、親プロセスの環境は変更されないということです。したがって、これは機能します:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

envコマンドの環境は、実行前に変更されているためです。ただし、これは機能しません。

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

これは、シェルによってパラメーターの展開が実行されるためです。

通訳の手順

問題のもう1つの部分は、Bash がインタープリターに対してこれらのステップ定義していることです

  1. ファイル(シェルスクリプトを参照)、-c呼び出しオプションの引数として指定された文字列(Bashの呼び出しを参照)、またはユーザーの端末から入力を読み取ります。
  2. 入力を単語と演算子に分解し、引用で説明されている引用規則に従います。これらのトークンはメタ文字で区切られています。エイリアスの展開は、このステップで実行されます(エイリアスを参照)。
  3. トークンを単純なコマンドと複合コマンドに解析します(シェルコマンドを参照)。
  4. さまざまなシェル展開(シェル展開を参照)を実行し、展開されたトークンをファイル名(ファイル名展開を参照)およびコマンドと引数のリストに分割します。
  5. 必要なリダイレクト(リダイレクトを参照)を実行し、リダイレクト演算子とそのオペランドを引数リストから削除します。
  6. コマンドを実行します(コマンドの実行を参照)。
  7. オプションで、コマンドが完了するまで待機し、その終了ステータスを収集します(終了ステータスを参照)。

ここで何が起こっているかというと、ビルトインは独自の実行環境を取得しないため、変更された環境を見ることはありません。さらに、単純なコマンド(例:/ bin / echo)変更された環境を取得します(これがenvの例が機能した理由です)が、ステップ4で現在の環境でシェルの拡張が行われています。

つまり、「aaa $ TESTVAR ccc」を/ bin / echoに渡していません。(現在の環境で展開されている)補間された文字列を/ bin / echoに渡します。この場合、現在の環境にはTESTVARがないため、コマンドに 'aaa ccc'を渡すだけです。

概要

ドキュメントはもっと明確になるでしょう。スタックオーバーフローがあるのは良いことです。

こちらもご覧ください

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment


私はすでにこれに賛成票を投じていました-しかし、私はこの質問に戻ってきました、そしてこの投稿には私が必要とするポインターが正確に含まれています。@CodeGnome、どうもありがとう!
sdaau 2013

この回答が投稿されてからBashがこの領域で変更されたかどうかはわかりませんが、プレフィックス付きの変数割り当て組み込みで機能します。たとえば、期待どおりにFOO=foo eval 'echo $FOO'印刷fooします。これは、次のようなことができることを意味しますIFS="..." read ...
Vousden、

何が起こっているのかと思うと、Bashは実際に一時的に独自の環境を変更し、コマンドが完了するとそれを復元します。これにより、奇妙な副作用が発生する可能性があります。
Vousden、

22

あなたが望むものを達成するには、

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

理由:

  • 割り当てはセミコロンまたは改行で次のコマンドから区切る必要があります。そうしないと、次のコマンド(エコー)のパラメーター展開が行われる前に実行されません。

  • 現在の行を超えて永続化しないようにするには、サブシェル環境内で割り当てを行う必要があります。

このソリューションは、他のいくつかのソリューションよりも短く、簡潔で効率的です。特に、新しいプロセスは作成されません。


3
ここで締めくくる将来のGoogle社員向け:これはおそらくこの質問に対する最良の答えです。さらに複雑にするために、コマンドの環境で割り当てを使用できるようにする必要がある場合は、それをエクスポートする必要があります。サブシェルは、割り当ての永続化を妨げます。(export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj

@eaj例のように、シェル変数を単一の外部プログラム呼び出しにエクスポートするには、次のように使用しますSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn

10

これは、1行に環境変数を設定するためです。しかし、echo拡張はbashしていません。コマンドが実行される前に、したがって、あなたの変数は、実際にもかかわらず、展開されSOME_VARているBBBエコーコマンドのコンテキストで。

効果を確認するには、次のようにします。

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

ここでは、子プロセスが実行されるまで変数は展開されないため、更新された値が表示されます。SOME_VARIABLE親シェルでもう一度チェックすると、AAA期待どおりにまだです。


1
+1は、記述どおりに機能しない理由を正しく説明し、実行可能な回避策を示します。
ジョナサンレフラー

1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

使う ; 同じ行にあるステートメントを区切るため。


1
それは機能しますが、かなり重要ではありません。アイデアは、ソリューションのように永続的にではなく、1つのコマンドのみの環境を設定することです。
ジョナサンレフラー

@Kyrosに感謝します。どうして私は今までにそれを逃したのかわからない:) LD_PRELOADセミコロンなしで実行可能ファイルの前でどのように動作するかまだ迷っています... 本当にありがとう-乾杯!
sdaau

@JonathanLeffler-確かに、それがアイデアでした。セミコロンが変更を永続的にすることに気づきませんでした。
sdaau

1

ここに一つの選択肢があります:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

コマンドを使用する&&;分離するかに関係なく、割り当ては持続します。これはOPの望ましい動作ではありません。Markus Kuhnがこの回答の正しいバージョンを持っています。
eaj
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.