Це не просто відлуння проти printf
Спочатку давайте розберемося, що відбувається з read a b cчастиною. 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для читання текстових файлів по черзі. Знову ж таки, IFS=є причина контролювати розділення слів, а точніше - відключити його та прочитати один рядок тексту в змінну. Якби його не було, 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() функція в мові С.
Що ви також зробили тут, що впливає на вихід, - це те, що ваші змінні не цитуються, що дозволяє оболонці (не 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, що котирується змінна, вона тепер визнається як один цілий рядок 3 4, і він не відповідає %dформату, який є лише одним цілим числом
Тепер зробіть те ж саме, не цитуючи:
bash-4.3$ printf "%d %d %d\n" $a $b $c
1 2 3
4 0 0
printf знову каже: "Гаразд, у вас є 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 +++
Додаткові нотатки
Як належним чином зауважив у коментарях Чарльз Даффі, у нього bashє своя вбудована система printf, яку ви використовуєте у своїй команді, straceнасправді буде називати /usr/bin/printfверсію, а не оболонку. Крім незначних відмінностей, для нашого інтересу до цього конкретного питання специфікатори стандартного формату однакові, а поведінка однакова.
Що також слід пам’ятати, це те, що printfсинтаксис є набагато більш портативним (і, отже, кращим), ніж echo, не кажучи вже про те, що синтаксис більш звичний для C або будь-якої мови, подібної С, яка має printf()в ньому функцію. Дивіться цей чудовий відповідь на terdon на тему printfпроти echo. Хоча ви можете зробити висновок з урахуванням вашої конкретної оболонки для вашої конкретної версії Ubuntu, якщо ви збираєтесь переносити сценарії в різні системи, вам, мабуть, слід віддати перевагу, printfа не відлуння. Можливо, ви початківець системний адміністратор, який працює з машинами Ubuntu та CentOS, або, можливо, навіть FreeBSD - хто це знає - тому в таких випадках вам доведеться робити вибір.
Цитата з інструкції з bash, розділ SHELL BUILTIN COMMANDS
read [-ers] [-a aname] [-d delim] [-i текст] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ... ]
Один рядок зчитується зі стандартного вводу або з дескриптора файлу fd, який подається як аргумент до опції -u, і перше слово присвоюється першому імені, другому - другому і т. Д., Залишившись слова та їх втручаються роздільники, присвоєні прізвищу. Якщо з вхідного потоку читається менше слів, ніж імен, решта імен присвоюються порожніми значеннями. Символи IFS використовуються для розділення рядка на слова, використовуючи ті самі правила, які використовує оболонка для розширення (описано вище в розділі Word).
straceрегістром та іншим -strace printfце використання/usr/bin/printf, тоді якprintfбезпосередньо в bash використовується оболонка, вбудована за однойменною назвою. Вони не завжди будуть ідентичними - наприклад, екземпляр bash має специфікатори формату%qта, у нових версіях,$()Tформат часу.