Unix: Wie tariere ich nur N erste Dateien jedes Ordners?

7

Ich habe einen Ordner mit 2 GB Bildern und Unterordnern, die mehrere Ebenen tief sind.

Ich möchte nur NDateien jedes (Unter-) Ordners in einer TAR-Datei archivieren . Ich habe versucht , zu verwenden , finddann taildann taraber konnte nicht verwalten es zur Arbeit zu kommen. Folgendes habe ich versucht (vorausgesetzt N = 10):

find . | tail -n 10 | tar -czvf backup.tar.gz

… Was diesen Fehler ausgibt:

Cannot stat: File name too long

Was ist hier los? Denken Sie daran - auch wenn es funktioniert, denke ich, dass es nur die ersten 10 Dateien aller Ordner tariert, nicht die 10 Dateien jedes Ordners.

Wie kann ich NDateien von jedem Ordner erhalten? (Keine Dateireihenfolge erforderlich)

Sam
quelle
+0. Warum willst du das tun?
unvergesslicheidSupportsMonica
Möchten Sie die ersten 10 Dateien numerisch, alphabetisch, nach Datum sortieren lassen oder spielt die Reihenfolge keine Rolle?
Alexander
@unforgettableid: Ich muss lokal an einer Website arbeiten, aber es sind 9 GB Bilder auf dem Server und ich brauche nicht alle!
Sam
@ Sam: Ah OK. Upvoted.
unvergesslicheidSupportsMonica

Antworten:

4

Wenn Sie paxdie -0Option unterstützen , mit zsh:

print -rN dir/**/*(D/e:'reply=($REPLY/*(ND^/[1,10]))':) |
  pax -w0 | xz > file.tar.xz

Es enthält die ersten 10 Nicht-Verzeichnisdateien jedes Verzeichnisses in der Liste, sortiert nach Dateinamen. Sie können eine andere Sortierreihenfolge auswählen, indem Sie das omGlob-Qualifikationsmerkmal (Reihenfolge nach Änderungszeit, Omum die Reihenfolge umzukehren), oL(Reihenfolge nach Länge), non(Sortierung nach Name, aber numerisch) hinzufügen ...

Wenn Sie den Standardbefehl nicht haben paxoder ihn nicht unterstützen, -0aber den GNU- tarBefehl haben, können Sie Folgendes tun:

print -rN -- dir/**/*(D/e:'reply=($REPLY/*(ND^/[1,10]))':) |
  tar --null -T - -cjf file.tar.xz

Wenn Sie nicht verwenden können zsh, aber Zugriff auf bash(die Shell des GNU-Projekts) haben, können Sie Folgendes tun:

find dir -type d -exec bash -O nullglob -O dotglob -c '
  for dir do
    set -- "$dir/*"; n=0
    for file do
      if [ ! -d "$file" ] || [ -L "$file" ]; then
        printf "%s\0" "$file"
        (( n++ < 10 )) || break
      fi
    done
  done' bash {} + | pax -0w | xz > file.tar.xz

Das wäre allerdings deutlich weniger effizient.

