「while read ...」を使用すると、echoとprintfで異なる結果が得られます


14

「この質問によると、『Linuxのスクリプトで読みながら使用します』 ...

echo '1 2 3 4 5 6' | while read a b c;do echo "$a, $b, $c"; done

結果:

1, 2, 3 4 5 6

しかし、私は交換するときechoprintf

echo '1 2 3 4 5 6' | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done

結果

1, 2, 3
4, 5, 6

誰かがこれら2つのコマンドの違いを教えてください。ありがとう〜

回答:


24

echoとprintfだけではありません

まず、read a b c部品で何が起こるかを理解しましょう。space-tab-newline readであるIFS変数のデフォルト値に基づいて単語分割を実行し、それに基づいてすべてに適合します。保持する変数よりも多くの入力がある場合、分割された部分が最初の変数に適合し、適合できないものは最後になります。ここに私が意味するものがあります:

bash-4.3$ read a b c <<< "one two three four"
bash-4.3$ echo $a
one
bash-4.3$ echo $b
two
bash-4.3$ echo $c
three four

これはまさにbashのマニュアルで説明されている方法です(回答の最後の引用を参照)。

あなたの場合、1と2がaとbの変数に適合し、cは他のすべてを取ります3 4 5 6

また、多くの場合に表示されるのは、人々while IFS= read -r line; do ... ; done < input.txtがテキストファイルを1行ずつ読み取るために使用することです。繰り返しますが、IFS=ここにあるのは、単語の分割を制御する理由、またはより具体的には-無効にして、1行のテキストを変数に読み込むためです。存在しない場合、read個々の単語をline変数に適合させようとします。しかし、これは別の話ですwhile IFS= read -r variable。非常に頻繁に使用される構造なので、後で勉強することをお勧めします。

echo vs printfの動作

echoここで期待することを行います。変数readを配置したとおりに表示します。これは、以前の議論ですでに実証されています。

printf変数がすべて使い果たされるまで変数をフォーマット文字列に適合させ続けるため、非常に特殊です。したがって、printf "%d, %d, %d \n" $a $b $cprintf を実行すると、小数点以下3桁の書式文字列が表示されますが、3つ以上の引数があります(変数は実際には個々の1,2,3,4,5,6に展開されるため)。これは紛らわしいように聞こえるかもしれませんが、実際の printf()関数がC言語で行うことから改善された動作としての理由で存在します。

ここで出力に影響することは、変数が引用されていないことです。これにより、シェル(ではなくprintf)が変数を6つの個別の項目に分解できます。これを引用と比較してください:

bash-4.3$ read a b c <<< "1 2 3 4"
bash-4.3$ printf "%d %d %d\n" "$a" "$b" "$c"
bash: printf: 3 4: invalid number
1 2 3

$c変数が引用符で囲まれているため、1つの文字列として認識される3 4ようになり、%dフォーマットに適合しません。これは単一の整数です

引用せずに同じことを行います:

bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0

printf 繰り返しますが、「OK、そこには6つのアイテムがありますが、フォーマットは3つしか表示されないので、ユーザーからの実際の入力と一致しないものは何でも合わせて空白のままにします」。

そして、これらのすべての場合において、あなたはそれについて私の言葉をとる必要はありません。実行strace -e trace=execveして、コマンドが実際に「見る」ものを自分で確認してください。

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" $a $b $c
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3", "4"], [/* 80 vars */]) = 0
1 2 3
4 0 0
+++ exited with 0 +++

bash-4.3$ strace -e trace=execve printf "%d %d %d\n" "$a" "$b" "$c"
execve("/usr/bin/printf", ["printf", "%d %d %d\\n", "1", "2", "3 4"], [/* 80 vars */]) = 0
1 2 printf: 3 4’: value not completely converted
3
+++ exited with 1 +++

その他の注意事項

Charles Duffyがコメントで適切に指摘したように、コマンドで使用しbashている独自のビルトインがありprintfstrace実際/usr/bin/printfにはシェルのバージョンではなくバージョンを呼び出します。わずかな違いは別として、この特定の質問に関心があるため、標準形式指定子は同じであり、動作も同じです。

また、覚えておくべきことは、printf構文はechoC よりもはるかに移植性があり(したがって、好ましい)ことです。もちろん、構文はCまたはその中にprintf()機能を持つC言語に馴染みがあることは言うまでもありません。vs のテーマに関するterdonのこの優れた回答を参照してください。Ubuntuの特定のバージョンの特定のシェルに合わせて出力を作成することもできますが、異なるシステム間でスクリプトを移植する場合は、おそらくエコーよりも好むはずです。たぶんあなたは、UbuntuやCentOSのマシンを使って作業する初心者のシステム管理者かもしれませんし、FreeBSDさえ知っているかもしれませんので、そのような場合には選択をしなければなりません。printfechoprintf

