Wie kann ich eine neue Zeile löschen, wenn es sich um das letzte Zeichen in einer Datei handelt?

164

Ich habe einige Dateien, bei denen ich den letzten Zeilenumbruch löschen möchte, wenn es sich um das letzte Zeichen in einer Datei handelt. od -czeigt mir, dass der von mir ausgeführte Befehl die Datei mit einer nachgestellten neuen Zeile schreibt:

0013600   n   t  >  \n

Ich habe ein paar Tricks mit sed ausprobiert, aber das Beste, was ich mir vorstellen kann, ist, den Trick nicht zu machen:

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

Irgendwelche Ideen, wie das geht?

Todd Partridge 'Gen2ly'
quelle
4
Zeilenumbruch ist nur ein Zeichen für Unix-Zeilenumbrüche. DOS-Zeilenumbrüche bestehen aus zwei Zeichen. Das Literal "\ n" besteht natürlich aus zwei Zeichen. Nach was suchst du eigentlich?
Bis auf weiteres angehalten.
3
Obwohl die Darstellung sein könnte, \nist in Linux ein Zeichen
Pavium
10
Können Sie näher erläutern, warum Sie dies tun möchten? Textdateien sollen mit einem Zeilenende enden, es sei denn, sie sind vollständig leer. Es kommt mir seltsam vor, dass Sie eine so abgeschnittene Datei haben möchten?
Thomas Padron-McCarthy
Der übliche Grund für so etwas ist das Löschen eines nachgestellten Kommas aus der letzten Zeile einer CSV-Datei. Sed funktioniert gut, aber Zeilenumbrüche müssen anders behandelt werden.
Pavium
9
@ ThomasPadron-McCarthy "Beim Rechnen gibt es aus jedem guten Grund, etwas zu tun, einen guten Grund, es nicht zu tun und umgekehrt." -Jesus - "das solltest du nicht tun" ist eine schreckliche Antwort, egal welche Frage. Das richtige Format ist: [wie es zu tun] , sondern [warum es kann schlechte Idee sein]. #sacrilege
Cory Mawhorter

Antworten:

225
perl -pe 'chomp if eof' filename >filename2

oder, um die Datei an Ort und Stelle zu bearbeiten:

perl -pi -e 'chomp if eof' filename

[Anmerkung der Redaktion: -pi -e war ursprünglich -pie, aber, wie von mehreren Kommentatoren bemerkt und von @hvd erklärt, funktioniert letzteres nicht.]

Dies wurde auf der awk-Website, die ich gesehen habe, als "Perl-Blasphemie" beschrieben.

Aber in einem Test hat es funktioniert.

Pavium
quelle
11
Sie können es sicherer machen, indem Sie verwenden chomp. Und es ist besser als die Datei zu schlürfen.
Sinan Ünür
6
Blasphemie ist zwar, aber es funktioniert sehr gut. perl -i -pe 'chomp if eof' Dateiname. Vielen Dank.
Todd Partridge 'Gen2ly'
13
Das Lustige an Blasphemie und Häresie ist, dass es normalerweise gehasst wird, weil es richtig ist. :)
Ether
8
Kleine Korrektur: Sie können verwenden perl -pi -e 'chomp if eof' filename, um eine Datei direkt zu bearbeiten, anstatt eine temporäre Datei zu erstellen
Romuald Brunet
8
perl -pie 'chomp if eof' filename-> Perl-Skript "chomp if eof" kann nicht geöffnet werden: Keine solche Datei oder kein solches Verzeichnis; perl -pi -e 'chomp if eof' filename-> funktioniert
Aditsu beendet, weil SE
57

Sie können die Tatsache nutzen, dass Shell- Befehlsersetzungen nachgestellte Zeilenumbrüche entfernen :

Einfache Form, die in bash, ksh, zsh funktioniert:

printf %s "$(< in.txt)" > out.txt

Tragbare (POSIX-kompatible) Alternative (etwas weniger effizient):

printf %s "$(cat in.txt)" > out.txt

