Це не просто відлуння проти 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 $c
printf, ви бачите рядок формату з 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
формат часу.