Stéphane Chazelas
quelle
print -rN - dir / ** / * (D / e: 'reply = ($ REPLY / * (ND ^ / [1,10]))' :) -> Fehlermeldung abrufen: -bash: Syntaxfehler in der Nähe unerwartetes Token `('
Sam
1
@ user1916171, Es scheint, dass Sie das mit zsh verpasst haben und wenn Sie zsh- Teile dieser Antwort nicht verwenden können . bashist eine andere Shell, die Shell des GNU-Projekts, die im Vergleich zu viel eingeschränkter ist zsh. Am Ende der Antwort finden Sie eine Lösung, mit der Sie arbeiten können bash.
Stéphane Chazelas
2

Angenommen, Ihr Hauptverzeichnis ist /tmp/dirdas Verzeichnis, aus dem Sie nur N (z. B. N = 10) Dateien jedes (Unter-) Ordners darunter in einer backup.tar.gzDatei archivieren möchten .

Beispiel treefür /tmp/dir:

dir/                                                                                                                                                                                                           
├── one
│   ├── one10.txt
│   ├── one11.txt
│   ├── one1.txt
│   ├── one2.txt
│   ├── one3.txt
│   ├── one4.txt
│   ├── one5.txt
│   ├── one6.txt
│   ├── one7.txt
│   ├── one8.txt
│   ├── one9.txt
│   └── one_deep
│       ├── one_deep1
│       ├── one_deep10
│       ├── one_deep11
│       ├── one_deep2
│       ├── one_deep3
│       ├── one_deep4
│       ├── one_deep5
│       ├── one_deep6
│       ├── one_deep7
│       ├── one_deep8
│       └── one_deep9
├── three
│   ├── three10.txt
│   ├── three11.txt
│   ├── three1.txt
│   ├── three2.txt
│   ├── three3.txt
│   ├── three4.txt
│   ├── three5.txt
│   ├── three6.txt
│   ├── three7.txt
│   ├── three8.txt
│   ├── three9.txt
│   └── three_deep
│       ├── three_deep1
│       ├── three_deep10
│       ├── three_deep11
│       ├── three_deep2
│       ├── three_deep3
│       ├── three_deep4
│       ├── three_deep5
│       ├── three_deep6
│       ├── three_deep7
│       ├── three_deep8
│       └── three_deep9

Code:

cd /tmp; for i in `find dir/* -type d`; do find $i -maxdepth 1 -type f | tail -n 10 | xargs -I file tar -rf backup.tar file; done; gzip backup.tar

Dadurch wird ein backup.tar.gzmit 10 Dateien jedes Unterordners von unter erstellt /tmp/dir.

Neuron
quelle
Wenn dies aus irgendeinem Grund cd /tmpfehlschlägt, führen Sie diesen Befehl im falschen Verzeichnis aus. Sie sollten immer den Ausgangsstatus überprüfen von cd:cd /tmp && for...
Stéphane Chazelas
find dir/* -type dbedeutet, dass Sie die versteckten Verzeichnisse im aktuellen Verzeichnis nicht verarbeiten, sondern in Unterverzeichnissen. Verwenden find dir -type dSie entweder oder, wenn Sie die Dateien nicht im aktuellen Verzeichnis haben möchten: find dir/. ! -name . -type doder find dir ! -path dir -type d.
Stéphane Chazelas
Verwenden `find...`bedeutet, den Operator split + glob aufzurufen (nur in zsh aufgeteilt). Hier möchten Sie den Glob-Teil nicht und nur in Zeilenumbruch teilen (obwohl Zeilenumbruch ein gültiges Zeichen in einem Dateinamen ist, sodass dieser Ansatz ohnehin fehlerhaft ist und Sie stattdessen den von finds -execverwenden sollten).
Stéphane Chazelas
Wenn Sie dies $inicht in Anführungszeichen setzen, müssen Sie auch den Operator split + glob aufrufen, was hier keinen Sinn ergibt. Verwenden Siefind "$i"
Stéphane Chazelas
Beachten Sie, dass auch bei -I, xargsnoch behandelt Zitat und Backslash Zeichen speziell in seinem Eingang. Es bedeutet auch, einen tarBefehl pro Zeile auszuführen, was nicht sehr effizient ist.
Stéphane Chazelas
2

Da die Ausgabe von findflach ist, wissen Sie nicht wirklich, welche Dateien zu denselben Verzeichnissen gehören, ohne die Pfade zu betrachten. Die Alternative besteht darin, mehrere finds (eines pro Ordner) zu verwenden, ohne die Pfade betrachten zu müssen. Das habe ich getan. Verwenden Sie Folgendes, um maximal 10 Dateien pro Unterordner zu tarieren:

for dir in $(find . -type d); do
  find "$dir" -maxdepth 1 -type f -printf "\"%p\"\n" | tail -10
done | xargs tar cvfz backup.tar.gz

Dadurch werden rekursiv alle Verzeichnisse im aktuellen Ordner gefunden. Für jedes Verzeichnis werden bis zu 10 Dateien in genau diesem Ordner gefunden ( -maxdepth 1). Sobald die gesamte Schleife beendet ist, wird der tarBefehl für alle Dateien ausgeführt, die von der Schleife ausgegeben wurden. Ich habe auch Verzeichnis- und Ordnernamen mit Leerzeichen berücksichtigt, indem $dirich findjeden Dateinamen mit der -printfOption in Anführungszeichen gesetzt und gedruckt habe .

Malte Skoruppa
quelle
1
for d in ./*/
do
    cd "$d"
    tar -rvf ../backup.tar $(ls | tail -10)
    cd ..
done
gzip backup.tar

andere Variante

find * -prune -type d -exec bash -c 'printf "%s\n" $0/* | tail -10' {} \; |
tar czvf backup.tar.gz -T -
Costas
quelle
0

Verwenden Sie einen Hash für den Verzeichnisnamen und geben Sie den Dateinamen nur aus, wenn die Anzahl der Hashwerte unter dem Schwellenwert liegt. Z.B

find . -depth -type f \
| perl -MFile::Spec -nle '(undef,$d,$f)=File::Spec->splitpath($_); print if $seen{$d}++ < 3' \
| tar ...
Thrig
quelle
0

Der einfachste (oder am einfachsten zu verstehende) Weg ist die Verwendung von xargs mit der -N max-argsOption.

Denken Sie daran, dass Ihre Eingabe immer etwas sein muss, für das keine Befehlszeile erforderlich echo *.*ist. Sie funktioniert also als Eingabe, wo ls *.*dies nicht der Fall ist (zu lange Befehlszeile).

find sollte in Ordnung sein, da sein Argument nur der Pfad ist, keine Liste von Dateien.

allo
quelle
0

OP hat dies auch in Stackoverflow gefragt . Hier ist die Antwort, die ich dort angeboten habe.

Die Auswahl und Reihenfolge der Dateien in dieser Antwort hängt von der Reihenfolge ab ab find, sodass "first" hier nicht genau definiert ist. Dies kann auch von GNU Awk 4.1.0 abhängen.

finden . -Typ f |
awk -v N = 10 -F / 'Übereinstimmung ($ 0, /.*\//, m) && a [m [0]] ++ <N' |
xargs -r -d '\ n' tar -rvf /tmp/backup.tar

gzip /tmp/backup.tar

Bemerkungen:

  1. Verwenden Sie find . -type fdiese Option, um sicherzustellen, dass Dateien ein führendes Verzeichnisnamenpräfix haben, damit der nächste Schritt ausgeführt werden kann
  2. Der awkBefehl verfolgt solche führenden Verzeichnisnamen und gibt vollständige Pfadnamen aus, bis N (10, hier) Dateien mit demselben führenden Verzeichnis ausgegeben wurden (möglicherweise ist eine einfachere awkVerwendung - Aufteilen von Infomuster und Programm - möglicherweise portabler).
  3. Verwenden Sie xargszum Aufrufen tar- wir erfassen reguläre Dateinamen, und diese müssen Argumente für diesen Archivierungsbefehl sein
  4. xargswird möglicherweise tarmehrmals aufgerufen, daher hängen wir (Option -r) an ein einfaches Archiv an und komprimieren es dann, nachdem alles geschrieben wurde

Möglicherweise möchten Sie auch keine Sicherungsdatei in das aktuelle Verzeichnis schreiben, da Sie diese scannen. Aus diesem Grund schreibt dieser Vorschlag in / tmp.

sjnarv
quelle