web-dev-qa-db-ja.com

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

この質問によると、「 Linuxスクリプトで「while read ...」を使用

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つのコマンドの違いを教えてください。ありがとう〜

14
user3094631

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

まず、read a b cパートで何が起こるかを理解しましょう。 readは、space-tab-newlineである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=は、Wordの分割を制御する理由、またはより具体的には無効にし、1行のテキストを変数に読み込むためにあります。存在しない場合、readは個々のWordをline変数に収めようとします。 while IFS= read -r variableは非常に頻繁に使用される構造なので、これは別の話です。後で勉強することをお勧めします。

エコーとprintfの動作

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

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

また、ここで出力に影響することは、変数が引用符で囲まれていないことです。これにより、シェル(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にはコマンドで使用しているprintfが組み込まれていますが、straceは実際には/usr/bin/printfバージョンを呼び出しますが、シェルのバージョン。わずかな違いは別として、この特定の質問に関心があるため、標準形式指定子は同じであり、動作も同じです。

覚えておくべきことは、printf構文はechoよりもはるかに移植性が高い(したがって優先される)ことです。 printf()関数。 printf vs echoの件名については、こちらをご覧ください terdonによる優れた回答 。 Ubuntuの特定のバージョンの特定のシェルに合わせて出力を作成できますが、異なるシステム間でスクリプトを移植する場合は、おそらくエコーではなくprintfを選択する必要があります。たぶん、あなたはUbuntuやCentOSのマシンで作業する初心者のシステム管理者かもしれませんし、おそらくFreeBSDでさえも知っているので、そのような場合には選択をしなければなりません。

Bashマニュアルの「シェルのビルトインコマンド」セクションからの引用

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

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

24

これは単なる提案であり、セルギーの答えを置き換えるものではありません。 Sergiyは、なぜ印刷が異なるのかについて素晴らしい答えを書いたと思います。 $c3 4 5 6abに割り当てられた後、読み取りの変数が1として2として残りに割り当てられる方法。 echoは、printf%dsを使用する場合に変数を分割しません。

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

/bin/bashでは次を使用できます。

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

/bin/shでは、次のものを使用できます。

echo "1 2 3 \n4 5 6"

Bashは-eを使用して、\エスケープ文字を有効にします。shは、すでに有効になっているため、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 

お役に立てれば!

7
Terrance