Как избежать подстановки команды bash для удаления символа новой строки?

Чтобы ускорить выполнение некоторых сценариев bash, я хотел бы сохранить результат команды в переменной с помощью подстановки команд, но при подстановке команды символ новой строки 0x0A заменяется пробелом. Например:

a=`df -H`

or

a=$( df -H )

Когда я хочу продолжить обработку $a, символы новой строки заменяются пробелом, и все строки теперь находятся в одной строке, что намного сложнее использовать grep:

echo $a

Каковы были бы простые уловки, чтобы избежать удаления символа новой строки при подстановке команды?


person Laurent    schedule 03.03.2013    source источник
comment
Я обнаружил, что эта проблема также возникает при использовании eval.   -  person enharmonic    schedule 16.12.2020


Ответы (3)


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

Новые строки, которые вы ищете, есть, вы их просто не видите, потому что вы используете echo без кавычек для переменной.

Проверка:

$ a=$( df -H )
$ echo $a
Filesystem Size Used Avail Use% Mounted on /dev/sda3 276G 50G 213G 19% / udev 2.1G 4.1k 2.1G 1% /dev tmpfs 832M 820k 832M 1% /run none 5.3M 0 5.3M 0% /run/lock none 2.1G 320k 2.1G 1% /run/shm
$ echo "$a"
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3       276G   50G  213G  19% /
udev            2.1G  4.1k  2.1G   1% /dev
tmpfs           832M  820k  832M   1% /run
none            5.3M     0  5.3M   0% /run/lock
none            2.1G  320k  2.1G   1% /run/shm
$ 

Завершающие символы новой строки удалены.

Как правильно указал @ user4815162342, хотя новые строки в выводе не удаляются, завершающие новые строки удаляются с заменой команды. См. Эксперимент ниже:

$ a=$'test\n\n'
$ echo "$a"
test


$ b=$(echo "$a")
$ echo "$b"
test
$

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

Обходные пути

1. Добавьте фиктивный символ

В этом случае, как упоминалось в @Scrutinizer, вы можете использовать следующий обходной путь:

$ a=$(printf 'test\n\n'; printf x); a=${a%x}
$ echo "$a"
test


$ 

Пояснение: символ x добавляется к выводу (с использованием printf x) после новой строки. Поскольку новые строки больше не завершающие, они не удаляются при подстановке команд. Следующим шагом будет удаление добавленного нами x с помощью оператора % в ${a%x}. Теперь у нас есть исходный результат со всеми символами новой строки !!!

2. Прочтите, используя подстановку процесса

Вместо использования подстановки команд для присвоения выходных данных программы переменной, мы можем вместо этого использовать подстановка процесса для передачи вывода программы во встроенную команду read (указана ссылка на @ormaaj). Замена процесса сохраняет все новые строки. Чтение вывода в переменную немного сложно, но вы можете сделать это так:

$ IFS= read -rd '' var < <( printf 'test\n\n' ) 
$ echo "$var"
test


$ 

Пояснение:

  • Мы устанавливаем внутренний разделитель полей для команды чтения в нуль, с IFS=. В противном случае read не будет назначать var весь вывод, а только первый токен.
  • Мы вызываем read с параметрами -rd ''. r предназначен для предотвращения того, чтобы обратная косая черта действовала как специальный символ, а с d '' установить разделитель на ноль, так что read считывает весь вывод, а не только первую строку.

3. Читать с трубы

Вместо использования подстановки команд или процессов для присвоения выходных данных программы переменной, мы можем передать выходные данные программы команде read (указана @ormaaj). Конвейер также сохраняет все символы новой строки. Однако обратите внимание, что на этот раз мы установили необязательное поведение оболочки lastpipe, используя встроенная shopt. Это необходимо, чтобы команда read выполнялась в текущей среде оболочки. В противном случае переменная будет назначена в подоболочке и не будет доступна из остальной части скрипта.

$ cat test.sh 
#!/bin/bash
shopt -s lastpipe
printf "test\n\n" | IFS= read -rd '' var
echo "$var"
$ ./test.sh 
test


