Куда делся завершающий символ новой строки из моей подстановки команд?

15

Следующий код лучше всего описывает ситуацию. Почему последняя строка не выводит завершающий символ новой строки? Вывод каждой строки показан в комментарии. Я использую GNU Bash, версия 4.1.5

     echo -n $'a\nb\n'                  | xxd -p  # 610a620a  
           x=$'a\nb\n'   ; echo -n "$x" | xxd -p  # 610a620a
     echo -ne "a\nb\n"                  | xxd -p  # 610a620a
x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p  # 610a62
Peter.O
источник
4
Обходной путь для тех редких случаев, когда это необходимо:tmp=$(somecommand; echo a); tmp=${tmp%a}
Жиль "ТАК - Хватит быть злым"
1
@ Жиль: Вот ваш пример, выше: tmp=$(somecommand; echo a)... Это, безусловно, послужило основанием ... Пока я не увидел пример, моя склонность все еще была бы в использовании echo -n a... но, конечно же, нет необходимости -n, потому что команда Замена удалит введенный трейлинг символ новой строки в любом случае! ... спасибо ...
Питер
stackoverflow.com/questions/613572/…
sancho.s Восстановите Монику

Ответы:

18

Функция подстановки команд $()(и ее двоюродный брат с обратной косой чертой) специально удаляет завершающие символы новой строки. Это документированное поведение , и вы всегда должны знать об этом при использовании конструкции.

Новые строки внутри текстового тела не удаляются оператором подстановки, но они также могут быть удалены при делении слов в оболочке, поэтому то, как это получится, зависит от того, использовали ли вы кавычки или нет. Обратите внимание на разницу между этими двумя способами:

$ echo -n "$(echo -n 'a\nb')"
a
b

$ !! | xxd -p
610a62

$ echo -n  $(echo -n 'a\nb')
a b

$ !! | xxd -p   
612062

Во втором примере выходные данные не были заключены в кавычки, а символ новой строки интерпретировался как разделение слов, в результате чего он отображался в выходных данных как пробел!

Калеб
источник
1
Спасибо, Калеб ... Я знал о том, что разделение слов заменяет пробел на один пробел без кавычек ... Вот почему я был очень удивлен, увидев, что мой завершающий символ новой строки исчез, хотя я процитировал его ... Теперь я знаю, что это из-за "нормального" поведения подстановки команд, сбрасывающего завершающий перевод строки ... О, хорошо, c'est la vie .. и спасибо за ссылку
Peter.O
6

При использовании подстановки команд оболочка выполняет команды в подоболочке, возвращая их стандартный вывод. в этом процессе символы IFS теряют свою значимость (если они не заключены в кавычки), поскольку commend возвращает простые разделенные слова, поэтому завершающие слова удаляются. Например:

$ echo "$(echo -e '\n')" | wc
 1       0       1

$ echo -e '\n' | wc
2       0       2

и более практично, pwdбудет работать, даже если в вашем имени каталога есть новая строка между ними, но $(pwd)не будет.

Обычный обходной путь заключается в добавлении чего-либо в конце вашей команды и последующем ее удалении.

человек, любящий учиться
источник
1
Спасибо, Филомат ... кстати, ваш первый пример синтаксически неверен ('wc' не принимает строку в качестве аргумента). Чтобы ваши примеры имели смысл, первый может быть echo "$(echo -e '\n')" | wc, который выводит 1   0   1, по сравнению с2   0   2
Peter.O
@fred: Ой, просто опечатка.
Филомат
1
«преобразовано в пробелы» не является подходящим объяснением, здесь есть только некоторое расщепление. В частности, вы можете удалить пространство из IFS, и они не будут действовать как разделители. Более того , ваш пример попадет в специальный случае категорию, и есть разница между $(pwd)и "$(pwd)", см ответа Калеба.
Стефан Гименес
PS .. Ваше предложение обычного обходного пути - это как раз то, что мне нужно, чтобы прояснить этот вопрос ..
Питер
Re "преобразованы в пробелы", см. Разделение слов
Peter.O