Wie bekomme ich die letzten N Dateien in einem Verzeichnis?

15

Ich habe viele Dateien, die nach Dateinamen in einem Verzeichnis sortiert sind. Ich möchte die letzten N (N = 4) Dateien in mein Home-Verzeichnis kopieren. Wie soll ich das machen

cp ./<the final 4 files> ~/

Spielende Geschwister
quelle
1
Bei allen folgenden Antworten müssen Sie möglicherweise ein "LC_ALL = ..." vor den Befehlen mit den Bereichen einfügen, damit deren Bereiche das richtige Gebietsschema für Sie verwenden (Gebietsschemas können sich ändern, und die Reihenfolge der Zeichen kann sich dann ändern Bsp .: In einigen Gebietsschemata kann [az] alle Buchstaben des Alphabets mit Ausnahme von "Z" enthalten, da die Buchstaben wie folgt sortiert sind: aAbBcC .... zZ Eine übliche Wahl ist:LC_ALL=C
Olivier Dulac

Antworten:

23

Dies kann einfach mit bash / ksh93 / zsh-Arrays durchgeführt werden:

a=(*)
cp -- "${a[@]: -4}" ~/

Dies funktioniert für alle nicht ausgeblendeten Dateinamen, auch wenn sie Leerzeichen, Tabulatoren, Zeilenumbrüche oder andere schwierige Zeichen enthalten (vorausgesetzt, das aktuelle Verzeichnis enthält mindestens 4 nicht ausgeblendete Dateien mit bash).

Wie es funktioniert

  • a=(*)

    Dadurch wird ein Array amit allen Dateinamen erstellt. Die von bash zurückgegebenen Dateinamen sind alphabetisch sortiert. (Ich gehe davon aus, dass dies das ist, was Sie mit "sortiert nach Dateinamen" meinen . )

  • ${a[@]: -4}

    Dies gibt die letzten vier Elemente des Arrays zurück a(vorausgesetzt, das Array enthält mindestens 4 Elemente mit bash).

  • cp -- "${a[@]: -4}" ~/

    Dadurch werden die letzten vier Dateinamen in Ihr Ausgangsverzeichnis kopiert.

Gleichzeitig kopieren und umbenennen

Dadurch werden nur die letzten vier Dateien in das Ausgangsverzeichnis a_kopiert und gleichzeitig die Zeichenfolge vor den Namen der kopierten Datei gestellt:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done

Aus einem anderen Verzeichnis kopieren und auch umbenennen

Wenn wir a=(./some_dir/*)anstelle von verwenden a=(*), tritt das Problem auf, dass das Verzeichnis an den Dateinamen angehängt wird. Eine Lösung ist:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done

Eine andere Lösung besteht darin, eine Subshell und cddas Verzeichnis in der Subshell zu verwenden:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 

Nach Abschluss der Subshell kehrt die Shell zum ursprünglichen Verzeichnis zurück.

Stellen Sie sicher, dass die Bestellung konsistent ist

Die Frage fragt nach Dateien "sortiert nach Dateinamen". Diese Reihenfolge wird, wie Olivier Dulac in den Kommentaren ausführt, von Land zu Land unterschiedlich sein. Wenn es wichtig ist, dass die Ergebnisse unabhängig von den Computereinstellungen festgelegt werden, empfiehlt es sich, das Gebietsschema beim aDefinieren des Arrays explizit anzugeben . Beispielsweise:

LC_ALL=C a=(*)

Sie können herausfinden, in welchem ​​Gebietsschema Sie sich gerade befinden, indem Sie den localeBefehl ausführen .

John1024
quelle
9

Wenn Sie verwenden zsh, können Sie ()eine Liste sogenannter Glob-Qualifizierer in Klammern einfügen, die die gewünschten Dateien auswählen. In deinem Fall wäre das

cp *(On[1,4]) ~/

Hier werden OnDateinamen in umgekehrter Reihenfolge alphabetisch sortiert und [1,4]nur die ersten 4 von ihnen verwendet.

Sie können dies robuster machen, indem Sie nur einfache Dateien (ohne Verzeichnisse, Pipes usw.) mit auswählen .und auch --den cpBefehl anhängen , um Dateien zu behandeln, deren Namen -ordnungsgemäß beginnen.

cp -- *(.On[1,4]) ~

Fügen Sie das DQualifikationsmerkmal hinzu, wenn Sie auch versteckte Dateien (Punktdateien) berücksichtigen möchten:

cp -- *(D.On[1,4]) ~
jimmij
quelle
2
Oder: *([-4,-1]).
Stéphane Chazelas
2
@StéphaneChazelas ja, lasst uns für zukünftige Leser klarstellen, dass alphabetisches Sortieren in normaler Richtung ( on) das Standardverhalten ist, daher muss dies nicht angegeben werden, und -vor den Zahlen werden die Dateinamen von unten gezählt.
Jimmy
7

Hier ist eine Lösung mit extrem einfachen Bash-Befehlen.

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

Erläuterung:

find . -maxdepth 1 -type f

Findet alle Dateien im aktuellen Verzeichnis.

sort

Sortiert alphabetisch.

tail -n 4

Nur die letzten 4 Zeilen anzeigen.

while read -r file; do cp "$file" ~/; done

Durchläuft jede Zeile, die den Kopierbefehl ausführt.

gswong
quelle
6

Solange Ihnen die Shell-Sortierung zusagt, können Sie Folgendes tun:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir

Dies ist der bashangebotenen Array-spezifischen Lösung sehr ähnlich , sollte jedoch auf jede POSIX-kompatible Shell portierbar sein. Einige Anmerkungen zu beiden Methoden:

  1. Es ist wichtig, dass Sie Ihre cpArgumente anführen, cp --oder Sie erhalten entweder .einen Punkt oder /den Kopf jedes Namens. Wenn Sie dies nicht tun, riskieren Sie einen führenden -Gedankenstrich im cpersten Argument, der als Option interpretiert werden kann und dazu führt, dass die Operation fehlschlägt oder auf andere Weise unbeabsichtigte Ergebnisse liefert.

    • Auch wenn Sie mit dem aktuellen Verzeichnis als Quellverzeichnis arbeiten, können Sie dies wie ... set -- ./*oder array=(./*).
  2. Wenn Sie beide Methoden verwenden, ist es wichtig, dass Sie mindestens so viele Elemente in Ihrem arg-Array haben, wie Sie zu entfernen versuchen - ich mache das hier mit einer mathematischen Erweiterung. Das shiftpassiert nur, wenn das arg-Array mindestens 4 Elemente enthält - und verschiebt dann nur die ersten Argumente, die einen Überschuss von 4 ergeben.

    • In einem set 1 2 3 4 5Fall verschiebt es das 1Weg, aber in einem set 1 2 3Fall verschiebt es nichts.
    • Beispiel: a=(1 2 3); echo "${a[@]: -4}"Druckt eine leere Zeile.

Wenn Sie von einem Verzeichnis in ein anderes kopieren, können Sie Folgendes verwenden pax:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir

... die sedalle Dateinamen beim Kopieren durch einen -Stil ersetzen würde.

mikeserv
quelle
4

Wenn es nur Dateien gibt, deren Namen keine Leerzeichen oder Zeilenumbrüche (und die Sie nicht geändert haben $IFS) oder Glob-Zeichen (oder einige nicht druckbare Zeichen mit einigen Implementierungen von ls) enthalten und nicht mit beginnen ., können Sie dies tun :

cp -- $(ls | tail -n 4) ~/
Hauke ​​Laging
quelle
Dies wird als Befehlsersetzung bezeichnet und ist sehr praktisch. tldp.org/LDP/abs/html/commandsub.html#CSPARENS
iyrin
Vielen Dank! Es klappt. Gibt es eine Möglichkeit a_, ALLEN vier Dateien schnell ein Präfix voran zu stellen? Ich habe es versucht echo "a_$(ls | tail -n 4)", aber es geht nur der ersten Datei voran.
Sibbs Gambling
@SibbsGambling Mein Ansatz ist dafür nicht geeignet, aber die Antwort von John1024 kann leicht darauf angepasst werden.
Hauke ​​Laging
5
Im Allgemeinen wird das Parsen von lsAusgaben als fehlerhafte Form angesehen, da bei Dateinamen, die nicht nur Leerzeichen, sondern auch Tabulatoren, Zeilenumbrüche oder andere gültige, aber "schwierige" Zeichen enthalten, normalerweise ein schrecklicher Fehler auftritt.
HBruijn
2
@HaukeLaging Auch wenn es momentan kein Problem ist (weil ich keine "komplizierten" Firenamen in meinem Verzeichnis habe), könnte es sein, dass ich in 2 Jahren plötzlich Dinge auf meine Füße
kriege
1

Dies ist eine einfache Bash-Lösung ohne Schleifencode. Kopieren Sie die letzten 4 Dateien vom aktuellen Verzeichnis zum Ziel:

$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
yasin_alm
quelle
1
Wie stellen Sie sicher, dass findSie die Dateien in der gewünschten Reihenfolge erhalten? Wie stellen Sie sicher, dass Dateien mit Leerzeichen (einschließlich Zeilenumbrüchen) im Namen korrekt behandelt werden?
Kusalananda