Hinweis:

  • Wenn in.txtEnden mit mehreren Zeilenumbrüche entfernt der Befehl Substitution alle von ihnen - danke, @Sparhawk. (Es werden keine anderen Leerzeichen als nachgestellte Zeilenumbrüche entfernt.)
  • Da dieser Ansatz die gesamte Eingabedatei in den Speicher liest , ist dies nur für kleinere Dateien ratsam.
  • printf %sstellt sicher, dass kein Zeilenumbruch an die Ausgabe angehängt wird (dies ist die POSIX-kompatible Alternative zum nicht standardmäßigen echo -n; siehe http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html und https: //unix.stackexchange). com / a / 65819 )

Eine Anleitung zu den anderen Antworten :

  • Wenn Perl verfügbar ist, wählen Sie die akzeptierte Antwort - sie ist einfach und speichereffizient (liest nicht die gesamte Eingabedatei auf einmal).

  • Andernfalls betrachten Sie die Awk- Antwort von ghostdog74 - sie ist dunkel, aber auch speichereffizient . Ein besser lesbares Äquivalent (POSIX-kompatibel) ist:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • Der Druckvorgang wird um eine Zeile verzögert, sodass die letzte Zeile im ENDBlock verarbeitet werden kann, wo sie ohne Nachstellen gedruckt wird \n, da das Trennzeichen für den Ausgabedatensatz ( OFS) auf eine leere Zeichenfolge gesetzt ist.
  • Wenn Sie eine ausführliche, aber schnelle und robuste Lösung suchen , die wirklich direkt bearbeitet wird (im Gegensatz zum Erstellen einer temporären Datei, die dann das Original ersetzt), sollten Sie das Perl-Skript von jrockway in Betracht ziehen .

mklement0
quelle
3
Hinweis: Wenn sich am Ende der Datei mehrere Zeilenumbrüche befinden, werden mit diesem Befehl alle gelöscht.
Sparhawk
47

Sie können dies mit headGNU-Coreutils tun. Es unterstützt Argumente, die sich auf das Ende der Datei beziehen. So lassen Sie die letzte Byte-Verwendung aus:

head -c -1

Zum Testen auf eine endende Newline können Sie tailund verwenden wc. Im folgenden Beispiel wird das Ergebnis in einer temporären Datei gespeichert und anschließend das Original überschrieben:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

Sie können auch spongevon verwenden moreutils, um "an Ort und Stelle" zu bearbeiten:

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

Sie können auch eine allgemeine wiederverwendbare Funktion .bashrcerstellen, indem Sie diese in Ihre Datei einfügen:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

Aktualisieren

Wie von Karl Wilbur in den Kommentaren erwähnt und in Sorentars Antwort verwendet , truncate --size=-1kann er die direktehead -c-1 Bearbeitung ersetzen und unterstützen.

Thor
quelle
3
Beste Lösung von allen bisher. Verwendet ein Standardtool, das wirklich jede Linux-Distribution hat und das präzise und klar ist, ohne Sed- oder Perl-Assistenten.
Dakkaron
2
Schöne Lösung. Eine Änderung ist, dass ich denke, ich würde sie truncate --size=-1stattdessen verwenden, head -c -1da sie nur die Größe der Eingabedatei ändert, anstatt die Eingabedatei einzulesen, sie in eine andere Datei zu schreiben und dann das Original durch die Ausgabedatei zu ersetzen.
Karl Wilbur
1
Beachten Sie, dass head -c -1das letzte Zeichen entfernt wird, unabhängig davon, ob es sich um eine neue Zeile handelt oder nicht. Deshalb müssen Sie überprüfen, ob das letzte Zeichen eine neue Zeile ist, bevor Sie es entfernen.
wisbucky
Funktioniert leider nicht auf Mac. Ich vermute, dass es bei keiner BSD-Variante funktioniert.
Edward Falk
16
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Bearbeiten 2:

Hier ist eine awkVersion (korrigiert) , die kein potenziell großes Array ansammelt:

awk '{if (line) print line; line = $ 0} END {printf $ 0} 'abc

Bis auf weiteres angehalten.
quelle
Gute originelle Art, darüber nachzudenken. Danke Dennis.
Todd Partridge 'Gen2ly'
Du hast Recht. Ich verlasse mich auf Ihre awkVersion. Es dauert zwei Offsets (und einen anderen Test) und ich habe nur einen verwendet. Sie können jedoch printfanstelle von verwenden ORS.
Bis auf weiteres angehalten.
Sie können die Ausgabe zu einer Pipe mit Prozessersetzung machen:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates
2
Die Verwendung von -c anstelle von -n für Kopf und Schwanz sollte noch schneller sein.
Rudimeier
1
Für mich hat head -n -1 abc die letzte tatsächliche Zeile der Datei entfernt und eine nachfolgende neue Zeile hinterlassen. Kopf -c -1 abc schien besser zu funktionieren
ChrisV
10

gaffen

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
Ghostdog74
quelle
Sieht für mich immer noch nach vielen Charakteren aus ... lerne es langsam :). Macht den Job aber. Danke Ghostdog.
Todd Partridge 'Gen2ly'
1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' fileDies sollte leichter zu lesen sein.
Yevhen Pavliuk
Wie wäre es mit : awk 'NR>1 {print p} {p=$0} END {printf $0}' file.
Isaac
@sorontar Das erste Argument für printfdas ist Format Argument. Wenn die Eingabedatei also etwas hätte, das als Formatbezeichner interpretiert werden könnte %d, würde eine Fehlermeldung angezeigt. Eine Lösung wäre, es inprintf "%s" $0
Robin A. Meade
9