bashマニュアル、シェルビルチンコマンドセクションからの引用

読み取り[-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]

標準入力または-uオプションの引数として指定されたファイル記述子fdから1行が読み取られ、最初の単語が名に、2番目の単語が2番目の名前に、というように残りが割り当てられます苗字に割り当てられた単語とその間にある区切り文字。入力ストリームから読み取られる単語が名前よりも少ない場合、残りの名前には空の値が割り当てられます。IFSの文字は、シェルが拡張に使用するのと同じルールを使用して行を単語に分割するために使用されます(上記の「単語の分割」で説明)。


2
straceケースと他のケースの注目すべき違いの1つstrace printfは、を使用/usr/bin/printfしているのに対しprintf、bash では直接同じ名前のシェル組み込みを使用していることです。それらは常に同一ではありません-たとえば、bashインスタンスにはフォーマット指定子が%qあり、新しいバージョンで$()Tは時間フォーマット用です。
チャールズダフィー

7

これは単なる提案であり、セルギーの答えを置き換えるものではありません。Sergiyは、なぜ印刷が異なるのかについて素晴らしい答えを書いたと思います。どのように読むの変数はに残って割り当てられます$cように、可変3 4 5 612に割り当てられているabechoどこあなたのための変数を分割しませんprintfとの意志%dの。

ただし、コマンドの先頭にある数字のエコーを操作することで、基本的に同じ答えが得られるようにすることができます。

以下で/bin/bash使用できます:

echo -e "1 2 3 \n4 5 6"

/bin/sh使用することができます:

echo "1 2 3 \n4 5 6"

Bashは-eを使用して\エスケープ文字を有効にしますが、shは既に有効になっているため、必要ありません。 \nこれにより、新しい行が作成されるため、エコー行は2つの独立した行に分割され、エコーループステートメントで2回使用できるようになりました。

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6

次のprintfコマンドを使用すると、同じ出力が生成されます。

:~$ echo -e "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

sh

$ echo "1 2 3 \n4 5 6" | while read a b c; do echo "$a, $b, $c"; done
1, 2, 3
4, 5, 6
$ echo "1 2 3 \n4 5 6" | while read a b c ;do printf "%d, %d, %d \n" $a $b $c; done
1, 2, 3 
4, 5, 6 

お役に立てれば!


echo -eは単なるPOSIXの拡張機能echoではありませんが-e、入力が実際に黒文字仕様に反している/破壊していることを考えると、その出力で出力しないものはすべてです。(bashはデフォルトでは非準拠ですが、両方posixxpg_echoフラグが設定されている場合は標準に準拠しています。後者コンパイル時にデフォルトにすることができます。つまり、bashのすべてのバージョンがecho -eここで期待どおりに動作するわけではありません)
チャールズダフィー

pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.htmlにあるPOSIX仕様のAPPLICATION USAGEおよびRATIONALEセクションを参照してください。入力のどこかに、またはバックスラッシュが存在する場合の動作は明示さechoれておらず、代わりに使用してください。echo-nprintf
チャールズダフィー

@CharlesDuffy私はこれがすべてのバージョンのbashで機能すると期待していると書いたことがありますか?そして、私の答えにはどんな問題がありますか?あなたがより良い解決策を持っていると感じたら、人々が間違っていることを証明しようとするのではなく、答えとしてそれを書いてください。私はあなたのような人々と何度もこの方法を経験してきたので、このようなコメントで停止してください。それだけです。
テランス

3
Ask Ubuntuの@CharlesDuffyは、他のLinuxディストリビューションには転送できない回答を書くことがあります。ここの担当者は103ポイントしかないので、気付いていないかもしれません。ただし、Stack Overflowの担当者は123.3Kポイントなので、一般に* NIXシステムに関する多くのことを知っています。You、Terrace、Sergは大丈夫ですが、スレッドを異なる角度から見ていると思います。* NIX移植性のある、または少なくともLinuxディストリビューション間で移植性のある回答を書くための感情をエコーし​​ます(しゃれは意図していません)。
WinEunuuchs2Unix

2
@CharlesDuffyあなたの感情の答え移植可能にすることに同意しますが、これは結局のところ、Ubuntu指向のサイトです。したがって、Ubuntu固有のツールはユーザーが想定する必要があります。そのため、警告は多少暗示されています。過度に長い議論を避けましょう。他のシェルを考慮する必要があり、答えから学ぶために読んでいる初心者も同様に考慮する必要があります。要点を説明するには、おそらく短いコメントで十分でしょう。
セルギーコロディアズニー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.