it-swarm.com.de

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

Ich habe einige Dateien, die ich gerne löschen möchte, wenn es sich um das letzte Zeichen in einer Datei handelt. od -c zeigt mir an, dass der Befehl, den ich ausführte, die Datei mit einer nachgestellten neuen Zeile schreibt:

0013600   n   t  >  \n

Ich habe ein paar Tricks mit sed ausprobiert, aber das Beste, was mir einfällt, macht nicht den Trick:

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

Irgendwelche Ideen, wie das geht?

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 des Herausgebers: -pi -e war ursprünglich -pie, aber wie von mehreren Kommentatoren bemerkt und von @hvd erläutert, funktioniert letzterer nicht.

Dies wurde auf der awk-Website, die ich gesehen habe, als „Perl-Blasphemie“ bezeichnet.

Aber in einem Test hat es funktioniert.

201
pavium

Sie können die Tatsache ausnutzen, dass Shell Befehlsersetzungen nachfolgende Zeilenumbrüche entfernen :

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

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

Portable (POSIX-konforme) Alternative (etwas weniger effizient):

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

Hinweis:

  • Wenn in.txt mit mehreren Zeilenumbrüchen endet, entfernt die Befehlsersetzung alle von ihnen - danke, @Sparhawk. (Es werden nur Leerzeichen entfernt, die in Zeilenumbrüchen stehen.)
  • Da bei dieser Methode die gesamte Eingabedatei in den Speicher eingelesen wird , ist dies nur für kleinere Dateien ratsam.
  • printf %s stellt sicher, dass keine Zeilenumbrüche an die Ausgabe angehängt werden (dies ist die POSIX-kompatible Alternative zum nicht standardmäßigen echo -n; siehe http://pubs.opengroup.org/onlinepubs/009696799/ Dienstprogramme/echo.html und https://unix.stackexchange.com/a/65819 )

Eine Anleitung zu den anderen Antworten :

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

  • Ansonsten betrachte ghostdog74's Awk answer - es ist obskur, aber auch speichereffizient ; Ein besser lesbares Äquivalent (POSIX-konform) ist:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • Der Druckvorgang wird um eine Zeile verzögert, so dass die letzte Zeile im Block END verarbeitet werden kann. Dort wird sie ohne nachfolgendes \n gedruckt, da das Trennzeichen für den Ausgabesatz festgelegt wurde (OFS). zu einer leeren Zeichenfolge.
  • Wenn Sie eine ausführliche, aber schnelle und robuste Lösung wünschen, die die vorhandenen (im Gegensatz dazu) bearbeitet Um eine temporäre Datei zu erstellen, die dann das Original ersetzt, ziehen Sie jrockways Perl-Skript in Betracht.

53
mklement0

Sie können dies mit head von GNU coreutils tun. Es unterstützt Argumente, die relativ zum Ende der Datei sind. So lassen Sie die letzte Byte-Verwendung weg:

head -c -1

Um zu testen, ob ein Zeilenumbruch vorhanden ist, können Sie tail und wc verwenden. Das folgende Beispiel speichert das Ergebnis in einer temporären Datei und überschreibt anschließend das Original:

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

Sie können auch sponge von moreutils verwenden, um die Bearbeitung direkt durchzuführen:

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

Sie können auch eine allgemeine wiederverwendbare Funktion erstellen, indem Sie diese in Ihre .bashrc-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 KarlWilbur in den Kommentaren angegeben und in verwendet, kann Sorentars answer , truncate --size=-1head -c-1 ersetzen und die direkte Bearbeitung unterstützen.

43
Thor
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Edit 2: 

Hier ist eine awk-Version (korrigiert) , die kein potenziell großes Array sammelt:

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

16

gaffen

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
10
ghostdog74

Eine sehr einfache Methode für einzeilige Dateien, die ein Echo GNU von coreutils erfordern:

/bin/echo -n $(cat $file)
8
anotheral

Wenn Sie es richtig machen wollen, brauchen Sie 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 seeked am Ende der Datei sind. Wir erhalten dann die numerische Position des Endes der Datei mit tell. Wir verwenden diese Zahl, um ein Zeichen zurückzusuchen, und dann lesen wir dieses eine Zeichen. Wenn es sich um eine neue Zeile handelt, kürzen wir die Datei auf das Zeichen vor der neuen Zeile, andernfalls machen wir nichts.

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

8
jrockway

Hier ist eine schöne, aufgeräumte Python-Lösung. Ich habe nicht versucht, hier knapp zu sein.

Dadurch wird die Datei direkt geändert, anstatt eine Kopie der Datei zu erstellen und den Zeilenumbruch 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. In Python 2.6 getestet.

Legen Sie dies in eine Datei namens "striplast" und 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)

