コマンドの前に「1 回限りの変数割り当て」を付けると、bash がこの変数を展開しないのはなぜですか

コマンドの前に「1 回限りの変数割り当て」を付けると、bash がこの変数を展開しないのはなぜですか

この bash コマンドを実行し、ステートメントの前にプレフィックスを付けると、変数はfruitこのコマンドの実行中のみ存在するようになります。

$ fruit=apple echo $fruit

$

結果は空行になります。なぜでしょうか?

ワイルドカードからのコメントを引用するにはこの質問:

パラメータの展開はシェルによって行われ、「fruit」変数はシェル変数ではなく、「echo」コマンドの環境内の環境変数にすぎません。

環境変数は依然として変数なので、echo コマンドでも使用できるはずですよね?

答え1

問題は、現在のシェルが変数を早期に展開していることです。変数はコンテキストに設定されていないため、コマンドはecho引数を取得しません。つまり、コマンドは次のようになります。

$ fruit=apple echo

以下は、単一引用符のせいで変数が早期に展開されないようにするための回避策です。

$ fruit=apple sh -c 'echo $fruit'

fruitあるいは、実行されたコマンドに変数が正しく渡されることを示す 1 行のシェル スクリプトを使用することもできます。

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

この質問が予想外の論争や議論を巻き起こしたので、いくつかコメントします。

  • 変数fruitがすでにエクスポートされているかどうかは動作に影響しません。重要なのは、シェルが変数を展開している瞬間の変数値が何であるかです。
$ エクスポートフルーツ=バナナ
$ fruit=apple echo $fruit
バナナ
  • コマンドが組み込みコマンドであるという事実は、echoOP の問題には影響しません。ただし、この構文で組み込みコマンドまたはシェル関数を使用すると、予期しない副作用が発生する場合があります。例:
$ エクスポートフルーツ=バナナ
$ fruit=apple eval 'echo $fruit'
りんご
$ エコー $フルーツ
りんご
  • 類似点はあるがここで尋ねられた質問あれは、まったく同じ問題ではありません。他の質問では、IFSシェルが起動したときには一時変数の値がまだ利用できません。単語分割 別の$varここで変数は、fruitシェルが起動したときにはまだ一時変数の値が利用できません。拡大する同じ変数。

  • もありますもう一つの質問ここでOP は使用されている構文の重要性について尋ねており、より正確には「なぜこれが機能するのか」と尋ねています。ここで OP は重要性を認識していますが、予期しない動作を報告し、その原因、つまり「なぜこれが機能しないのか」について尋ねています。わかりました。他の質問に投稿された貧弱なスクリーンショットを詳しく読んだ後、同じ状況が確かにそこに説明されているので ( BAZ=jake echo $BAZ)、はい、結局のところこれは重複です。

答え2

これを正しく理解するために、まず区別しましょうシェル変数から環境変数。

環境変数は、内部的に使用しているかどうかに関係なく、すべてのプロセスが持つプロパティです。sleep 10実行中であっても、環境変数は存在します。すべてのプロセスに PID (プロセス識別子)、現在の作業ディレクトリ (cwd)、PPID (親 PID)、引数リスト (空の場合でも) などがあるのと同じです。同様に、すべてのプロセスには、分岐時に親プロセスから継承される「環境」と呼ばれるものがあります。

ユーティリティ作成者(C言語でコードを書く人)の視点から見ると、プロセスは環境変数を設定、設定解除、または変更する機能を持っています。しかし、スクリプト作成者の視点から見ると、ほとんどのツールはユーザーにその機能を提供していません。代わりに、シェルプロセス環境を変更し、呼び出したコマンド (外部バイナリ) の実行時にその環境が継承されます。(シェル自体の環境を変更してその変更を継承することも、シェルに対して、フォーク後、呼び出したコマンドの実行前に変更を行うように指示することもできます。どちらの方法でも、環境は継承されます。両方のアプローチについて見ていきます。)

シェル変数は別の話です。殻の中でそれらは同じように動作しますが、違いは単なる「シェル変数」は、呼び出したコマンドの動作を変更したり影響を与えたりしないということです。からシェル。適切な用語では、この区別は実際には少し異なる方法で表現されます。輸出されたシェル変数は、呼び出すツールの環境の一部になりますが、ないエクスポートされたシェル変数はそうではありません。しかし、エクスポートされていないシェル変数を「シェル変数」と呼び、エクスポートされたシェル変数を「シェル変数」と呼ぶ方がコミュニケーションには役立つと思います。「環境変数」としてエクスポートされるのは、シェルからフォークされたプロセスの観点から見た環境変数。


