Даже если @cyrus верен - он на самом деле не отвечает на весь вопрос, и нет объяснения тому, что происходит.
Итак, давайте пройдемся по нему.
Новые строки в строке
Сначала определите ожидаемую последовательность байтов:
$ { echo a; echo b; } | xxd
0000000: 610a 620a a.b.
Теперь используйте команду подстановки (раздел 3.5.4), чтобы попытаться перехватить эту последовательность байтов:
$ x=$( echo a; echo b; )
Затем выполните простое эхо для проверки последовательности байтов:
$ echo $x | xxd
0000000: 6120 620a a b.
Таким образом, кажется, что первая новая строка была заменена пробелом, а вторая новая строка осталась нетронутой. Но почему ?
Давайте посмотрим на то, что на самом деле здесь происходит:
Во-первых, bash сделает расширение параметров оболочки (раздел 3.5.3)
Символ «$» вводит раскрытие параметров, подстановку команд или арифметическое расширение. Имя параметра или символ, который должен быть расширен, может быть заключен в фигурные скобки, которые являются необязательными, но служат для защиты переменной, которая должна быть развернута, от символов, следующих непосредственно за ним, которые могут быть интерпретированы как часть имени.
Затем bash выполнит разделение слов (раздел 3.5.7).
Оболочка сканирует результаты раскрытия параметров, подстановки команд и арифметического раскрытия, которые не встречались в двойных кавычках для разделения слов.
Оболочка обрабатывает каждый символ $ IFS как разделитель и разбивает результаты других расширений на слова на этих символах. Если IFS не установлен или его значение точно ...
Далее, bash будет рассматривать это как простую команду (раздел 3.2.1)
Простая команда - это команда, с которой чаще всего сталкиваются. Это просто последовательность слов, разделенных пробелами, оканчивающихся одним из управляющих операторов оболочки (см. Определения). Первое слово обычно определяет команду, которая должна быть выполнена, а остальные слова являются аргументами этой команды.
Определение пробелов (Раздел 2 - Определения)
пробел или символ табуляции.
Наконец, bash вызывает внутреннюю команду echo (Раздел 4.2 - Команды Bash)
... Вывести аргументы, разделенные пробелами, заканчивающиеся символом новой строки. ...
Таким образом, чтобы подвести итог, новые строки удаляются Word Splitting, а затем echo получает 2 аргумента, «a» и «b», а затем выводит их разделенные пробелами и заканчивая символом новой строки.
Делая то, что предложил @cyrus (и подавив перевод строки из echo с -n), результат получится лучше:
$ echo -n "$x" | xxd
0000000: 610a 62 a.b
Новые строки в конце строки
Это все еще не идеально, хотя завершающий символ новой строки исчез. Присмотревшись к подстановке команд (раздел 3.5.4) :
Bash выполняет расширение, выполняя команду и заменяя подстановку команды стандартным выводом команды, при этом удаляются все завершающие символы новой строки.
Теперь, когда стало ясно, почему символ новой строки исчезает, bash можно обмануть, чтобы сохранить его. Для этого добавьте в конец дополнительную строку и удалите ее при использовании переменной:
$ x=$( echo a; echo b; echo -n extra )
$ echo -n "${x%extra}" | xxd
0000000: 610a 620a a.b.
TL; DR
Добавьте дополнительную часть в конец вывода и заключите в кавычки переменные:
$ cat /no/file/here 2>&1 | xxd
0000000: 6361 743a 202f 6e6f 2f66 696c 652f 6865 cat: /no/file/he
0000010: 7265 3a20 4e6f 2073 7563 6820 6669 6c65 re: No such file
0000020: 206f 7220 6469 7265 6374 6f72 790a or directory.
$ cat /no/file/here 2>&1 | cksum
3561909523 46
$
$ var=$( cat /no/file/here 2>&1; rc=${?}; echo extra; exit ${rc})
$ echo $?
1
$
$ echo -n "${var%extra}" | xxd
0000000: 6361 743a 202f 6e6f 2f66 696c 652f 6865 cat: /no/file/he
0000010: 7265 3a20 4e6f 2073 7563 6820 6669 6c65 re: No such file
0000020: 206f 7220 6469 7265 6374 6f72 790a or directory.
$ echo -n "${var%extra}" | cksum
3561909523 46