Unix: comment tarer seulement N premiers files de chaque dossier?

J'ai un dossier contenant 2 Go d'images, avec des sous-dossiers de plusieurs niveaux profonds.

Je voudrais archiver seulement N files de chaque (sous) dossier dans un file tar. J'ai essayé d'utiliser find puis tail puis tar mais je n'arrivais pas à le faire fonctionner. Voici ce que j'ai essayé (en supposant que N = 10 ):

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

… qui génère cette erreur:

 Cannot stat: File name too long 

Qu'est-ce qui ne va pas ici? en y pensant – même si cela fonctionne, je pense que cela ne goudrera que les 10 premiers files de tous les dossiers, pas les 10 files de chaque dossier.

Comment puis-je get N files de chaque dossier? (Aucun ordre de file nécessaire)

Si votre pax prend en charge l'option -0 , avec zsh :

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

Il inclut les 10 premiers files non-directory de chaque directory de la list sortingés par nom de file. Vous pouvez choisir un ordre de sorting différent en ajoutant le qualificateur om glob (ordre par heure de modification, Om pour inverser l'ordre), oL (ordre par longueur), non (sorting par nom mais numériquement) …

Si vous n'avez pas la command pax standard, ou si elle ne supporte pas -0 mais que vous avez la command GNU tar , vous pouvez faire:

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

Si vous ne pouvez pas utiliser zsh , mais avoir access à bash (le shell du projet GNU), vous pourriez faire:

 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 

Ce serait cependant beaucoup less efficace.

Supposons que votre directory principal soit /tmp/dir partir duquel vous ne souhaitez archiver que des files N (par exemple N = 10) de chaque sous-dossier dans un file backup.tar.gz .

Exemple d' tree pour /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 

Cela créera un backup.tar.gz avec 10 files de chaque sous-dossier de sous /tmp/dir .

Comme la sortie de find est plate, vous ne savez pas vraiment quels files appartiennent aux mêmes directorys sans regarder les paths. L'alternative est d'utiliser plusieurs find (une par dossier), sans avoir à regarder les paths. C'est ce que j'ai fait. Pour utiliser jusqu'à 10 files de chaque sous-dossier, utilisez quelque chose comme ceci:

 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 

Cela récursivement trouve tous les directorys dans le dossier en cours. Pour chaque directory, il trouve jusqu'à 10 files exactement dans ce dossier ( -maxdepth 1 ). Une fois la boucle terminée, la command tar est exécutée sur tous les files qui ont été générés par la boucle. J'ai également pris en count les noms de directory et de dossier avec des espaces en citant $dir et en ayant find printing de chaque nom de file entre guillemets en utilisant l'option -printf .

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

autre variante

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

Utilisez un hachage sur le nom du directory et n'émettez le nom de file que si le nombre de valeurs de hachage est inférieur au seuil. Par exemple

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

Le moyen le plus simple (ou le plus facile à comprendre) est d'utiliser xargs avec l'option -N max-args .

Gardez à l'esprit que votre input doit toujours être quelque chose qui ne nécessite pas de command line, alors echo *.* Fonctionnera comme input, où ls *.* Ne le fait pas (trop long)

find devrait être bien, car son argument est seulement le path, pas une list de files.

OP a demandé cela dans Stackoverflow aussi . Voici la réponse que j'ai offert là.

La sélection et l'ordre des files dans cette réponse sont déterminés par l'ordre de find , donc "premier" n'est pas bien défini ici. Cela peut également dépendre de GNU Awk 4.1.0.

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

 gzip /tmp/backup.tar

Commentaires:

  1. utiliser find . -type f find . -type f pour s'assurer que les files ont un préfixe de premier nom de directory, de sorte que la prochaine étape peut fonctionner
  2. la command awk suit ces noms de directorys principaux et émet les noms de paths complets jusqu'à ce que des files N (10, ici) soient émis avec le même directory principal (peut-être plus simple)
  3. utilisez xargs pour invoquer tar – nous recueillons des noms de files réguliers, et ils doivent être des arguments pour cette command d'archivage
  4. xargs peut invoquer tar plus d'une fois, donc nous allons append (option -r) à une archive simple, puis la compresser après tout ce qui est écrit

En outre, vous ne voudrez peut-être pas écrire un file de sauvegarde dans le directory courant, puisque vous numérisez cela – c'est pourquoi cette suggestion écrit dans / tmp.