Jak mogę usunąć końcowy znak nowej linii w bash?

10

Szukam czegoś, co zachowuje się jak Perl chomp. Szukam polecenia, które po prostu wypisuje dane wejściowe, minus ostatni znak, jeśli jest to nowy wiersz:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(Podstawianie poleceń w Bash i Zsh usuwa wszystkie końcowe nowe linie, ale szukam czegoś, co usunie co najwyżej jedną końcową nową linię.)

Flimm
źródło

Odpowiedzi:

9

To powinno działać:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Skrypt zawsze drukuje poprzednią linię zamiast bieżącej, a ostatnia linia jest traktowana inaczej.

Co robi bardziej szczegółowo:

  1. NR>1{print PREV} Wydrukuj poprzedni wiersz (oprócz pierwszego).
  2. {PREV=$0}Przechowuje bieżącą linię w PREVzmiennej.
  3. END{printf("%s",$0)} Na koniec wydrukuj ostatni wiersz bez podziału linii.

Zauważ też, że spowoduje to usunięcie maksymalnie jednej pustej linii na końcu (brak obsługi usuwania "one\ntwo\n\n\n").

LatinSuD
źródło
15

Możesz używać perlbez chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Ale dlaczego nie użyć chompsamego siebie:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"
Cuonglm
źródło
4

Jeśli potrzebujesz dokładnego odpowiednika chomp, pierwszą metodą, która przychodzi mi do głowy, jest awk, które już opublikował LatinSuD . Dodam kilka innych metod, które nie implementują, chompale implementują niektóre typowe zadania, do których chompczęsto się stosuje.

Po wstawieniu tekstu do zmiennej wszystkie znaki nowej linii na końcu są usuwane. Więc wszystkie te polecenia generują to samo wyjście w jednym wierszu:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

Jeśli chcesz dołączyć tekst do ostatniego wiersza pliku lub wyniku polecenia, sedmoże być wygodne. Z GNU sed i większością innych nowoczesnych implementacji działa to nawet, jeśli dane wejściowe nie kończą się na nowej linii¹; nie spowoduje to jednak dodania nowego wiersza, jeśli jeszcze go nie było.

sed '$ s/$/ done/'

¹ Jednak to nie działa ze wszystkimi implementacjami sed: sed jest narzędziem do przetwarzania tekstu, a plik, który nie jest pusty i nie kończy się znakiem nowej linii, nie jest plikiem tekstowym.

Gilles „SO- przestań być zły”
źródło
Nie jest to dokładnie równoważne chomp, ponieważ chompusuwa tylko co najwyżej jedną końcową linię.
Flimm
@Flimm Tak, najbardziej oczywistym dokładnym odpowiednikiem chompbyłoby rozwiązanie awk, które LatinSuD już opublikowało. Ale w wielu przypadkach chompjest tylko narzędziem do wykonania pracy, a ja zapewniam sposoby wykonywania niektórych typowych zadań. Pozwól, że zaktualizuję moją odpowiedź, aby to wyjaśnić.
Gilles 'SO - przestań być zły'
1

Inne perlpodejście. Ten odczytuje cały zapis do pamięci, więc może nie być dobrym pomysłem dla dużych ilości danych (użyj cuonglm lub awkpodejście do tego):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done
terdon
źródło
Dzięki, @ StéphaneChazelas, naprawione. Z jakiegoś powodu ten przełącznik zawsze mnie myli !
terdon
0

Zrobiłem to gdzieś z repozytorium github, ale nie mogę znaleźć gdzie

delete-trailing-blank-lines-sed

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson ([email protected])
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
Brad Parks
źródło
0

abstrakcyjny

Drukuj linie bez nowej linii, dodaj nową linię tylko wtedy, gdy jest inna linia do wydrukowania.

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

Inne rozwiązania

Jeśli pracujemy z plikiem, możemy po prostu obciąć z niego jeden znak (jeśli kończy się na nowej linii):

removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || truncate -s-1 "$ 1"; }

Jest to szybkie rozwiązanie, ponieważ musi odczytać tylko jeden znak z pliku, a następnie usunąć go bezpośrednio ( truncate) bez czytania całego pliku.

Jednak podczas pracy z danymi ze standardu (strumienia) dane muszą zostać odczytane. I jest „konsumowany”, jak tylko zostanie odczytany. Brak powrotu (jak w przypadku obcięcia). Aby znaleźć koniec strumienia, musimy przeczytać do końca strumienia. W tym momencie nie ma możliwości powrotu do strumienia wejściowego, dane zostały już „zużyte”. Oznacza to, że dane muszą być przechowywane w jakiejś formie bufora, dopóki nie dopasujemy końca strumienia, a następnie nie zrobimy czegoś z danymi w buforze.

Najbardziej oczywistym rozwiązaniem jest konwersja strumienia do pliku i przetworzenie tego pliku. Ale pytanie wymaga jakiegoś filtru strumienia. Nie chodzi o wykorzystanie dodatkowych plików.

zmienna

Naiwnym rozwiązaniem byłoby przechwycenie całego wkładu do zmiennej:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

pamięć

Możliwe jest załadowanie całego pliku do pamięci za pomocą sed. W sed nie można uniknąć końcowego znaku nowej linii w ostatnim wierszu. GNU sed może uniknąć drukowania końcowego znaku nowej linii, ale tylko wtedy, gdy brakuje go w pliku źródłowym. Więc nie, prosty sed nie może pomóc.

Z wyjątkiem GNU awk z -zopcją:

sed -z 's/\(.*\)\n$/\1/'

Za pomocą awk (dowolnego awk), slurpuj cały strumień i printfto bez końcowego znaku nowej linii.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

Ładowanie całego pliku do pamięci może nie być dobrym pomysłem, może zajmować dużo pamięci.

Dwie linie w pamięci

W awk możemy przetwarzać dwie linie na pętlę, przechowując poprzednią linię w zmiennej i drukując obecną:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

Bezpośrednie przetwarzanie

Ale moglibyśmy zrobić lepiej.

Jeśli wydrukujemy obecną linię bez nowej linii i wydrukujemy nową linię tylko wtedy, gdy istnieje kolejna linia, przetwarzamy jedną linię na raz, a ostatnia linia nie będzie miała nowej linii końcowej:

awk 'NR == 1 {printf ("% s", 0 $); dalej}; {printf („\ n% s”, 0 USD)} ”

Lub napisane w inny sposób:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

Lub:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Więc:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
Izaak
źródło