Comment récupérer les derniers N fichiers dans un répertoire?
15
J'ai de nombreux fichiers classés par nom de fichier dans un répertoire. Je souhaite copier les N fichiers finaux (disons N = 4) dans mon répertoire personnel. Comment dois-je procéder?
Sur toutes les réponses ci-dessous, vous devrez peut-être ajouter un "LC_ALL = ..." devant les commandes utilisant les plages, afin que leurs plages utilisent les paramètres régionaux appropriés pour vous (les paramètres régionaux peuvent changer et l'ordre des caractères peut alors changer Ex., dans certains endroits, [az] peut contenir toutes les lettres de l'alphabet à l'exception de "Z", car les lettres sont ordonnées: aAbBcC .... zZ. Il existe d'autres variantes (lettres accentuées et encore plus de commandes exotiques Un choix habituel est:LC_ALL=C
Olivier Dulac
Réponses:
23
Cela peut être facilement fait avec les tableaux bash / ksh93 / zsh:
a=(*)
cp --"${a[@]: -4}"~/
Cela fonctionne pour tous les noms de fichiers non masqués même s'ils contiennent des espaces, des tabulations, des retours à la ligne ou d'autres caractères difficiles (en supposant qu'il y ait au moins 4 fichiers non masqués dans le répertoire actuel avec bash).
Comment ça fonctionne
a=(*)
Cela crée un tableau aavec tous les noms de fichiers. Les noms de fichiers renvoyés par bash sont triés par ordre alphabétique. (Je suppose que c'est ce que vous entendez par "trié par nom de fichier". )
${a[@]: -4}
Cela renvoie les quatre derniers éléments du tableau a(à condition que le tableau contienne au moins 4 éléments bash).
cp -- "${a[@]: -4}" ~/
Cela copie les quatre derniers noms de fichier dans votre répertoire personnel.
Pour copier et renommer en même temps
Cela copiera les quatre derniers fichiers uniquement dans le répertoire de base et, en même temps, ajoutera la chaîne a_au nom du fichier copié:
Copiez à partir d'un répertoire différent et renommez également
Si nous utilisons a=(./some_dir/*)au lieu de a=(*), alors nous avons le problème du répertoire attaché au nom de fichier. Une solution est:
a=(./some_dir/*)for f in"${a[@]: -4}";do cp "$f"~/a_"${f##*/}";done
Une autre solution consiste à utiliser un sous-shell et cddans le répertoire du sous-shell:
(cd ./some_dir && a=(*)&&for f in"${a[@]: -4}";do cp --"$f"~/a_"$f";done)
Une fois le sous-shell terminé, le shell nous renvoie au répertoire d'origine.
S'assurer que la commande est cohérente
La question demande des fichiers "classés par nom de fichier". Cet ordre, souligne Olivier Dulac dans les commentaires, variera d'un endroit à l'autre. S'il est important d'avoir des résultats fixes indépendamment des paramètres de la machine, il est préférable de spécifier explicitement les paramètres régionaux lorsque le tableau aest défini. Par exemple:
LC_ALL=C a=(*)
Vous pouvez découvrir les paramètres régionaux dans lesquels vous vous trouvez actuellement en exécutant la localecommande.
Si vous utilisez, zshvous pouvez mettre entre parenthèses ()une liste de soi-disant qualificateurs glob qui sélectionnent les fichiers souhaités. Dans votre cas, ce serait
cp *(On[1,4])~/
Ici, Onles noms de fichiers sont triés par ordre alphabétique dans l'ordre inverse et [1,4]ne prend que les 4 premiers d'entre eux.
Vous pouvez rendre cela plus robuste en sélectionnant uniquement les fichiers simples (à l'exception des répertoires, des canaux, etc.) avec ., et également en ajoutant --à la cpcommande afin de traiter les fichiers dont les noms commencent -correctement, donc:
cp --*(.On[1,4])~
Ajoutez le Dqualificatif si vous souhaitez également considérer les fichiers cachés (fichiers dot):
@ StéphaneChazelas oui, permet de clarifier pour les futurs lecteurs que le tri alphabétique dans le sens normal ( on) est un comportement par défaut, donc pas besoin de le spécifier, et -devant les nombres compte les noms de fichiers à partir du bas.
jimmij
7
Voici une solution utilisant des commandes bash extrêmement simples.
Ceci est très similaire à la bashsolution de tableau spécifique offerte, mais devrait être portable sur n'importe quel shell compatible POSIX. Quelques notes sur les deux méthodes:
Il est important que vous meniez vos cparguments avec cp --un ou .un point ou /à la tête de chaque nom. Si vous ne le faites pas, vous risquez un premier -tiret dans cple premier argument de qui peut être interprété comme une option et entraîner l'échec de l'opération ou rendre autrement des résultats inattendus.
Même si vous travaillez avec le répertoire courant comme répertoire source, cela se fait facilement comme ... set -- ./*ou array=(./*).
Il est important lorsque vous utilisez les deux méthodes de vous assurer d'avoir au moins autant d'éléments dans votre tableau arg que vous essayez de supprimer - je le fais ici avec une extension mathématique. Le shiftseul problème se produit s'il y a au moins 4 éléments dans le tableau d'arguments - et ne déplace ensuite que les premiers arguments qui font un surplus de 4.
Donc, dans un set 1 2 3 4 5cas, cela déplacera l' 1écart, mais dans un set 1 2 3cas, cela ne changera rien.
Par exemple: a=(1 2 3); echo "${a[@]: -4}"imprime une ligne vierge.
Si vous copiez d'un répertoire à un autre, vous pouvez utiliser pax:
S'il n'y a que des fichiers et que leurs noms ne contiennent pas d'espace ou de nouvelle ligne (et que vous n'avez pas modifié $IFS) ou de caractères globaux (ou certains caractères non imprimables avec certaines implémentations de ls), et ne commencent pas par ., alors vous pouvez le faire :
Merci! Ça marche. Est-il possible d'ajouter rapidement un préfixe a_à TOUS les quatre fichiers? J'ai essayé echo "a_$(ls | tail -n 4)", mais il n'ajoute que le premier fichier.
Sibbs Gambling
@SibbsGambling Mon approche ne convient pas à cela, mais la réponse de John1024 peut facilement être adaptée à cela.
Hauke Laging du
5
En général, l'analyse de la lssortie est considérée comme mauvaise, car elle échoue généralement horriblement sur les noms de fichiers qui contiennent non seulement des espaces, mais également des tabulations, des sauts de ligne ou d'autres caractères valides mais "difficiles".
HBruijn
2
@HaukeLaging Même si ce n'est actuellement pas un problème (parce que je n'ai pas de noms de feu "compliqués" dans mon annuaire), je pourrais en avoir dans 2 ans, et tout à coup les choses tombent sur mes pieds ...
glglgl
1
Il s'agit d'une solution bash simple sans code de boucle. Copiez les 4 derniers fichiers du répertoire actuel vers la destination:
Comment vous assurez-vous que findvous donnez les fichiers dans l'ordre souhaité? Comment vous assurez-vous que les fichiers contenant des espaces (y compris les retours à la ligne) dans leurs noms sont traités correctement?
LC_ALL=C
Réponses:
Cela peut être facilement fait avec les tableaux bash / ksh93 / zsh:
Cela fonctionne pour tous les noms de fichiers non masqués même s'ils contiennent des espaces, des tabulations, des retours à la ligne ou d'autres caractères difficiles (en supposant qu'il y ait au moins 4 fichiers non masqués dans le répertoire actuel avec
bash
).Comment ça fonctionne
a=(*)
Cela crée un tableau
a
avec tous les noms de fichiers. Les noms de fichiers renvoyés par bash sont triés par ordre alphabétique. (Je suppose que c'est ce que vous entendez par "trié par nom de fichier". )${a[@]: -4}
Cela renvoie les quatre derniers éléments du tableau
a
(à condition que le tableau contienne au moins 4 élémentsbash
).cp -- "${a[@]: -4}" ~/
Cela copie les quatre derniers noms de fichier dans votre répertoire personnel.
Pour copier et renommer en même temps
Cela copiera les quatre derniers fichiers uniquement dans le répertoire de base et, en même temps, ajoutera la chaîne
a_
au nom du fichier copié:Copiez à partir d'un répertoire différent et renommez également
Si nous utilisons
a=(./some_dir/*)
au lieu dea=(*)
, alors nous avons le problème du répertoire attaché au nom de fichier. Une solution est:Une autre solution consiste à utiliser un sous-shell et
cd
dans le répertoire du sous-shell:Une fois le sous-shell terminé, le shell nous renvoie au répertoire d'origine.
S'assurer que la commande est cohérente
La question demande des fichiers "classés par nom de fichier". Cet ordre, souligne Olivier Dulac dans les commentaires, variera d'un endroit à l'autre. S'il est important d'avoir des résultats fixes indépendamment des paramètres de la machine, il est préférable de spécifier explicitement les paramètres régionaux lorsque le tableau
a
est défini. Par exemple:Vous pouvez découvrir les paramètres régionaux dans lesquels vous vous trouvez actuellement en exécutant la
locale
commande.la source
Si vous utilisez,
zsh
vous pouvez mettre entre parenthèses()
une liste de soi-disant qualificateurs glob qui sélectionnent les fichiers souhaités. Dans votre cas, ce seraitIci,
On
les noms de fichiers sont triés par ordre alphabétique dans l'ordre inverse et[1,4]
ne prend que les 4 premiers d'entre eux.Vous pouvez rendre cela plus robuste en sélectionnant uniquement les fichiers simples (à l'exception des répertoires, des canaux, etc.) avec
.
, et également en ajoutant--
à lacp
commande afin de traiter les fichiers dont les noms commencent-
correctement, donc:Ajoutez le
D
qualificatif si vous souhaitez également considérer les fichiers cachés (fichiers dot):la source
*([-4,-1])
.on
) est un comportement par défaut, donc pas besoin de le spécifier, et-
devant les nombres compte les noms de fichiers à partir du bas.Voici une solution utilisant des commandes bash extrêmement simples.
Explication:
Recherche tous les fichiers du répertoire actuel.
Trie par ordre alphabétique.
Afficher uniquement les 4 dernières lignes.
Boucles sur chaque ligne exécutant la commande de copie.
la source
Tant que vous trouvez le tri du shell agréable, vous pouvez simplement faire:
Ceci est très similaire à la
bash
solution de tableau spécifique offerte, mais devrait être portable sur n'importe quel shell compatible POSIX. Quelques notes sur les deux méthodes:Il est important que vous meniez vos
cp
arguments aveccp --
un ou.
un point ou/
à la tête de chaque nom. Si vous ne le faites pas, vous risquez un premier-
tiret danscp
le premier argument de qui peut être interprété comme une option et entraîner l'échec de l'opération ou rendre autrement des résultats inattendus.set -- ./*
ouarray=(./*)
.Il est important lorsque vous utilisez les deux méthodes de vous assurer d'avoir au moins autant d'éléments dans votre tableau arg que vous essayez de supprimer - je le fais ici avec une extension mathématique. Le
shift
seul problème se produit s'il y a au moins 4 éléments dans le tableau d'arguments - et ne déplace ensuite que les premiers arguments qui font un surplus de 4.set 1 2 3 4 5
cas, cela déplacera l'1
écart, mais dans unset 1 2 3
cas, cela ne changera rien.a=(1 2 3); echo "${a[@]: -4}"
imprime une ligne vierge.Si vous copiez d'un répertoire à un autre, vous pouvez utiliser
pax
:... qui appliquerait une
sed
substitution -style à tous les noms de fichiers lors de leur copie.la source
S'il n'y a que des fichiers et que leurs noms ne contiennent pas d'espace ou de nouvelle ligne (et que vous n'avez pas modifié
$IFS
) ou de caractères globaux (ou certains caractères non imprimables avec certaines implémentations dels
), et ne commencent pas par.
, alors vous pouvez le faire :la source
a_
à TOUS les quatre fichiers? J'ai essayéecho "a_$(ls | tail -n 4)"
, mais il n'ajoute que le premier fichier.ls
sortie est considérée comme mauvaise, car elle échoue généralement horriblement sur les noms de fichiers qui contiennent non seulement des espaces, mais également des tabulations, des sauts de ligne ou d'autres caractères valides mais "difficiles".Il s'agit d'une solution bash simple sans code de boucle. Copiez les 4 derniers fichiers du répertoire actuel vers la destination:
la source
find
vous donnez les fichiers dans l'ordre souhaité? Comment vous assurez-vous que les fichiers contenant des espaces (y compris les retours à la ligne) dans leurs noms sont traités correctement?