Это не просто эхо против печати
Сначала давайте разберемся, что происходит с read a b c
деталью. read
выполнит разделение слов на основе значения IFS
переменной по умолчанию, которое является space-tab-newline, и подгонит все на основе этого. Если есть больше входных данных, чем переменные для его хранения, он поместит разделенные части в первые переменные, а то, что не может быть подогнано - перейдет в последнюю. Вот что я имею в виду:
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
как это очень часто используемая структура.
эхо против поведения 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 или любого C-подобного языка, который имеет printf()
функцию в нем. Посмотрите этот превосходный ответ Тердона на тему printf
против echo
. Хотя вы можете настроить вывод в соответствии с вашей конкретной оболочкой в вашей конкретной версии Ubuntu, если вы собираетесь переносить скрипты на разные системы, вам, вероятно, следует предпочесть, printf
а не echo. Возможно, вы начинающий системный администратор, работающий с машинами Ubuntu и CentOS, или, может быть, даже FreeBSD - кто знает - поэтому в таких случаях вам придется делать выбор.
Цитата из руководства bash, раздел SHELL BUILTIN COMMANDS
читать [-ers] [-a имя] [-d раздел] [-i текст] [-n nchars] [-N nchars] [-p приглашение] [-t тайм-аут] [-u fd] [имя ... ]
Одна строка читается из стандартного ввода или из файлового дескриптора fd, предоставленного в качестве аргумента опции -u, и первое слово присваивается первому имени, второе слово - второму имени и т. Д. С остатком слова и их промежуточные разделители, присвоенные фамилии. Если из входного потока прочитано меньше слов, чем имён, оставшимся именам присваиваются пустые значения. Символы в IFS используются для разделения строки на слова с использованием тех же правил, которые оболочка использует для раскрытия (описано выше в разделе «Разделение слов»).
strace
случаем и другим -strace printf
это использование/usr/bin/printf
, тогда какprintf
непосредственно в bash используется встроенная оболочка с таким же именем. Они не всегда будут идентичны - например, экземпляр bash имеет спецификаторы формата%q
и, в новых версиях,$()T
для форматирования времени.