Eine sehr einfache Methode für einzeilige Dateien, für die GNU-Echo von coreutils erforderlich ist:

/bin/echo -n $(cat $file)
otheral
quelle
Dies ist ein anständiger Weg, wenn es nicht zu teuer ist (sich wiederholend).
Dies hat Probleme, wenn \nvorhanden. Da wird es in eine neue Zeile umgewandelt.
Chris Stryczynski
Scheint auch für mehrzeilige Dateien zu funktionieren, wenn es $(...)zitiert wird
Thor
/bin/echo -n "$(cat infile)" Ich muss das definitiv zitieren ... Außerdem bin ich mir nicht sicher, wie echohoch die maximale Länge oder die Shell in den Betriebssystemen / Shell-Versionen / Distributionen sein würde (ich habe nur gegoogelt und es war ein Kaninchenbau), also bin ich es Ich bin mir nicht sicher, wie portabel (oder performant) es tatsächlich für etwas anderes als kleine Dateien wäre - aber für kleine Dateien großartig.
Michael
8

Wenn du es richtig machen willst, brauchst du so etwas:

use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

Wir öffnen die Datei zum Lesen und Anhängen. Öffnen zum Anhängen bedeutet, dass wir bereits seekam Ende der Datei sind. Wir erhalten dann die numerische Position des Dateiende mit tell. Wir verwenden diese Zahl, um ein Zeichen zu suchen, und lesen dann dieses eine Zeichen. Wenn es sich um eine neue Zeile handelt, kürzen wir die Datei auf das Zeichen vor dieser neuen Zeile, andernfalls tun wir nichts.

Dies läuft in konstanter Zeit und konstantem Speicherplatz für jede Eingabe und erfordert auch keinen weiteren Speicherplatz.

Jrockway
quelle
2
Dies hat jedoch den Nachteil, dass die Eigentumsrechte / Berechtigungen für die Datei nicht zurückgesetzt werden ... ähm, warten Sie ...
2.
1
Ausführlich, aber sowohl schnell als auch robust - scheint hier die einzig wahre Antwort auf die Bearbeitung von Dateien zu sein (und da dies möglicherweise nicht für alle offensichtlich ist: Dies ist ein Perl- Skript).
mklement0
7

Eine schnelle Lösung ist die Verwendung des Dienstprogramms gnu truncate:

[ -z $(tail -c1 file) ] && truncate -s-1 file

Der Test ist wahr, wenn die Datei eine nachfolgende neue Zeile enthält.