テキストが長すぎます。いくつかの例を見て、何が起こっているのか説明しましょう。

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

この例では、somevarは単なるシェル変数であり、特別なものではありません。シェルパラメータ拡張(参照LESS='+/Parameter Expansion' man bash)が発生する前に実行lsファイルは実際にはロード(「実行」)され、lsコマンド(プロセス)は実行されません。見る文字列「ドル記号 somevar」。文字列「myfile」のみを認識し、それを現在の作業ディレクトリ内のファイルへのパスとして解釈し、その情報を取得して出力します。

コマンドexport somevarの前に実行するとlssomevar=myfile環境プロセスの変数は変更されますが、コマンドはこの変数に対して何も行わないlsため、何も影響はありません。ls効果環境変数の場合、呼び出しているプロセスが実際にチェックして何かを実行する環境変数を選択する必要があります。


bc: 基本的な計算機

もっと良い例があるかもしれませんが、これは私が思いついたあまり複雑ではない例です。まず、 はbc基本的な計算機であり、数式を処理して計算することを知っておく必要があります。(入力ファイルの内容を処理した後、標準入力を処理します。例では標準入力を使用しません。Ctrl-D を押すだけですが、これは以下のテキスト スニペットには表示されません。また、-q呼び出しごとに導入メッセージを抑制するために を使用しています。)

ここで説明する環境変数は、次のとおりですman bc

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

では、始めましょう:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

これは、どのように動作するかを示すためのものですbc。これらの各インスタンスでは、 に「入力の終了」を通知するために Ctrl + D を押す必要がありましたbc

さて、環境変数を渡してみましょう直接bc

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

この変数に入れたものはない後でコマンドから参照できますecho。代入を同じコマンドの一部として(セミコロンなしで)記述することで、変数代入を次のように記述します。一部環境のbc— 実行しているシェル自体は影響を受けません。

さて、設定してみましょBC_ENV_ARGSシェル変数:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

echoここで、コマンドは内容を見ることができますが、それが の環境の一部ではないため、bc特別bcな操作を行うことはできないことがわかります。

もちろん、変数自体をbcの引数リストに入れると、次のようになります。

$ bc -q "$BC_ENV_ARGS"
25
$ 

しかし、ここではシェル変数を展開すると、実際に'sfile1に表示されるのはbc引数リスト。 したがって、これは環境変数ではなく、シェル変数として使用されています。

ここで、この変数を「エクスポート」して、シェル変数と環境変数の両方にしてみましょう。

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

ここでは、コマンドラインには記載されていないものの、 がfile1の前に処理されることがわかります。これはシェルの環境の一部であり、そのプロセスを実行すると の環境の一部になります。したがって、この環境変数の値は次のようになります。file2bc遺伝性の動作方法に影響しますbc

これをコマンドごとにオーバーライドすることも、空の値にオーバーライドすることもできます。

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

しかし、ご覧のとおり、変数はシェルに設定されエクスポートされたままであり、シェル自体とそれ以降のbcコマンドの両方で表示されます。しない値を上書きします。これを「unexport」または「unset」しない限り、この状態のままになります。私は後者を実行します。

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

別のシェルを生成する別の例:

次のコマンドをシェルに次々に入力し、結果を検討します。実行する前に結果を予測できるかどうかを確認してください。

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

最後にもう 1 つ、さらに難しい質問です。次のコマンドを順番に実行した場合の出力を予測できますか? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

必要な説明があればコメント欄に記入してください。確かに、もう少し説明が必要だと思います。しかし、このままでも役立つはずです。

答え3

コマンドラインでの展開は変数の割り当ての前に行われるためです。標準ではそう言われている:

特定の単純なコマンドを実行する必要がある場合、次のすべてが実行されます。

  1. 変数割り当てとして認識された単語は、手順 3 と 4 で処理するために保存されます。

  2. 変数の割り当てやリダイレクトではない単語は展開されます。 [...]

  3. リダイレクトは、「リダイレクト」で説明されているとおりに実行されます。

  4. 各変数の割り当ては、値を割り当てる前に展開される必要があります [...]。

順序に注意してください。最初のステップでは、割り当ては保存された、その後、他の単語が展開され、最後にのみ変数の割り当てが行われます。

もちろん、標準でそう書かれているのは、これまでずっとそうだったからというだけであり、既存の動作を成文化しただけである可能性が高いです。その歴史や背後にある理由については何も述べられていません。シェルは、ある時点で代入のように見える単語を識別する必要があります (コマンドの一部として受け取らないためだけでも)。そのため、代わりに逆の順序で動作し、最初に変数を割り当て、次にコマンド ラインで何かを展開できると思います。(または、単に左から右に実行します...)

関連情報