Wie kann ich eine nachgestellte Newline in Bash löschen?

10

Ich suche etwas, das sich wie das von Perl verhält chomp. Ich suche nach einem Befehl, der einfach seine Eingabe druckt, abzüglich des letzten Zeichens, wenn es sich um eine neue Zeile handelt:

$ 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

(Befehlsersetzung in Bash und Zsh löscht alle nachfolgenden neuen Zeilen, aber ich suche nach etwas, das höchstens eine nachfolgende neue Zeile löscht.)

Flimm
quelle

Antworten:

9

Das sollte funktionieren:

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

Das Skript druckt immer die vorherige anstelle der aktuellen Zeile, und die letzte Zeile wird anders behandelt.

Was es im Detail macht:

  1. NR>1{print PREV} Vorherige Zeile drucken (außer beim ersten Mal).
  2. {PREV=$0}Speichert die aktuelle Zeile in einer PREVVariablen.
  3. END{printf("%s",$0)} Drucken Sie abschließend die letzte Zeile ohne Zeilenumbruch.

Beachten Sie auch, dass dadurch höchstens eine leere Zeile am Ende entfernt wird (keine Unterstützung für das Entfernen "one\ntwo\n\n\n").

LatinSuD
quelle
15

Sie können verwenden perlohne 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

Aber warum nicht chompselbst verwenden:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"
cuonglm
quelle
4

Wenn Sie ein genaues Äquivalent zu wünschen chomp, ist die erste Methode, die mir in den Sinn kommt, die awk-Lösung, die LatinSuD bereits veröffentlicht hat . Ich werde einige andere Methoden hinzufügen, die nicht implementiert werden, chompaber einige allgemeine Aufgaben implementieren, chompdie häufig verwendet werden.

Wenn Sie Text in eine Variable einfügen, werden alle Zeilenumbrüche am Ende entfernt. Alle diese Befehle erzeugen also dieselbe einzeilige Ausgabe:

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"

Wenn Sie Text an die letzte Zeile einer Datei oder an die Ausgabe eines Befehls anhängen möchten, sedkann dies praktisch sein. Mit GNU sed und den meisten anderen modernen Implementierungen funktioniert dies auch dann, wenn die Eingabe nicht in einer neuen Zeile endet¹; Dies fügt jedoch keine neue Zeile hinzu, wenn noch keine vorhanden war.

sed '$ s/$/ done/'

¹ Dies funktioniert jedoch nicht bei allen sed-Implementierungen: sed ist ein Textverarbeitungswerkzeug, und eine Datei, die nicht leer ist und nicht mit einem Zeilenumbruchzeichen endet, ist keine Textdatei.

Gilles 'SO - hör auf böse zu sein'
quelle
Dies ist nicht genau gleichbedeutend mit chomp, da chomphöchstens eine nachfolgende Newline gelöscht wird.
Flimm
@Flimm Ja, das offensichtlichste genaue Äquivalent zu chompwäre die awk-Lösung, die LatinSuD bereits veröffentlicht hat. Aber in vielen Fällen chompist es nur ein Werkzeug, um einen Job zu erledigen, und ich biete Möglichkeiten, einige allgemeine Aufgaben zu erledigen. Lassen Sie mich meine Antwort aktualisieren, um dies zu klären.
Gilles 'SO - hör auf böse zu sein'
1

Ein anderer perlAnsatz. Dieser liest die gesamte Eingabe in den Speicher, sodass dies für große Datenmengen möglicherweise keine gute Idee ist (verwenden Sie cuonglm's oder den awkAnsatz dafür):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done
terdon
quelle
Danke, @ StéphaneChazelas, behoben. Aus irgendeinem Grund verwirrt mich dieser Schalter immer !
Terdon
0

Ich habe mir das irgendwo von einem Github-Repo geholt, kann aber nicht finden, wo

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
quelle
0

abstrakt

Zeilen ohne Zeilenumbruch drucken, nur dann eine Zeilenumbruch hinzufügen, wenn eine andere Zeile gedruckt werden soll.

$ printf 'one\ntwo\n' | 

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

one
two done

Andere Lösungen

Wenn wir mit einer Datei gearbeitet haben, können wir nur ein Zeichen davon abschneiden (wenn es in einer neuen Zeile endet):

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

Dies ist eine schnelle Lösung, da nur ein Zeichen aus der Datei gelesen und dann direkt ( truncate) entfernt werden muss, ohne die gesamte Datei zu lesen.

Während der Arbeit mit Daten aus stdin (einem Stream) müssen jedoch alle Daten gelesen werden. Und es wird "verbraucht", sobald es gelesen wird. Kein Backtrack (wie beim Abschneiden). Um das Ende eines Streams zu finden, müssen wir bis zum Ende des Streams lesen. Zu diesem Zeitpunkt gibt es keine Möglichkeit, zum Eingabestream zurückzukehren. Die Daten wurden bereits "verbraucht". Dies bedeutet, dass Daten in einer Art Puffer gespeichert werden müssen , bis wir mit dem Ende des Streams übereinstimmen und dann etwas mit den Daten im Puffer tun.

Die naheliegendste Lösung besteht darin, den Stream in eine Datei zu konvertieren und diese Datei zu verarbeiten. Aber die Frage fragt nach einer Art Filter des Streams. Nicht über die Verwendung zusätzlicher Dateien.

Variable

Die naive Lösung wäre, die gesamte Eingabe in eine Variable zu erfassen:

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

Erinnerung

Es ist möglich, mit sed eine ganze Datei in den Speicher zu laden. In sed ist es unmöglich, die nachfolgende neue Zeile in der letzten Zeile zu vermeiden. GNU sed vermeidet möglicherweise das Drucken einer nachfolgenden Zeilenumbruch, jedoch nur, wenn die Quelldatei bereits fehlt. Also, nein, einfaches Sed kann nicht helfen.

Außer auf GNU awk mit der -zOption:

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

printfSchlürfen Sie mit awk (any awk) den gesamten Stream und dies ohne die nachfolgende Newline.

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

Das Laden einer ganzen Datei in den Speicher ist möglicherweise keine gute Idee, da sie möglicherweise viel Speicher verbraucht.

Zwei Zeilen im Speicher

In awk können wir zwei Zeilen pro Schleife verarbeiten, indem wir die vorherige Zeile in einer Variablen speichern und die aktuelle drucken:

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

Direkte Bearbeitung

Aber wir könnten es besser machen.

Wenn wir die aktuelle Zeile ohne neue Zeile drucken und eine neue Zeile nur dann drucken, wenn eine nächste Zeile vorhanden ist, verarbeiten wir jeweils eine Zeile und die letzte Zeile enthält keine nachfolgende neue Zeile:

awk 'NR == 1 {printf ("% s", $ 0); next}; {printf ("\ n% s", $ 0)} '

Oder anders geschrieben:

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

Oder:

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

So:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
Isaac
quelle