Das Entfernen ist sehr schnell, wirklich vorhanden, es wird keine neue Datei benötigt und die Suche liest auch am Ende nur ein Byte ( tail -c1).

Isaac
quelle
1
abschneiden: fehlender Dateioperand
Brian Hannay
2
es fehlt nur der nachfolgende Dateiname im Beispiel, dh [ -z $(tail -c1 filename) ] && truncate -s -1 filename(auch als Antwort auf den anderen Kommentar truncatefunktioniert der Befehl nicht mit stdin, ein Dateiname ist erforderlich)
michael
6

Hier ist eine schöne, ordentliche Python-Lösung. Ich habe nicht versucht, hier knapp zu werden.

Dadurch wird die Datei an Ort und Stelle geändert, anstatt eine Kopie der Datei zu erstellen und die neue Zeile aus der letzten Zeile der Kopie zu entfernen. Wenn die Datei groß ist, ist dies viel schneller als die Perl-Lösung, die als beste Antwort ausgewählt wurde.

Es schneidet eine Datei um zwei Bytes ab, wenn die letzten zwei Bytes CR / LF sind, oder um ein Byte, wenn das letzte Byte LF ist. Es wird nicht versucht, die Datei zu ändern, wenn die letzten Bytes nicht (CR) LF sind. Es behandelt Fehler. Getestet in Python 2.6.

Fügen Sie dies in eine Datei mit dem Namen "striplast" und ein chmod +x striplast.

#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

PS Im Geiste von "Perl Golf" ist hier meine kürzeste Python-Lösung. Es schlürft die gesamte Datei von der Standardeingabe in den Speicher, entfernt alle Zeilenumbrüche am Ende und schreibt das Ergebnis in die Standardausgabe. Nicht so knapp wie der Perl; Sie können Perl einfach nicht für kleine knifflige schnelle Sachen wie diese schlagen.

Entfernen Sie das "\ n" aus dem Aufruf von .rstrip()und es wird der gesamte Leerraum vom Ende der Datei entfernt, einschließlich mehrerer Leerzeilen.

Fügen Sie dies in "slurp_and_chomp.py" ein und führen Sie es aus python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
steveha
quelle
os.path.isfile () informiert Sie über das Vorhandensein von Dateien. Die Verwendung von try /
Except kann
4

Noch ein Perl WTDI:

perl -i -p0777we's/\n\z//' filename
ysth
quelle
3
$ perl -e 'local $ /; $ _ = <>; s / \ n $ //; print 'a-text-file.txt

Siehe auch Übereinstimmen mit einem beliebigen Zeichen (einschließlich Zeilenumbrüchen) in sed .

Sinan Ünür
quelle
1
Das bringt alle Zeilenumbrüche raus. Entsprichttr -d '\n'
Bis auf weiteres angehalten.
Dies funktioniert auch gut, wahrscheinlich weniger blasphemisch als bei Paviums.
Todd Partridge 'Gen2ly'
Sinan, obwohl Linux und Unix möglicherweise Textdateien definieren, die mit einem Zeilenumbruch enden, stellt Windows keine solche Anforderung. Notepad schreibt beispielsweise nur die von Ihnen eingegebenen Zeichen, ohne am Ende etwas zusätzliches hinzuzufügen. C-Compiler benötigen möglicherweise eine Quelldatei, um mit einem Zeilenumbruch zu enden, aber C-Quelldateien sind nicht "nur" Textdateien, daher können zusätzliche Anforderungen gestellt werden.
Rob Kennedy
In diesem Sinne entfernen die meisten Javascript / CSS-Minifizierer nachgestellte Zeilenumbrüche und erzeugen dennoch Textdateien.
Ysth
@Rob Kennedy und @ysth: Es gibt dort ein interessantes Argument, warum solche Dateien eigentlich keine Textdateien sind und so.
Sinan Ünür
2

Verwenden von dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1
cpit
quelle
2
perl -pi -e 's/\n$// if(eof)' your_file
Vijay
quelle
Effektiv das Gleiche wie die akzeptierte Antwort, aber für Nicht-Perl-Benutzer wohl klarer im Konzept. Beachten Sie, dass die goder die Klammern nicht erforderlich sind eof: perl -pi -e 's/\n$// if eof' your_file.
mklement0
2

