web-dev-qa-db-ja.com

“ echo $(stuff)”または“ echo `stuff`”の何が問題になっていますか?

私は次のどれかを使いました

echo $(stuff)
echo `stuff`

(ここでstuffpwddate、あるいはもっと複雑なものです。).

それから私はこの構文が間違っている、悪い習慣、エレガントではない、過度の、冗長な、過度に複雑な、カーゴカルトプログラミング、noobish、素朴なことなどを言われました。

しかし、というコマンドはでも動作します。

31

tl; dr

唯一のstuffおそらく動作します。



完全な答え

何が起こるのですか

foo $(stuff)を実行すると、次のようになります。

  1. stuffが実行されます。
  2. その出力(stdout)は、印刷される代わりに、fooの呼び出しで$(stuff)を置き換えます。
  3. その後fooが実行され、そのコマンドライン引数は明らかにstuffが返したものに依存します。

この$(…)メカニズムは、「コマンド置換」と呼ばれます。あなたの場合、メインコマンドはechoで、基本的にコマンドライン引数を標準出力に出力します。したがって、stuffがstdoutに出力しようとするものはすべてキャプチャされ、echoに渡され、echoによってstdoutに出力されます。

stuffの出力をstdoutに出力する場合は、唯一のstuffを実行してください。

`…`構文は$(…)と同じ目的を果たします(同じ名前:「コマンド置換」)。ただし、違いはほとんどないため、盲目的に交換することはできません。 このFAQ および この質問 を参照してください。


何があってもecho $(stuff)を避けるべきですか?

自分が何をしているのかわかっているなら、echo $(stuff)を使いたくなる理由があります。同じ理由で、何をしているのかわからない場合はecho $(stuff)を避けるべきです。

ポイントはstuffecho $(stuff)は完全に同等ではありません。後者は、$IFSのデフォルト値でstuffの出力でsplit + glob演算子を呼び出すことを意味します。コマンド置換を二重引用符で囲むと、これが防止されます。コマンド置換を単一引用符で囲むと、コマンド置換ではなくなります。

分割に関してこれを観察するには、次のコマンドを実行します。

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the Shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

そしてグロビングのために:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the Shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

ご覧のとおり、echo "$(stuff)"stuffと同等です(-ish *)。あなたはそれを使用することができますが、このように物事を複雑にすることのポイントは何ですか?

一方、stuffの出力を分割+グロビングしたい場合、echo $(stuff)が有用であることがわかりますmay。しかし、それはあなたの意識的な決定でなければなりません。

評価して(分割、グロブなどを含む)、シェルで実行する必要がある出力を生成するコマンドがあるため、eval "$(stuff)"が可能です( this answer を参照)。 printedになる前に、追加の分割+グロビングを行うために出力を必要とするコマンドを見たことはありません。意図的にecho $(stuff)を使用することは非常にまれです。


var=$(stuff); echo "$var"はどうですか?

いい視点ね。このスニペット:

var=$(stuff)
echo "$var"

stuffと同等のecho "$(stuff)"と同等である必要があります。コード全体の場合は、代わりにstuffを実行します。

ただし、stuffの出力を複数回使用する必要がある場合、このアプローチ

var=$(stuff)
foo "$var"
bar "$var"

通常は

foo "$(stuff)"
bar "$(stuff)"

fooechoであり、コードでecho "$var"を取得した場合でも、このように保持する方がよい場合があります。考慮事項:

  1. var=$(stuff)を使用すると、stuffは1回実行されます。コマンドが高速であっても、同じ出力を2回計算しないようにするのが正しいことです。または、stuffにはstdoutへの書き込み(たとえば、一時ファイルの作成、サービスの開始、仮想マシンの開始、リモートサーバーへの通知など)以外の効果があるため、複数回実行する必要はありません。
  2. stuffが時間依存またはややランダムな出力を生成する場合、foo "$(stuff)"およびbar "$(stuff)"から一貫性のない結果が得られる可能性があります。 var=$(stuff)の後、$varの値は固定され、foo "$var"bar "$var"が同じコマンドライン引数を取得することを確認できます。

場合によっては、foo "$var"の代わりにfoo $varを使用する必要があります(特に、stufffooに対してmultiple引数を生成する場合(配列変数はシェルがサポートしています)。繰り返しますが、あなたが何をしているかを知っています。 echoに関しては、echo $varecho "$var"の違いは、echo $(stuff)echo "$(stuff)"の違いと同じです。


*同等(-ish)?

echo "$(stuff)"stuffと同等(-ish)であると言いました。正確に同等ではない少なくとも2つの問題があります。

  1. $(stuff)はサブシェルでstuffを実行するため、echo "$(stuff)"(stuff)と同等(-ish)であると言う方が良いです。実行するシェルに影響するコマンドは、サブシェルの場合、メインシェルには影響しません。

    この例では、stuffa=1; echo "$a"です。

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    と比較する

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    そして

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    別の例では、stuffcd /; pwdであることから始まります。

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    stuffおよび(stuff)バージョンをテストします。

  2. echoは、非制御データを表示するのに適したツールではありません 。私たちが話していたecho "$var"printf '%s\n' "$var"でなければなりませんでした。しかし、質問はechoに言及しているため、最も可能性の高い解決策は、最初はechoを使用しないことなので、これまでprintfを導入しないことにしました。

  3. stuffまたは(stuff)は、stdoutとstderrの出力をインターリーブし、echo $(stuff)は、stuff(最初に実行される)からのすべてのstderr出力を印刷し、その後echo(最後に実行される)で消化されたstdout出力のみを印刷します。

  4. $(…)は末尾の改行を取り除き、echoがそれを追加し直します。したがって、echo "$(printf %s 'a')" | xxdprintf %s 'a' | xxdとは異なる出力を提供します。

  5. 一部のコマンド(lsなど)は、標準出力がコンソールであるかどうかによって動作が異なります。 ls | catlsと同じではありません。同様に、echo $(ls)lsとは異なる動作をします。

    lsは別として、一般的な場合ifでこの他の動作を強制する必要がある場合、stuff | catecho $(ls)またはecho "$(ls)"よりも優れています。 。

  6. 終了ステータスが異なる可能性があります(このwikiの回答の完全性について言及されています。詳細については、 別の回答 に値します)。

63

もう1つの違いは、サブシェルの終了コードが失われるため、代わりにechoの終了コードが取得されることです。

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
5
WaffleSouffle