Wie entferne ich die letzte Zeile aller Dateien aus einem Verzeichnis?


17

Ich habe viele Textdateien in einem Verzeichnis und möchte die letzte Zeile jeder Datei im Verzeichnis entfernen.

Wie kann ich es tun?


6
Was hast du versucht? unix.stackexchange.com/help/how-to-ask : "Das Teilen Ihrer Recherche hilft allen. Sagen Sie uns, was Sie gefunden haben und warum es nicht Ihren Anforderungen entsprach. Dies zeigt, dass Sie sich die Zeit genommen haben, um zu versuchen, sich selbst zu helfen , es erspart uns, offensichtliche Antworten zu wiederholen, und vor allem hilft es Ihnen, eine spezifischere und relevantere Antwort zu erhalten! "
Patrick

Warum hat der Gedanke, dass jemand das versehentlich auf / etc anwendet, eine ganz besondere Qualität der Cringe-Induktion :)
Rackandboneman

Antworten:


4

Wenn Sie Zugriff auf haben vim, können Sie Folgendes verwenden:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done

16

Sie können diesen netten Oneliner verwenden, wenn Sie GNU haben sed.

 sed -i '$ d' ./*

Dadurch wird die letzte Zeile jeder nicht ausgeblendeten Datei im aktuellen Verzeichnis entfernt. Switch -ifor GNU sedbedeutet, an Ort und Stelle zu arbeiten, und '$ d'befiehlt sed, die letzte Zeile zu löschen ( $bedeutet "Letzte" und " dLöschen").


3
Dies wird Fehler auslösen (und nichts tun), wenn der Ordner etwas anderes als eine reguläre Datei enthält, z. B. einen anderen Ordner ...
Najib Idrissi

1
@StefanR Du benutzt -ieinen GNUism, also ist das strittig, aber ich würde meinen alten Bart verlieren, wenn ich nicht darauf hinweisen würde, dass einige ältere Versionen von sedes dir nicht erlauben, Leerzeichen zwischen den $und d(oder) zu setzen. im Allgemeinen zwischen dem Muster und dem Befehl).
zwol

1
@zwol Wie ich geschrieben habe, führt dies zu einem Fehler , nicht zu einer Warnung, und sed gibt auf, sobald es diese Datei erreicht (zumindest mit der Version von sed, die ich habe). Die nächsten Dateien werden nicht verarbeitet. Die Fehlermeldungen wegzuwerfen wäre eine schreckliche Idee, da Sie nicht einmal wissen würden, dass es passiert ist! Mit zsh können Sie *(.)Glob für reguläre Dateien verwenden, ich kenne keine anderen Shells.
Najib Idrissi

@NajibIdrissi Hmm, du hast recht. Das überrascht mich; Ich hätte erwartet, dass es sich über das Verzeichnis beschwert, aber dann mit der nächsten Datei in der Befehlszeile fortfährt. Tatsächlich denke ich, dass ich das als Fehler melden werde.
Sonntag,

@don_crissti Ich habe auch GNU sed v4.3 ... Ich weiß nicht, was ich dir sagen soll, ich habe es gerade noch einmal getestet. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi

11

Die anderen Antworten haben alle Probleme, wenn das Verzeichnis etwas anderes als eine reguläre Datei oder eine Datei mit Leerzeichen / Zeilenumbrüchen im Dateinamen enthält. Folgendes funktioniert unabhängig davon:

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f: Finden Sie die Dateien im Verzeichnis $dir
    • -type f welche sind reguläre Dateien;
    • -exec Führen Sie den Befehl für jede gefundene Datei aus
    • sed -i: Bearbeiten Sie die Dateien an Ort und Stelle;
    • '$d': lösche ( d) die letzte ( $) Zeile.
    • '+': weist find an, weiterhin Argumente hinzuzufügen sed(etwas effizienter, als den Befehl für jede Datei einzeln auszuführen, dank @zwol).

Wenn Sie in die Unterverzeichnisse absteigen wollen nicht, dann können Sie das Argument hinzufügen -maxdepth 1zu find.


1
Hmm, aber im Gegensatz zu den anderen Antworten dieses absteigt in Unterverzeichnisse. (Auch mit aktuellen Versionen ist finddies effizienter geschrieben find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol

@zwol Danke, das habe ich der Antwort hinzugefügt.
Najib Idrissi

-print0ist nicht im vollen Befehl vorhanden, warum es in der Erklärung setzen?
Ruslan

1
Funktioniert auch -depth 0nicht (findutils 4.4.2), sollte es stattdessen sein -maxdepth 1.
Ruslan

@ Ruslan Ich hatte eine erste Version, in der ich xargs verwendet habe, erinnerte mich dann aber -exec.
Najib Idrissi

9

Wenn Sie GNU verwenden sed -i '$d', müssen Sie die gesamte Datei lesen und eine Kopie davon ohne die letzte Zeile erstellen, während es viel effizienter wäre, die Datei nur an Ort und Stelle zu kürzen (zumindest bei großen Dateien).

Mit GNU truncatekönnen Sie:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

Wenn die Dateien relativ klein sind, ist dies wahrscheinlich weniger effizient, da mehrere Befehle pro Datei ausgeführt werden.

Beachten Sie, dass für Dateien, die zusätzliche Bytes nach dem letzten Zeilenumbruchzeichen (nach der letzten Zeile) oder, mit anderen Worten, eine nicht begrenzte letzte Zeile enthalten , je nach tailImplementierung tail -n 1nur diese zusätzlichen Bytes (wie GNU tail) zurückgegeben werden. oder die letzte (richtig begrenzte) Zeile und diese zusätzlichen Bytes.


brauchst du einen |wc -cim tailanruf? (oder a ${#length})
Jeff Schaller

@ JeffSchaller. Hoppla. wc -c war in der Tat beabsichtigt. ${#length}würde nicht funktionieren, da Zeichen, nicht Bytes, gezählt werden, und $(...)würde das abschließende Zeilenumbruchzeichen entfernen, so ${#...}dass es um eins versetzt wäre, selbst wenn alle Zeichen Einzelbytes wären.
Stéphane Chazelas

6

Ein portablerer Ansatz:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

Ich glaube nicht, dass dies einer Erklärung bedarf ... außer vielleicht, dass dies in diesem Fall ddasselbe ist, $dda edstandardmäßig die letzte Zeile ausgewählt wird.
Dies sucht nicht rekursiv und verarbeitet keine versteckten Dateien (auch Dotfiles genannt).
Wenn Sie auch diese bearbeiten möchten, lesen Sie Wie Sie versteckte Dateien in einem Verzeichnis mit * abgleichen


Nett! Wenn Sie zu wechseln [[]], []ist es vollständig POSIX-kompatibel. ( [[ ... ]]Ist ein Bashismus.)
Wildcard

@Wildcard - danke, geändert (obwohl [[kein Bashismus ist )
don_crissti

Ein Nicht-POSIX-Ismus, sollte ich sagen. :)
Wildcard

3

POSIX-kompatibler Einzeiler für alle Dateien, die rekursiv im aktuellen Verzeichnis beginnen, einschließlich Punktdateien:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

.txtNur für Dateien, nicht rekursiv:

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Siehe auch:

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.