Як уникнути заміни команди bash, щоб видалити символ нового рядка?


77

Щоб пришвидшити виконання сценарію bash, я хотів би зберегти результат команди у змінній за допомогою заміни команди, але заміна команди замінює 0x0Aсимвол нового рядка пробілом. Наприклад:

a=`df -H`

або

a=$( df -H )

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

echo $a

Які б були легкі хитрощі, щоб уникнути видалення символу нового рядка за допомогою заміни команди?


Я виявив, що ця проблема також виникає під час використання eval.
енгармонія

Відповіді:


119

Невідстежувані нові рядки не видаляються

Нові рядки, які ви шукаєте, є, ви просто їх не бачите, оскільки використовуєте 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доданого нами за допомогою%оператора in${a%x}. Тепер ми маємо оригінальний вихід, з усіма новими рядками !!!

2. Читання з використанням заміни процесу

Замість того, щоб використовувати заміну команди для призначення виходу програми змінної, ми можемо замість цього замінити процес, щоб подати вихід програми readвбудованій команді (кредит @ormaaj ). Заміна процесу зберігає всі нові рядки. Читання вихідних даних для змінної трохи складно, але ви можете зробити це так:

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


$ 

Пояснення:

  • Ми встановлюємо внутрішній роздільник полів для команди читання як нуль, за допомогою IFS=. Інакше readне присвоїв би весь вихід var, а лише перший маркер.
  • Ми використовуємо readваріанти -rd ''. Служить rдля запобігання зворотній косою рисою виступати в ролі спеціального символу та d ''встановити роздільник на нуль, щоб читання читало весь вивід, а не лише перший рядок.

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


$

3
У завершальні символи нового рядка по - , як і раніше видалені, хоча, і я боюся , що це не може бути попереджено. (ОП, мабуть, не піклується про них, але це добре знати загалом.)
user4815162342 03.03.13

6
@ user4815162342: Цікаво, може бути корисним хрустка роботаa=$(printf 'test\n\n'; printf x); echo "${a%x}"
Перевірка

1
Це не дуже хороший обхідний шлях. Немає хорошого способу уникнути зачищення нових рядків із заміною команди. Найкращим рішенням є використання readнестандартних опцій для виконання завдання. shopt -s lastpipe; printf %s "$myData" | IFS= read -rd '' var. Баш printf -vтакож корисний у деяких подібних ситуаціях.
ormaaj 03.03.13

1
@ user000001 Набагато частіше і зворотно сумісний у Bash використання заміни процесу в редиректі, а не покладатися на нього lastpipe(хоча я зазвичай lastpipeвсе-таки використовую ). Причина, яку ви помічаєте, пов’язана з контролем роботи. Інтерактивні оболонки мають можливість set -mзмушувати конвеєри працювати в окремих групах процесів, що вимагає допоміжної оболонки для останнього елемента. Оскільки контроль за роботою не стосується дочірньої частини оболонки, обгортання всього (...)є обхідним шляхом (що я майже завжди роблю при інтерактивному тестуванні).
ormaaj

5
Примітка: readвиходить із ненульовим значенням, коли потрапляє на EOF. Це призводить до виходу методів 2 і 3 з ненульовим значенням, отже, маскуючи стан виходу основної команди. У методі 1 використання main_cmd ; printf xтакож перезаписує головні команди коду виходу та заважає обробці помилок. Спробуйте main_cmd && printf xзамість цього (якщо кінцеві рядки важливі лише при виході з успіху) або main_cmd ; ec=$?; printf x; exit $ecдля збереження кінцевих пробілів як у випадках успіху, так і у випадках помилок.
AnyDev

1

Я намагався обернутися головою навколо цього, оскільки використовував 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

Припускаючи, звичайно, що вам потрібно запустити програму терміналу. Сподіваюся, це допомагає.


1

Інший "акуратний фокус" полягає у використанні символу повернення каретки, який запобігає зачищенню нового рядка, але нічого не додає до результату:

$ 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'.


1
Гарний фокус, якщо метою є обробка вихідних даних лише візуально, а не програмно. Але якщо ви подивитесь на вихідні дані, echo -n "$(my_func_2)" | cat -Aто побачите, що вони $'\r'зберігаються у вихідних даних, тому це може спричинити проблеми, якщо процес приймача цього не очікує.
user000001
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.