Bashでコマンド出力を変数として読み取るためのより効率的または推奨される方法は何ですか?

Bashでコマンド出力を変数として読み取るためのより効率的または推奨される方法は何ですか?

システムコマンドの1行出力をBashシェル変数として読み取るには、次の例に示すように、少なくとも2つのオプションがあります。

  1. IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)

そして

  1. IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)

2つの間に違いはありますか?何がより効果的または推奨されますか?


この/etc/passwd文書は例示のためだけに読まれています。私の質問の焦点はここに文字列がありますそしてプロセスの交換

ベストアンサー1

まず、readWithoutを使用することは、フィールドや行区切り文字をエスケープするために使用される入力を-r処理するためのものです\が、そうではありません/etc/passwd。あなたはreadそれを使用したくない場合はほとんどありません-r

今、どちらの形式も標準構文ではないことに注意してくださいsh。 1991年<<<からzsh。 1985年頃<(...)から、最初はリダイレクトはサポートされていません。kshksh

$(...)また、kshから来ましたが、POSIXによって標準化されているので(間違って設計された`...`Bourneシェルを置き換えたため)、これはsh実装全体で移植可能です。

$(code)サブシェルのコードが解釈され、出力がパイプにリダイレクトされますが、親シェルはパイプのもう一方の端から出力を読み取り、それをメモリに保存します。その後、コマンドが完了すると、出力から末尾の改行文字が削除され(およびNUL文字が削除されbash)、拡張が形成されます$(...)

参照されず、リストのコンテキストにある場合は、$(...)分割+glob(zshのみで分割)の影響を受けます。それ以降は<<<リストコンテキストではありませんが、まだ以前のバージョンでは、bashまだ部分を分割(グローバルではない)してから、その部分をスペースで連結していました。。したがって、使用する場合は、対象として使用時に引用もしてbashいただければと思います。$(...)<<<

cmd <<< wordzsh以前のbashでは、シェルはword改行文字と改行文字を一時ファイルに保存し、実行するプロセスの標準入力として使用され、以前に削除された一時cmdファイルを実行します。cmdこれは<< EOF、1970年代にBourne Shellで発生したのと同じことです。実際には、次のものとまったく同じです。

cmd << EOF
word
EOF

5.1では、bashは単語がパイプバッファに完全に入ることができるたびに一時ファイルの使用からパイプの使用に切り替えました(またはデッドロックを回避したくない場合は一時ファイルの使用に置き換えます)、cmdシェルの標準入力にofパイプラインが事前にシードされますできるようにしましたword

したがって、cmd1 <<< "$(cmd2)"1 つまたは 2 つのパイプが関連付けられ、出力全体をcmd2メモリに保存し、もう一方のパイプまたは一時ファイルに保存し、NUL と改行文字を削除します。

cmd1 < <(cmd2)cmd2 | cmd1に対応する機能の出力はcmd2パイプの書き込み端に接続されます。その後、<(...)もう一方の端を識別するパスに展開され、その反対側の< that-pathファイル記述子を提供します。したがって、シェルはデータに対して何もせずにcmd2直接会話を実行できます。cmd1

bash特に、bashAT&T ksh または zsh とは異なり、次の理由でシェルでこの構成を表示できます。

cmd2 | cmd1

cmd1サブシェルで実行されるので、その場合、cmd1そのサブシェルの変数のみreadread入力されます。

したがって、ここでは次のことをしたいと思います。

IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored < <(
  grep :root: /etc/passwd)

head重複と同様に、-rとにかく1行だけが読み込まれます² readrest_if_any_ignored将来、新しいフィールドが追加され、追加のコンテンツが含まれる場合/etc/passwdに備えて、今後の校正のために1つを追加しました。$shell/bin/sh:that-field

ポータブル(sh)では、次のことはできません。

grep :root: /etc/passwd |
  IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored 

readPOSIXは、サブシェル(例:bash/ dash...)で実行するのか、サブシェルで実行しないのか(例:zsh/ ksh)を指定しないからです。

しかし、次のようにすることができます。

IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored << EOF
$(grep :root: /etc/passwd | head -n1)
EOF

(これは、head出力grep全体がメモリと一時ファイル/パイプに保存されるのを防ぐために復元されます。)

これは効率的ではなくても標準です(@muruが指摘したように、これらの小さな入力の違いは、フォークされたプロセスで外部ユーティリティを実行するコストと比較して無視できます)。

ここでパフォーマンスが重要な場合は、grepシェルの組み込み機能を使用して作業を完了することでパフォーマンスを向上させることができます。ただし、特にbash非常に小さい入力に対してのみこれを実行できます。シェルはこれらの操作のために設計されておらず、この点ではそれよりはるかに小さいためですgrep

while
  IFS=: read <&3 -r user x1 uid gid name home shell rest_if_any_ignored
do
  if [ "$name" = root ]; then
    do-something-with "$user" "$home"...
    break
  fi
done 3< /etc/passwd

lastpipe¹ オプションが設定されておらbashず、シェルがスクリプトのように非対話型の場合は除外

-m1²最初の一致後に検索を停止するように指示するGNU実装またはオプションも参照してください。または移植可能なものと同等のもの:--max-count=1grepgrepsed '/:root:/!d;q'

おすすめ記事