Разница между 'ls' и 'echo $ (ls)'

27

Рассмотрим два образца оболочки

$ ls
myDoc.html
SomeDirectory
someDoc.txt

а также

$ echo $(ls)
myDoc.html SomeDirectory someDoc.txt

Выполняется первый, lsкоторый, как я понимаю, добавляет содержимое текущего рабочего каталога в stdoutфайл (это то, что отображает терминал). Это верно?

Вторая получает значение lsкоманды (что означает содержимое текущего рабочего каталога) и печатает его в stdoutфайл. Это верно?

Почему две команды дают разные результаты?

Оуэн
источник
1
потому что вы делаете эхо. Вывод ls в качестве ввода для команды echo.
ankidaemon
Это «ожидаемое» поведение, которое кто-то вскоре объяснит. Тем не менее, вы уверены, что lsсамо по себе не дает вам несколько элементов в строке?
Ройма
@roaima columnized ls является остатком bsd-функции. Версии Unix распечатать по одной записи в строке
Стив Кокс
@ SteveCox Я не использовал правильную UNIX с раннего SVR4, поэтому моя память немного мутная. Спасибо за напоминание.
Ройма
Почему нет echo *?
jdh8

Ответы:

70

Когда вы запускаете эту команду:

ls

терминал отображает вывод ls.

Когда вы запускаете эту команду:

echo $(ls)

оболочка захватывает выходные данные $(ls)и выполняет разбиение по ним. По умолчанию IFSэто означает, что все последовательности пробелов, включая символы новой строки, заменяются одним пробелом. Вот почему вывод echo $(ls)выводится в одной строке.

Для расширенного обсуждения разделения слов, см . Часто задаваемые вопросы Грега .

Подавление разделения слов

Оболочка не выполняет разбиение слов на строки в кавычках. Таким образом, вы можете подавить разбиение слов и сохранить многострочный вывод с помощью:

echo "$(ls)"

ls и многострочный вывод

Возможно, вы заметили, что lsиногда печатает более одного файла в строке:

$ ls
file1  file2  file3  file4  file5  file6

Это значение по умолчанию, когда вывод lsидет на терминал. Когда вывод не идет непосредственно к терминалу, lsменяет его по умолчанию на один файл на строку:

$ echo "$(ls)"
file1
file2
file3
file4
file5
file6

Это поведение задокументировано в man ls.

Еще одна тонкость: подстановка команд и конечные строки

$(...)является подстановкой команды, и оболочка удаляет завершающие символы новой строки из вывода подстановки команды . Обычно это не заметно, потому что по умолчанию echoдобавляет одну новую строку в конец вывода. Таким образом, если вы потеряете один символ новой строки в конце, $(...)и вы получите один из echo, нет никаких изменений. Однако, если вывод вашей команды заканчивается 2 или более символами новой строки, в то время как echoдобавляется только один, в вашей выдаче будет отсутствовать один или несколько символов новой строки. В качестве примера, мы можем использовать printfдля генерации конечных символов новой строки. Обратите внимание, что обе следующие команды, несмотря на разное количество новых строк, выдают одинаковый вывод одной пустой строки:

$ echo "$(printf "\n")"

$ echo "$(printf "\n\n\n\n\n")"

$ 

Это поведение задокументировано в man bash.

Еще один сюрприз: расширение пути, дважды

Давайте создадим три файла:

$ touch 'file?' file1 file2

Обратите внимание на разницу между ls file?и echo $(ls file?):

$ ls file?
file?  file1  file2
$ echo $(ls file?)
file? file1 file2 file1 file2

В случае echo $(ls file?), глобус файла file?раскрывается дважды , в результате чего имена файлов file1и file2дважды появляются в выводе. Это связано с тем, что, как указывает Джеффикинс, расширение имени пути выполняется сначала оболочкой перед lsзапуском, а затем снова перед echoзапуском.

Второе расширение пути может быть подавлено, если мы использовали двойные кавычки:

$ echo "$(ls file?)"
file?
file1
file2
John1024
источник
4
Кроме того, используя isattyвы можете проверить, является ли stdout tty, ls использует это, чтобы определить, использовать ли цвета или нет.
Янус Троелсен
1
Разве кавычки не совпадают в последнем фрагменте кода? Или $( )фактически обеспечивает синтаксический барьер, поэтому вам не нужно интерполировать?
кошка
6
@cat $()обеспечивает синтаксический барьер.
Random832
2
Еще одно важное отличие: если любое имя файла содержит подстановочный знак, echoкоманда расширит подстановочный знак . Например, если lsвозвращает файл? file1 file2 , тогда echo $(ls)вернет файл? файл1 файл2 файл1 файл2 .
Jeffiekins
1
@Jeffiekins echoне расширяет подстановочные знаки; оболочка выполняет перед передачей полученного расширения в качестве аргументов echo.
chepner
0

ls знает, отправляет ли он вывод на терминал или нет.

Выполнение lsв командной строке запишет в ваш псевдо-tty. Перенаправление вывода ls, как правило, не будет передаваться псевдо-tty и lsбудет форматировать вывод иначе.

mpez0
источник