この質問によると、「 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
しかし、echo
をprintf
に置き換えると
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つのコマンドの違いを教えてください。ありがとう〜
まず、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
は非常に頻繁に使用される構造なので、これは別の話です。後で勉強することをお勧めします。
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でさえも知っているので、そのような場合には選択をしなければなりません。
読み取り[-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の文字は、シェルが展開に使用するのと同じルールを使用して行を単語に分割するために使用されます(上記の「単語の分割」で説明)。
これは単なる提案であり、セルギーの答えを置き換えるものではありません。 Sergiyは、なぜ印刷が異なるのかについて素晴らしい答えを書いたと思います。 $c
と3 4 5 6
がa
とb
に割り当てられた後、読み取りの変数が1
として2
として残りに割り当てられる方法。 echo
は、printf
が%d
sを使用する場合に変数を分割しません。
ただし、コマンドの先頭にある数字のエコーを操作することで、基本的に同じ答えが得られるようにすることができます。
/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
お役に立てれば!