Angenommen, der Unix-Dateityp und Sie möchten nur den letzten Zeilenumbruch, funktioniert dies.

sed -e '${/^$/d}'

Es funktioniert nicht bei mehreren Zeilenumbrüchen ...

* Funktioniert nur, wenn die letzte Zeile eine leere Zeile ist.

LoranceStinson
quelle
Hier ist eine sedLösung, die auch für eine nicht leere letzte Zeile funktioniert
wisbucky
2

Dies ist eine gute Lösung, wenn Sie sie benötigen, um mit Pipes / Umleitungen zu arbeiten, anstatt sie aus oder in eine Datei zu lesen / auszugeben. Dies funktioniert mit einzelnen oder mehreren Zeilen. Es funktioniert, ob es einen nachgestellten Zeilenumbruch gibt oder nicht.

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

Einzelheiten:

  • head -c -1schneidet das letzte Zeichen der Zeichenfolge ab, unabhängig davon, um welches Zeichen es sich handelt. Wenn die Zeichenfolge also nicht mit einer neuen Zeile endet, verlieren Sie ein Zeichen.
  • Um dieses Problem zu beheben, fügen wir einen weiteren Befehl hinzu, der eine nachfolgende neue Zeile hinzufügt, wenn es keine gibt : sed '$s/$//'. Das erste $Mittel wendet den Befehl nur auf die letzte Zeile an. s/$//bedeutet, das "Ende der Zeile" durch "nichts" zu ersetzen, was im Grunde nichts bedeutet. Es hat jedoch den Nebeneffekt, dass eine nachfolgende neue Zeile hinzugefügt wird, sofern keine vorhanden ist.

Hinweis: Die Standardeinstellung von Mac headunterstützt diese -cOption nicht. Sie können stattdessen tun brew install coreutilsund verwenden ghead.

wisbucky
quelle
1

Noch eine Antwort FTR (und mein Favorit!): Echo / Katze das Ding, das Sie entfernen und die Ausgabe durch Backticks erfassen möchten. Die letzte Zeile wird entfernt. Zum Beispiel:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline
Nicholas Wilson
quelle
1
Ich habe die Cat-Printf-Combo zufällig gefunden (habe versucht, das gegenteilige Verhalten zu erreichen). Beachten Sie, dass dadurch ALLE nachfolgenden Zeilenumbrüche entfernt werden, nicht nur die letzten.
Technosaurus
1

POSIX SED:

'$ {/ ^ $ / d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.
Oleg Mazko
quelle
Ich denke, dies wird es nur entfernen, wenn die letzte Zeile leer ist. Die nachfolgende neue Zeile wird nicht entfernt, wenn die letzte Zeile nicht leer ist. Zum Beispiel echo -en 'a\nb\n' | sed '${/^$/d}'wird nichts entfernt. echo -en 'a\nb\n\n' | sed '${/^$/d}'wird entfernt, da die gesamte letzte Zeile leer ist.
wisbucky
0

Das einzige Mal, dass ich dies tun wollte, war für Code Golf, und dann habe ich meinen Code einfach aus der Datei kopiert und in eine echo -n 'content'>fileAnweisung eingefügt .

Dlamblin
quelle
Auf halbem Wege; vollständiger Ansatz hier .
mklement0
0
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
Ghostdog74
quelle
Funktioniert, entfernt jedoch alle nachfolgenden Zeilenumbrüche.
mklement0
0

Ich hatte ein ähnliches Problem, arbeitete aber mit einer Windows-Datei und musste diese CRLF behalten - meine Lösung unter Linux:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
Cadrian
quelle
0
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Sollte das letzte Vorkommen von \ n in der Datei entfernen. Funktioniert nicht mit großen Dateien (aufgrund der Einschränkung des Sed-Puffers)

NeronLeVelu
quelle
0

Rubin:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

oder:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
Gipfel
quelle