$
person user000001    schedule 03.03.2013
comment
Однако завершающие символы новой строки по-прежнему удаляются, и я боюсь, что этого нельзя избежать. (OP, вероятно, не заботится об этом, но это полезно знать в целом.) - person user4815162342; 03.03.2013
comment
Большое спасибо, очень поучительно - еще один пример сосредоточения внимания не на том месте, поскольку это была не подстановка команды, удаляющая новую строку. - person Laurent; 03.03.2013
comment
@ user4815162342 Спасибо за комментарий. Я обновил ответ для будущих читателей. - person user000001; 03.03.2013
comment
@ user4815162342: Интересно, неприятным решением может быть использование a=$(printf 'test\n\n'; printf x); echo "${a%x}" - person Scrutinizer; 03.03.2013
comment
Это не очень хорошее решение. Нет хорошего способа избежать удаления завершающих символов новой строки с помощью подстановки команд. Лучшее решение - использовать read с нестандартными параметрами для выполнения задания. shopt -s lastpipe; printf %s "$myData" | IFS= read -rd '' var. printf -v Bash также полезен в некоторых подобных ситуациях. - person ormaaj; 04.03.2013
comment
@ormaaj: Почему, по вашему мнению, это не лучший способ решения проблемы? Я согласен, что это неприятно, но что вы обнаружили, что не работает должным образом? - person Scrutinizer; 04.03.2013
comment
@Scrutinizer Он включает в себя два присваивания, и все эти манипуляции со строками только для решения проблемы не очень очевидны. Его не обязательно ломать, чтобы быть дерьмом. Подстановка команд в большинстве оболочек - это просто сахар для чтения из конвейера для начала, поэтому решение проблемы таким способом намного проще. На практике удаление завершающих символов новой строки обычно является желательной чертой, и я не могу сказать, что мне приходилось использовать это в реальном скрипте более нескольких раз. (Я могу вспомнить случай, когда этот кладж может быть предпочтительнее read, но он не относится к Bash.) - person ormaaj; 04.03.2013
comment
@ormaaj Я тоже добавил ваш обходной путь к ответу. Не стесняйтесь редактировать, если мое объяснение в чем-то неверно. Я заметил, что хотя в скрипте он работает нормально, в терминале он выводит только пустое поле вроде. Мне пришлось заключить его в ( ), чтобы он работал в терминале вот так: ( shopt -s lastpipe; printf 'test\n\n' | IFS= read -rd '' var; echo "$var"; ) - person user000001; 05.03.2013
comment
@ user000001 В Bash гораздо более распространено и с обратной совместимостью использовать подстановку процесса при перенаправлении, а не полагаться на lastpipe (хотя я обычно использую lastpipe в любом случае). Причина, по которой вы это замечаете, связана с контролем над работой. Интерактивные оболочки имеют set -m, что заставляет конвейеры работать в отдельных группах процессов, что требует наличия вспомогательной оболочки для последнего элемента. Поскольку управление заданиями не применяется к дочерним элементам подоболочки, обертывание всего в (...) - это обходной путь (который я почти всегда делаю при интерактивном тестировании в любом случае). - person ormaaj; 05.03.2013
comment
Примечание: read завершается с ненулевым значением при достижении EOF. Это приводит к тому, что методы 2 и 3 завершаются с ненулевым значением, таким образом маскируя статус выхода основной команды. В методе 1 использование main_cmd ; printf x также перезаписывает код выхода основных команд и мешает обработке ошибок. Попробуйте вместо этого main_cmd && printf x (если завершающие строки важны только при успешном выходе) или main_cmd ; ec=$?; printf x; exit $ec для сохранения конечных пробелов как в случае успеха, так и в случае ошибки. - person AnyDev; 01.10.2014
comment
@ormaaj Интересно, что через полтора года AndrDevEK обнаружил недостаток метода read. Он маскирует статус выхода. Прочтите комментарий выше. - person user000001; 24.11.2014
comment
@ user000001 Это ожидаемо и не является недостатком. Если вам необходимо получить доступ к статусу, вы можете получить его через массив PIPESTATUS. Это будет работать только в том случае, если вы действительно используете конвейер, поскольку bash не раскрывает статус выхода замен процесса. shopt -s lastpipe; { printf %s $'foo\nbar'; exit 3; } | IFS= read -rd '' x; printf '%q, %s\n' "$x" "${PIPESTATUS[0]}" - person ormaaj; 25.11.2014
comment
Всегда цитируйте свои переменные, дети - person giorgiosironi; 11.12.2017

Я пытался обдумать это, потому что использовал bash для потоковой передачи в результате запуска интерпретатора в сценарии F #. После некоторых проб и ошибок проблема была решена:

$ cat fsi.ch
#!/bin/bash
echo "$(fsharpi --quiet --exec --nologo $1)"

$ fsi.ch messages.fsx
Welcome to my program. Choose from the menu:
new | show | remove

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

person ワイきんぐ    schedule 10.07.2016

Еще один изящный трюк - использовать символ возврата каретки, который предотвращает удаление новой строки, но ничего не добавляет к выводу:

$ my_func_1 () {
>     echo "This newline is squashed"
> }
$ my_func_2 () {
>     echo "This newline is not squashed"
>     echo -n $'\r'
> }
$ echo -n "$(my_func_1)" && echo -n "$(my_func_2)" && echo done
This newline is squashedThis newline is not squashed
done
$

Но покупатель будьте осторожны: как упоминалось в комментариях, это может хорошо работать для вывода, который просто идет на терминал, но если вы передаете это другому процессу, вы можете запутать его, поскольку он, вероятно, не будет ожидать странного завершения '\r'.

person daphtdazz    schedule 22.10.2020
comment
Хороший трюк, если цель состоит в том, чтобы обрабатывать вывод только визуально, а не программно. Но если вы посмотрите на вывод echo -n "$(my_func_2)" | cat -A, вы увидите, что $'\r' сохраняется в выводе, поэтому это может вызвать проблемы, если процесс-получатель этого не ожидает. - person user000001; 23.10.2020