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?
cp ./<the final 4 files> ~/
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?
cp ./<the final 4 files> ~/
Réponses:
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
).
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éments bash
).
cp -- "${a[@]: -4}" ~/
Cela copie les quatre derniers noms de fichier dans votre répertoire personnel.
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é:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
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 cd
dans 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.
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:
LC_ALL=C a=(*)
Vous pouvez découvrir les paramètres régionaux dans lesquels vous vous trouvez actuellement en exécutant la locale
commande.
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 serait
cp *(On[1,4]) ~/
Ici, 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 --
à la cp
commande afin de traiter les fichiers dont les noms commencent -
correctement, donc:
cp -- *(.On[1,4]) ~
Ajoutez le D
qualificatif si vous souhaitez également considérer les fichiers cachés (fichiers dot):
cp -- *(D.On[1,4]) ~
*([-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.
find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
Explication:
find . -maxdepth 1 -type f
Recherche tous les fichiers du répertoire actuel.
sort
Trie par ordre alphabétique.
tail -n 4
Afficher uniquement les 4 dernières lignes.
while read -r file; do cp "$file" ~/; done
Boucles sur chaque ligne exécutant la commande de copie.
Tant que vous trouvez le tri du shell agréable, vous pouvez simplement faire:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
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 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 cp
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 -- ./*
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 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 un set 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
:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
... qui appliquerait une sed
substitution -style à tous les noms de fichiers lors de leur copie.
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 :
cp -- $(ls | tail -n 4) ~/
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:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
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?
LC_ALL=C