Gdzie zniknął końcowy znak nowej linii od mojego zastąpienia polecenia?

15

Poniższy kod najlepiej opisuje sytuację. Dlaczego ostatni wiersz nie wyświetla końcowego znaku nowej linii? Wynik każdego wiersza jest pokazany w komentarzu. Używam GNU bash, wersja 4.1.5

     echo -n $'a\nb\n'                  | xxd -p  # 610a620a  
           x=$'a\nb\n'   ; echo -n "$x" | xxd -p  # 610a620a
     echo -ne "a\nb\n"                  | xxd -p  # 610a620a
x="$(echo -ne "a\nb\n")" ; echo -n "$x" | xxd -p  # 610a62
Peter.O
źródło
4
Obejście tego rzadkiego czasu, gdy jest to potrzebne:tmp=$(somecommand; echo a); tmp=${tmp%a}
Gilles 'SO- przestań być zły'
Zobacz także Dlaczego podstawianie poleceń powłoki pożera znak końca nowej linii?
Gilles 'SO - przestań być zły'
1
@Gilles: Powtórz swój przykład powyżej: tmp=$(somecommand; echo a)... To z pewnością doprowadziło do sedna sprawy ... Dopóki nie zobaczyłem przykładu, moja tendencja nadal byłaby wykorzystywana echo -n a... ale oczywiście !, nie ma potrzeby -n, ponieważ Komenda Zmiana usunie wprowadzony spływu przełamane w każdym razie! ... dzięki ...
Peter.O
stackoverflow.com/questions/613572/…
sancho.s Przywróć Monikę

Odpowiedzi:

18

Funkcja podstawiania poleceń $()(i jej kuzyn backstick) specjalnie usuwa końcowe znaki nowej linii. Jest to udokumentowane zachowanie i zawsze powinieneś być tego świadomy podczas korzystania z konstrukcji.

Nowe linie w treści tekstu nie są usuwane przez operator podstawiania, ale mogą być również usuwane podczas dzielenia słów na powłoce, więc to, jak się to okaże, zależy od tego, czy użyłeś cudzysłowów, czy nie. Zwróć uwagę na różnicę między tymi dwoma zastosowaniami:

$ echo -n "$(echo -n 'a\nb')"
a
b

$ !! | xxd -p
610a62

$ echo -n  $(echo -n 'a\nb')
a b

$ !! | xxd -p   
612062

W drugim przykładzie wynik nie był cytowany, a nowy wiersz został zinterpretowany jako podział na słowa, dzięki czemu pojawił się na wyjściu jako spacja!

Caleb
źródło
1
Dzięki Caleb .. Byłem świadomy dzielenia słów zmieniających białe znaki na pojedynczą spację, gdy nie cytowałem ... Dlatego byłem najbardziej zaskoczony widząc, że moja nowa linia zniknęła, mimo że to zacytowałem ... Teraz jestem świadomy, że
dzieje się
6

Podczas korzystania z podstawiania poleceń powłoka wykonuje polecenia w podpowłoce, zwracając swoje standardowe wyjście. w tym procesie znaki IFS tracą swoje znaczenie (jeśli nie są cytowane), ponieważ polecenie zwraca zwykłe słowa podzielone, więc końcowe są usuwane. Na przykład:

$ echo "$(echo -e '\n')" | wc
 1       0       1

$ echo -e '\n' | wc
2       0       2

i bardziej praktycznie pwdbędzie działać, nawet jeśli nazwa katalogu ma między sobą nowy wiersz, ale $(pwd)nie zadziała.

Zwykłym obejściem jest dodanie czegoś na końcu polecenia i usunięcie go później.

Philomath
źródło
1
Dzięki Philomath ... tak przy okazji, pierwszy przykład jest niepoprawny pod względem składniowym („wc” nie przyjmuje ciągu jako argumentu). Aby przykłady miały sens, pierwszym może być echo "$(echo -e '\n')" | wc, który wyprowadza 1   0   1, w porównaniu do2   0   2
Peter.O
@fred: Ups, tylko literówka.
Philomath
1
„konwersja na spacje” nie jest właściwym wyjaśnieniem, jest tylko pewne rozszczepienie. W szczególności możesz usunąć spację z IFS, które nie będą działać jako separatory. Co więcej, twój przykład należy do specjalnej kategorii przypadków i istnieje różnica między $(pwd)i "$(pwd)", patrz odpowiedź Caleba.
Stéphane Gimenez
PS .. Twoja sugestia dotycząca zwykłego obejścia jest właśnie tym, czego potrzebowałem, aby to wyjaśnić ..
Peter.O
Re „konwertowane na spacje”, patrz Podział słów
Peter.O