J'ai un dossier contenant plus d'un million de fichiers qui doit être trié, mais je ne peux rien faire car mv
affiche ce message tout le temps
-bash: /bin/mv: Argument list too long
J'utilise cette commande pour déplacer des fichiers sans extension:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
xargs
est l'outil pour le travail. Cela, ou find
avec -exec … {} +
. Ces outils exécutent une commande plusieurs fois, avec autant d'arguments que possible en une seule fois.
Les deux méthodes sont plus faciles à exécuter lorsque la liste des arguments variables est à la fin, ce qui n'est pas le cas ici: l'argument final de mv
est la destination. Avec GNU utilitaires (c'est-à-dire sur Linux non intégré ou Cygwin), le -t
option à mv
est utile, pour passer la destination en premier.
Si les noms de fichier ne contiennent ni espace ni \"'
, vous pouvez simplement fournir les noms de fichiers en entrée dans xargs
(la commande echo
est une commande bash, elle n'est donc pas soumise à la limite de longueur de ligne de commande):
echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir
Vous pouvez utiliser le -0
option à xargs
pour utiliser une entrée délimitée par des valeurs nulles au lieu du format entre guillemets par défaut.
printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir
Alternativement, vous pouvez générer la liste des noms de fichiers avec find
. Pour éviter de revenir dans des sous-répertoires, utilisez -type d -Prune
. Étant donné qu'aucune action n'est spécifiée pour les fichiers image répertoriés, seuls les autres fichiers sont déplacés.
find . -name . -o -type d -Prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec mv -t targetdir/ {} +
(Cela inclut les fichiers à points, contrairement aux méthodes de caractères génériques Shell.)
Si vous ne disposez pas des utilitaires GNU, vous pouvez utiliser un shell intermédiaire pour obtenir les arguments dans le bon ordre. Cette méthode fonctionne sur tous les systèmes POSIX.
find . -name . -o -type d -Prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec sh -c 'mv "$@" "$0"' targetdir/ {} +
Dans zsh, vous pouvez charger le mv
builtin :
setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/
ou si vous préférez laisser mv
et d'autres noms continuer à se référer aux commandes externes:
setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/
ou avec des globes de style ksh:
setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/
Alternativement, en utilisant GNU mv
et zargs
:
autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/
Si travailler avec le noyau Linux est suffisant, vous pouvez simplement le faire
ulimit -S -s unlimited
Cela fonctionnera car le noyau Linux a inclus il y a environ 10 ans un correctif qui a modifié la limite d'argument pour qu'elle soit basée sur la taille de la pile: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/ linux.git/commit /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba
Si vous ne voulez pas d'espace de pile illimité, vous pouvez dire par exemple.
ulimit -S -s 100000
pour limiter la pile à 100 Mo. Notez que vous devez définir l'espace de pile sur une utilisation normale de la pile (généralement 8 Mo) plus la taille de la ligne de commande que vous souhaitez utiliser.
Vous pouvez interroger la limite réelle comme suit:
getconf ARG_MAX
qui affichera la longueur maximale de la ligne de commande en octets. Par exemple, les paramètres par défaut d'Ubuntu définissent ce paramètre sur 2097152
ce qui signifie environ 2 Mo. Si je cours avec une pile illimitée, j'obtiens 4611686018427387903
qui est exactement 2 ^ 62 ou environ 46000 To. Si votre ligne de commande dépasse , je m'attends à ce que vous puissiez contourner le problème par vous-même.
La limite de passage d'arguments du système d'exploitation ne s'applique pas aux extensions qui se produisent dans l'interpréteur Shell. Ainsi, en plus d'utiliser xargs
ou find
, nous pouvons simplement utiliser une boucle Shell pour diviser le traitement en commandes individuelles mv
:
for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done
Cela utilise uniquement les fonctionnalités et utilitaires du langage de commande POSIX Shell. Cette doublure est plus claire avec une indentation, avec des points-virgules inutiles supprimés:
for x in *; do
case "$x" in
*.jpg|*.png|*.bmp)
;; # nothing
*) # catch-all case
mv -- "$x" target
;;
esac
done
Parfois, il est plus facile d'écrire simplement un petit script, par exemple en Python:
import glob, shutil
for i in glob.glob('*.jpg'):
shutil.move(i, 'new_dir/' + i)
Pour une solution plus agressive que celles précédemment proposées, extrayez la source de votre noyau et modifiez include/linux/binfmts.h
Augmentez la taille de MAX_ARG_PAGES
à quelque chose de plus grand que 32. Cela augmente la quantité de mémoire que le noyau autorisera pour les arguments du programme, vous permettant ainsi de spécifier votre commande mv
ou rm
pour un million de fichiers ou tout ce que vous voulez ' re faisant. Recompilez, installez, redémarrez.
IL FAUT SE MÉFIER! Si vous définissez cette valeur trop grande pour votre mémoire système, puis exécutez une commande avec beaucoup d'arguments, MAUVAIS CHOSES ARRIVERONT! Soyez extrêmement prudent en faisant cela sur des systèmes multi-utilisateurs, cela permet aux utilisateurs malveillants d'utiliser plus facilement toute votre mémoire!
Si vous ne savez pas comment recompiler et réinstaller votre noyau manuellement, il est probablement préférable de prétendre que cette réponse n'existe pas pour l'instant.
Une solution plus simple utilisant "$Origin"/!(*.jpg|*.png|*.bmp)
au lieu d'un bloc catch:
for file in "$Origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done
Merci à @Score_Under
Pour un script multiligne, vous pouvez effectuer les opérations suivantes (notez le ;
avant que le done
soit supprimé):
for file in "$Origin"/!(*.jpg|*.png|*.bmp); do # don't copy types *.jpg|*.png|*.bmp
mv -- "$file" "$destination"
done
Pour faire une solution plus généralisée qui déplace tous les fichiers, vous pouvez faire le one-liner:
for file in "$Origin"/*; do mv -- "$file" "$destination" ; done
Qui ressemble à ceci si vous faites une indentation:
for file in "$Origin"/*; do
mv -- "$file" "$destination"
done
Cela prend chaque fichier dans l'origine et les déplace un par un vers la destination. Les citations autour de $file
sont nécessaires en cas d'espaces ou d'autres caractères spéciaux dans les noms de fichiers.
Voici un exemple de cette méthode qui a parfaitement fonctionné
for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
Vous pouvez contourner cette restriction tout en utilisant mv
si cela ne vous dérange pas de l'exécuter plusieurs fois.
Vous pouvez déplacer des portions à la fois. Disons par exemple que vous aviez une longue liste de noms de fichiers alphanumériques.
mv ./subdir/a* ./
Ça marche. Puis éliminez un autre gros morceau. Après quelques mouvements, vous pouvez simplement recommencer à utiliser mv ./subdir/* ./
Voici mes deux cents, ajoutez ceci dans .bash_profile
mv() {
if [[ -d $1 ]]; then #directory mv
/bin/mv $1 $2
Elif [[ -f $1 ]]; then #file mv
/bin/mv $1 $2
else
for f in $1
do
source_path=$f
#echo $source_path
source_file=${source_path##*/}
#echo $source_file
destination_path=${2%/} #get rid of trailing forward slash
echo "Moving $f to $destination_path/$source_file"
/bin/mv $f $destination_path/$source_file
done
fi
}
export -f mv
Usage
mv '*.jpg' ./destination/
mv '/path/*' ./destination/