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?


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.


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 .


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.


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


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

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)

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.


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).


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"))

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

3
$ perl -e 'local $ /; $ _ = <>; s / \ n $ //; print 'a-text-file.txt

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


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

2
perl -pi -e 's/\n$// if(eof)' your_file

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.


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.


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

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.

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 .


Auf halbem Wege; vollständiger Ansatz hier .
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

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)


0

Rubin:

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

oder:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.