P.S. Im Sinne von "Perl Golf" hier meine kürzeste Python-Lösung. Es schlürft die gesamte Datei von der Standardeingabe in den Speicher, entfernt alle Zeilenumbrüche und schreibt das Ergebnis in die Standardausgabe. Nicht so knapp wie das Perl; Sie können Perl einfach nicht für kleine, knifflige Dinge wie dieses schlagen.

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

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

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
5
steveha

Noch ein Perl-WTDI:

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

Siehe auch Gleiche Zeichen (einschließlich Zeilenumbrüche) in sed .

3
Sinan Ünür
Perl -pi -e 's/\n$// if(eof)' your_file
2
Vijay

Mit 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
cpit

Angenommen, der Unix-Dateityp und Sie möchten nur die letzte neue Zeile, die funktioniert.

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

Es wird nicht bei mehreren Zeilenumbrüchen funktionieren ...

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

2
LoranceStinson

Noch eine andere Antwort, FTR (und mein Favorit!): Echo/Cat das Ding, das Sie entfernen möchten, und die Ausgabe durch Backticks erfassen. 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
Nicholas Wilson

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.
1
Oleg Mazko

Eine schnelle Lösung ist das Abschneiden des Dienstprogramms gnu:

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

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

Das Entfernen ist sehr schnell, wirklich vorhanden, es ist keine neue Datei erforderlich und die Suche liest auch nur ein Byte (tail -c1).

1
sorontar

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

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
0
cadrian

Rubin:

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

oder:

Ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
0
peak

Ich wollte dies nur für Code-Golf, und dann habe ich einfach meinen Code aus der Datei kopiert und in eine echo -n 'content'>file-Anweisung eingefügt.

0
dlamblin
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
0
ghostdog74

Dies ist eine gute Lösung, wenn Sie es benötigen, um mit Pipes/Umleitungen zu arbeiten, anstatt aus einer oder in eine Datei zu lesen/auszugeben. Dies funktioniert mit einer oder mehreren Zeilen. Es funktioniert, ob es eine nachgestellte Zeile 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 -1 schneidet das letzte Zeichen der Zeichenfolge ab, unabhängig davon, was das Zeichen ist. Wenn der String also nicht mit einem Zeilenvorschub endet, verlieren Sie einen Charakter.
  • Um diesem Problem zu begegnen, fügen wir einen weiteren Befehl hinzu, der eine nachgestellte neue Zeile hinzufügt, falls keine vorhanden ist: sed '$s/$//'. Das erste $ bedeutet, dass der Befehl nur auf die letzte Zeile angewendet wird. s/$// bedeutet, das "Ende der Zeile" durch "nichts" zu ersetzen, was im Grunde nichts tut. Es hat jedoch den Nebeneffekt, dass eine nachgestellte Zeile hinzugefügt wird, wenn es keine gibt.

Hinweis: Die head-Option von Mac unterstützt die -c-Option nicht. Sie können brew install coreutils ausführen und stattdessen ghead verwenden.

0
wisbucky
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Sollte das letzte Vorkommen von\n in der Datei entfernen. Funktioniert nicht für große Dateien (aufgrund begrenzter Pufferbegrenzung)

0
NeronLeVelu