
システムコマンドの1行出力をBashシェル変数として読み取るには、次の例に示すように、少なくとも2つのオプションがあります。
IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)
そして
IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)
2つの間に違いはありますか?何がより効果的または推奨されますか?
この/etc/passwd
文書は例示のためだけに読まれています。私の質問の焦点はここに文字列がありますそしてプロセスの交換。
答え1
まず、read
Withoutを使用することは、フィールドや行区切り文字をエスケープするために使用される入力を-r
処理するためのものです\
が、そうではありません/etc/passwd
。あなたはread
それを使用したくない場合はほとんどありません-r
。
今、どちらの形式も標準構文ではないことに注意してくださいsh
。 1991年<<<
からzsh
。 1985年頃<(...)
から、最初はリダイレクトはサポートされていません。ksh
ksh
$(...)
また、kshから来ましたが、POSIXによって標準化されているので(間違って設計された`...`
Bourneシェルを置き換えたため)、これはsh
実装全体で移植可能です。
$(code)
サブシェルのコードが解釈され、出力がパイプにリダイレクトされますが、親シェルはパイプのもう一方の端から出力を読み取り、それをメモリに保存します。その後、コマンドが完了すると、出力から末尾の改行文字が削除され(およびNUL文字が削除されbash
)、拡張が形成されます$(...)
。
参照されず、リストのコンテキストにある場合は、$(...)
分割+glob(zshのみで分割)の影響を受けます。それ以降は<<<
リストコンテキストではありませんが、まだ以前のバージョンでは、bash
まだ部分を分割(グローバルではない)してから、その部分をスペースで連結していました。。したがって、使用する場合は、対象として使用時に引用もしてbash
いただければと思います。$(...)
<<<
cmd <<< word
zsh以前の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
特に、bash
AT&T ksh または zsh とは異なり、次の理由でシェルでこの構成を表示できます。
cmd2 | cmd1
cmd1
サブシェルで実行されるので、その場合、cmd1
そのサブシェルの変数のみread
がread
入力されます。
したがって、ここでは次のことをしたいと思います。
IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored < <(
grep :root: /etc/passwd)
head
重複と同様に、-r
とにかく1行だけが読み込まれます² read
。rest_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
read
POSIXは、サブシェル(例: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=1
grep
grep
sed '/:root:/!d;q'
答え2
存在するここにある文字列、bashはここで文字列の内容を生成するためにreplacement()コマンドの出力全体を読み、それを使用して$(grep :root: /etc/passwd | head -n1)
再読み込みを指示しますread
。
一方、プロセスの交換、bashはパイプを設定してから出力を読み込みます。一度。
1行を読み取るためにbash(および他の2つの外部コマンド)を実行しています。その頃には効率が消えてから古くなった。
私たちがまだその仕事をしている間、牛に似た一種の栄養grep
オプションがあります-m
:
-m
シリアル番号
--max-count
=シリアル番号最初の後に停止シリアル番号選択された行。