Jak sprawić, by operator back -ick bash utrzymywał nowe wiersze w wynikach?

36

Czy istnieje sposób na to, aby bash nie zjadał nowych linii w wyniku zamiany wstecz?

Na przykład:

var=`echo line one && echo line two`
echo $var

line one line two

Chcę to

var=`echo line one && echo line two` # plus some magic
echo $var

line one
line two
slowpoison
źródło

Odpowiedzi:

54

Nie jest to problem z substytucją backicks, ale z echo; musisz zacytować zmienną, aby znaki sterujące działały:

$ var=`echo line one && echo line two`
$ echo "$var"
line one
line two
Cyrus
źródło
Więc bash usuwa znaki nowej linii podczas rozwijania zmiennej w zastawie poleceń? I to zachowanie można wyłączyć, cytując zmienną?
slowpoison
7
bashEkspansja wykonuje parametrów przed wykonaniem polecenia, powołując należy poinformować bash, że chcesz wydrukować jeden ciąg, a nie 4 słowa ( line, one, linei two). Spróbuj: echo a <many spaces> bi echo "a <many spaces> b". Spójrz również na tę odpowiedź .
cyr
5

Mimo że @cyrus jest poprawny - tak naprawdę nie odpowiada na całe pytanie i nie ma wyjaśnienia, co się dzieje.

Przejdźmy więc to.

Nowe linie w ciągu

Najpierw określ oczekiwaną sekwencję bajtów:

$ { echo a; echo b; } | xxd
0000000: 610a 620a                                a.b.

Teraz użyj polecenia Podstawienie komendy (sekcja 3.5.4), aby spróbować przechwycić tę sekwencję bajtów:

$ x=$( echo a; echo b; )

Następnie wykonaj proste echo, aby sprawdzić sekwencję bajtów:

$ echo $x | xxd
0000000: 6120 620a                                a b.

Wygląda na to, że pierwsza nowa linia została zastąpiona spacją, a druga nowa linia pozostała nienaruszona. Ale dlaczego ?

Przyjrzyjmy się, co się tu właściwie dzieje:

Po pierwsze, bash wykona rozszerzenie parametru powłoki (rozdział 3.5.3)

Znak „$” wprowadza interpretację parametrów, podstawianie poleceń lub interpretację arytmetyczną. Nazwa parametru lub symbol, który ma zostać rozwinięty, może być ujęty w nawiasy klamrowe, które są opcjonalne, ale służą do ochrony zmiennej, która ma zostać rozwinięta przed znakami bezpośrednio po niej, które mogą być interpretowane jako część nazwy.

Następnie bash wykona dzielenie wyrazów (rozdział 3.5.7)

Powłoka skanuje wyniki interpretacji parametrów, podstawiania poleceń i interpretacji arytmetycznej, które nie wystąpiły w ramach podwójnych cudzysłowów dla podziału słów.

Powłoka traktuje każdy znak $ IFS jako ogranicznik i dzieli wyniki pozostałych rozszerzeń na słowa na tych znakach. Jeśli IFS jest rozbrojony lub jego wartość jest dokładnie, ...

Następnie bash potraktuje to jako proste polecenie (rozdział 3.2.1)

Proste polecenie to najczęściej spotykane polecenie. To tylko sekwencja słów oddzielona spacjami, zakończona jednym z operatorów sterujących powłoki (patrz Definicje). Pierwsze słowo zazwyczaj określa polecenie do wykonania, a pozostałe słowa to argumenty tego polecenia.

Definicja pustych miejsc (sekcja 2 - Definicje)

pusty Znak spacji lub tabulacji.

Wreszcie bash powołuje się na echo (sekcja 4.2 - Bash Builtin Commands) polecenie wewnętrzne

... Wyprowadzaj argumenty, oddzielone spacjami, zakończone znakiem nowej linii. ...

Podsumowując, nowe wiersze są usuwane przez podział tekstu, a następnie echo dostaje 2 argumenty, „a” i „b”, a następnie wyprowadza je oddzielone spacjami i kończąc na nowej linii.

Robiąc to, co sugerował @cyrus (i pomijaj echo nowej linii za pomocą -n), wynik jest lepszy:

$ echo -n "$x" | xxd
0000000: 610a 62                                  a.b

Nowe linie na końcu łańcucha

Wciąż jednak nie jest idealny, nowa linia zniknęła. Przyglądając się bliżej podstawieniu poleceń (sekcja 3.5.4) :

Bash wykonuje rozwinięcie, wykonując polecenie i zastępując podstawienie polecenia standardowym wyjściem polecenia, a wszelkie końcowe znaki nowej linii są usuwane.

Teraz, gdy jest jasne, dlaczego nowa linia odchodzi, bash można oszukać. Aby to zrobić, dodaj dodatkowy ciąg na końcu i usuń go, gdy używasz zmiennej:

$ x=$( echo a; echo b; echo -n extra )
$ echo -n "${x%extra}" | xxd
0000000: 610a 620a                                a.b.

TL; DR

Dodaj dodatkową część na końcu danych wyjściowych i cytuj zmienne:

$ 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
Iwan Aucamp
źródło
0

Umieściłbym mój mały dodatek w komentarzu do odpowiedzi cYrus, ale myślę, że nie mogę jeszcze komentować odpowiedzi. Powtórzę więc część jego odpowiedzi dla kompletności.

W pierwszym wierszu kodu dzieje się tak, że podstawienie wstecznego zjada końcowe znaki wyjściowe podstawionego polecenia, jak udokumentowano . Ale nie zjada nowej linii między twoimi dwiema liniami.

W drugim wierszu kodu następuje echo z dwoma argumentami, pierwszym i drugim wierszem. Cytując zmienną rozwiniętą, naprawisz to: echo "$var"Pozwoli to zachować nową linię między linią pierwszą a linią drugą. I echodomyślnie doda końcowy znak nowej linii, aby wszystko było w porządku.

Teraz, jeśli chcesz zachować wszystkie końcowe znaki nowego wiersza w wyniku niektórych podstawień poleceń, obejście polega na dodaniu kolejnego wiersza wyniku, który możesz usunąć po podstawieniu polecenia, zobacz tutaj .

gaston
źródło