Несмотря на то, что @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