コマンド置換を使用して変数を割り当て、その変数をエコーすると、常に失敗するのはなぜですか?

コマンド置換を使用して変数を割り当て、その変数をエコーすると、常に失敗するのはなぜですか?

以下が Bash で動作しないのはなぜですか?

# Ensure TEST is unset
export TEST=''
echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

何らかの理由で常に空白行が返されます。

別々の行で実行すると、期待どおりの結果が得られます。

# Ensure TEST is unset
export TEST=''
echo "Hello world" > test.txt
TEST=$(cat test.txt)
echo "$TEST"
Hello world!

これをワンライナーで動作させるにはどうすればよいですか?

暗号を解読するにはこれが必要ですGPG の特殊文字を含む文字列を含むファイル。これをアンシブル

ANSIBLE_VAULT_PASSWORD="$( gpg -d ~/gpg_encrypted_vault_password_file 2>/dev/null )" ansible-playbook etc etc etc

何らかの理由で、Ansible 開発者は、ボールト パスワードをプレーン テキスト ファイルに保存するのが安全であると考えています。また、プレイをテストするたびに長い安全なパスワードを入力 (またはコピー アンド ペースト) するのは好きではないため、暗号化されたファイルで GPG エージェントを使用するのが、私が思いつく唯一の安全な回避策です。

答え1

他の既存の回答と矛盾するわけではありませんが、段階的な分析が役立つかもしれないと思いました。u1686_grawity の回答正確に見えますが、これがより明確になると思いました...)

# ensure TEST is unset export TEST=''

良いお知らせです。これはおそらく期待どおりに動作します。# は最初の行をコメントに変換し、次の行は TEST 変数に空の文字列 (0 バイト) を設定します。

echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"

シェルは && を認識します。コードの最初の部分を実行します...

echo "Hello world!" > test.txt

次に、&& を処理します。echo コマンドの戻り値を確認すると、ゼロであることがわかります。したがって、2 番目のコマンドの実行に進むことができます。2 番目のコマンドは次のとおりです。

TEST="$(cat test.txt)" echo "$TEST"

したがって、ここでシェルが最初に行うことは、変数を置き換えることです。「コマンド置換」はここでは変数のように動作するため、「cat text.txt」コマンドが実行されます。シェルの観点から見ると、コマンドは次のようになります。

TEST="Hello world!" echo "$TEST"

さて、ここまではおそらくすべて順調に見えますが、ここで大きな問題があります。シェルはTEST="Hello world!"まだ ' ' コマンドの実行を開始していません。代わりに、シェルは への参照がまだあることに気づき$TEST、それを置き換えます。

ここで、シェルが実行しようとしているコマンドは次のようになります。

TEST="Hello world!" echo ""

もう少し詳しく説明すると、シェルはこのコマンドを分割する動作を実行することを指摘しておきます。この動作により引用符が削除されます。したがって、個々の部分は次のようになります。

  1. テスト
  2. =
  3. こんにちは世界!
  4. エコー
  5. (空の値)

さて、これでシェルはようやく置換を完了したので、実行する実行可能コードを見つけるという次のタスクに進むことができます。

コマンド ラインはA=B C( はA変数名を表し、等号は変数に値が割り当てられることを指定し、 はB割り当てられる値を表し、 はC実行するコマンドを表すオプションの部分です) の形式で始まることがわかります。

シェルがコマンド ラインがこの一般的なパターンに当てはまることに気付くと、実行される実行可能コードは等号を処理するコードになります。基本的に、等号は実行可能ファイル名に似ており、等号はシェルが実行するコードを制御することになります。シェルは、 という名前の変数に という値を割り当てるコードを実行しTESTますHello world!

その後、それが完了すると、シェルは要求されたコマンドを開始します。このコマンドは、上で特定した 4 番目と 5 番目の部分を使用します (4 番目の部分はコマンドで、echo5 番目の部分は空の文字列です)。

私が答えた理由は(えーっと、つまり、sqrt-1 の答え) が機能する理由は、2 番目の && によってシェルがTEST="$(cat test.txt)"コードの戻り値を実行し、その戻り値がゼロであることに注意し、その後で 2 番目の後に続く処理に進むためです&&(そのため、そのecho "$TEST"部分では$TEST変数が処理され、これですべて機能します)。

sqrt-1 の回答には、質問に直接答えているので当然ですが、少し無駄なコードがいくつかあることに注意してください。基本的には次のようになります。

まず、コマンドを 3 つの部分に分割します。

  • echo "Hello world!" > test.txt
  • テスト="$(cat test.txt)"
  • エコー「$TEST」
  • 2 つの演算子があるため、3 番目の部分は 2 番目の部分 (runs and) が成功した場合にのみ実行され、2 番目の部分は最初のコマンドが成功した場合にのみ実行されます&&

    別のバリエーションとしては、次のものが考えられます。

    echo "Hello world!" > test.txt && ( TEST="$(cat test.txt)" ; echo "$TEST" )

    このセミコロンを使用すると、 ' TEST="$(cat test.txt)"' の結果は評価されず、 ' が評価されるかどうかecho "$TEST"' will be run. When I see a &&は判断されません。私は、何が評価されるのか、そしてそれがなぜ重要なのかを理解しようとする傾向があります。そのため、括弧を使用する必要があるという複雑さが増すにもかかわらず、セミコロンを使用したこのバリエーションは、実際には頭の中で処理するのが少し簡単に思えます。

    答え2

    $varこれは、コマンドラインが実行される前にシェルによって展開が行われるため機能しません。ない「echo」コマンドによって。

    一方、VAR=value somecommandは通常の代入ではありません。新しいプロセスに環境変数を挿入することだけを目的としており、そのプロセスが作成される前にシェル変数には影響しません。つまり、 とはまったく異なりますVAR=value; somecommand

    つまり、これは投稿のタイトルで説明されている順序とは逆の順序で起こります。まず、(まだ空)"$TEST"が に展開され""それから echo ""追加のTEST=...環境変数を使用して実行されますが、これは考慮されません。

    テストコマンドはAnsibleの動作を模倣することになっているので、実際には彼らの環境変数を使用し、シェルによる事前展開に依存しない。VAR=value envまたは のようなものVAR=value printenv VARがより適しています。

    答え3

    以下が bash で動作しないのはなぜですか?

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
    

    しかし、実行するとShellCheck – シェルスクリプト解析ツールその理由は次のようになります:

    $ shellcheck myscript
     
    Line 2:
    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" echo "$TEST"
                                      ^-- SC2097 (warning): This assignment is only seen by the forked process.
    >>                                                             ^-- SC2098 (warning): This expansion will not see the mentioned assignment.
    

    見るShellCheck: SC2097 – この割り当ては、フォークされたプロセスでのみ表示されます。

    答え4

    @DavidPostillが提供したリンクに基づいて、私は

    echo "Hello world!" > test.txt && TEST="$(cat test.txt)" && echo "$TEST"
    

    編集期限
    多くのユーザーが正しく指摘しているように、私の回答は役に立つとは言えません。この回答では、このトピックについて誰も何も学べません。
    代わりに@TOOGAMの役職以下は最良の回答(私の個人的な意見)であり、@Peter Mortensen のコードが機能しない理由、bash 変数拡張の仕組み、さらには私が 2 番目を使用することが&&最適な選択ではない理由が完全に明らかにされています。

    関連情報