¿Resolver "mv: Lista de argumentos demasiado larga"?


64

Tengo una carpeta con más de un millón de archivos que necesita ser ordenada, pero realmente no puedo hacer nada porque mvgenera este mensaje todo el tiempo

-bash: /bin/mv: Argument list too long

Estoy usando este comando para mover archivos sin extensión:

mv -- !(*.jpg|*.png|*.bmp) targetdir/

Respuestas:


82

xargsEs la herramienta para el trabajo. Eso o findcon -exec … {} +. Estas herramientas ejecutan un comando varias veces, con tantos argumentos como se pueden pasar de una vez.

Ambos métodos son más fáciles de llevar a cabo cuando la lista de argumentos variables está al final, lo cual no es el caso aquí: el argumento final mves el destino. Con las utilidades GNU (es decir, en Linux no integrado o Cygwin), la -topción mves útil, para pasar el destino primero.

Si los nombres de archivo no tienen espacios en blanco ni ninguno \"', simplemente puede proporcionar los nombres de archivo como entrada xargs(el echocomando es un bash incorporado, por lo que no está sujeto al límite de longitud de la línea de comando):

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir

Puede usar la -0opción para xargsusar una entrada delimitada por nulos en lugar del formato predeterminado entre comillas.

printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir

Alternativamente, puede generar la lista de nombres de archivo con find. Para evitar recurrir a subdirectorios, use -type d -prune. Como no se especifica ninguna acción para los archivos de imagen enumerados, solo se mueven los otros archivos.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +

(Esto incluye archivos de puntos, a diferencia de los métodos comodín de shell).

Si no tiene utilidades GNU, puede usar un shell intermedio para obtener los argumentos en el orden correcto. Este método funciona en todos los sistemas POSIX.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +

En zsh, puede cargar el mvarchivo incorporado :

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/

o si prefiere dejar que mvotros nombres sigan refiriéndose a los comandos externos:

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/

o con globos de estilo ksh:

setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/

Alternativamente, usando GNU mvy zargs:

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/

1
Los primeros dos comandos devolvieron "-bash:!: Evento no encontrado" y los dos siguientes no movieron ningún archivo. Estoy en CentOS 6.5 si debes saberlo
Dominique

1
@Dominique Usé la misma sintaxis global que usaste en tu pregunta. Tendrás shopt -s extglobque habilitarlo. Me perdí un paso en los findcomandos, los arreglé.
Gilles 'SO- deja de ser malvado'

Obtengo esto con el comando de búsqueda "find: expresión no válida; ha utilizado un operador binario '-o' sin nada antes". Ahora probaré los otros.
Dominique

@Dominique Los findcomandos que he publicado (ahora) funcionan. Debe haber dejado una parte al copiar y pegar.
Gilles 'SO- deja de ser malvado'

Gilles, para los comandos de búsqueda, ¿por qué no usar el operador "no" !? Es más explícito y más fácil de entender que el final extraño -o. Por ejemplo,! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan

13

Si trabajar con el kernel de Linux es suficiente, simplemente puede hacer

ulimit -s 100000

eso funcionará porque el kernel de Linux incluyó un parche hace unos 10 años que cambió el límite de argumento para basarse en el tamaño de la pila: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ commit /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Actualización: si te sientes valiente, puedes decir

ulimit -s unlimited

y estará bien con cualquier expansión de shell siempre que tenga suficiente RAM.


Eso es un truco. ¿Cómo sabrías a qué establecer el límite de la pila? Esto también afecta a otros procesos iniciados en la misma sesión.
Kusalananda

1
Sí, es un truco. La mayoría de las veces este tipo de hacks son únicos (¿con qué frecuencia mueves manualmente una gran cantidad de archivos de todos modos?). Si está seguro de que el proceso no va a consumir toda su RAM, puede configurarlo ulimit -s unlimitedy funcionará para archivos prácticamente ilimitados.
Mikko Rantalainen

Con ulimit -s unlimitedel límite de línea de comando real es 2 ^ 31 o 2 GB. ( MAX_ARG_STRLENen la fuente del núcleo.)
Mikko Rantalainen

9

El límite de aprobación de argumentos del sistema operativo no se aplica a las expansiones que ocurren dentro del intérprete de shell. Entonces, además de usar xargso find, simplemente podemos usar un bucle de shell para dividir el procesamiento en mvcomandos individuales :

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done

Esto usa solo las características y utilidades del lenguaje de comandos de shell POSIX. Esta línea es más clara con sangría, con puntos y comas innecesarios eliminados:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done

Con más de un millón de archivos, esto a su vez generará más de un millón de mvprocesos, en lugar de solo los pocos necesarios con la findsolución POSIX que @Gilles publicó. En otras palabras, de esta manera se producen muchas pérdidas innecesarias de CPU.
CivFan

@CivFan Otro problema es convencerse de que la versión modificada es equivalente a la original. Es fácil ver que la casedeclaración sobre el resultado de la *expansión para filtrar varias extensiones es equivalente a la !(*.jpg|*.png|*.bmp)expresión original . La findrespuesta, de hecho, no es equivalente; desciende a subdirectorios (no veo un -maxdepthpredicado).
Kaz

-name . -o -type d -prune -oprotege de descender a subdirectorios. -maxdepthaparentemente no es compatible con POSIX, aunque eso no se menciona en mi findpágina de manual.
CivFan

Retrocedió a la revisión 1. La pregunta no dice nada acerca de las variables de origen o destino, por lo que esto agrega una respuesta innecesaria a la respuesta.
Kaz

5

Para obtener una solución más agresiva que las ofrecidas anteriormente, abra la fuente de su núcleo y edite include/linux/binfmts.h

Aumente el tamaño MAX_ARG_PAGESa algo más grande que 32. Esto aumenta la cantidad de memoria que el núcleo permitirá para argumentos de programa, lo que le permite especificar su mvo rmcomando para un millón de archivos o lo que sea que esté haciendo. Recompilar, instalar, reiniciar.

¡TENER CUIDADO! Si configura esto demasiado grande para la memoria de su sistema, y ​​luego ejecuta un comando con muchos argumentos ¡MALAS COSAS OCURRIRÁN! Sea extremadamente cauteloso al hacer esto con los sistemas multiusuario, ¡facilita a los usuarios maliciosos el uso de toda su memoria!

Si no sabe cómo volver a compilar y reinstalar su núcleo manualmente, probablemente sea mejor que simplemente finja que esta respuesta no existe por ahora.


5

Una solución más simple usando en "$origin"/!(*.jpg|*.png|*.bmp)lugar de un bloque catch:

for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done

Gracias a @Score_Under

Para una secuencia de comandos de varias líneas, puede hacer lo siguiente (observe ;antes de que donese elimine):

for file in "$origin"/!(*.jpg|*.png|*.bmp); do        # don't copy types *.jpg|*.png|*.bmp
    mv -- "$file" "$destination" 
done 

Para hacer una solución más generalizada que mueva todos los archivos, puede hacer una línea:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done

Que se ve así si haces sangría:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 

Esto toma cada archivo en el origen y los mueve uno por uno al destino. Las comillas $fileson necesarias en caso de que haya espacios u otros caracteres especiales en los nombres de archivo.

Aquí hay un ejemplo de este método que funcionó perfectamente

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done

Podría usar algo como el globo original en el ciclo for para obtener una solución más cercana a lo que se solicita.
Score_Under

¿A qué te refieres con glob original?
Whitecat

Lo siento si eso fue un poco críptico, me refiero a la pegote en la pregunta: !(*.jpg|*.png|*.bmp). Puede agregar eso a su bucle for haciendo un globo "$origin"/!(*.jpg|*.png|*.bmp)que evitaría la necesidad del interruptor utilizado en la respuesta de Kaz y mantener el cuerpo simple del bucle for.
Score_Under

Puntuación impresionante. Incorporé tu comentario y actualicé mi respuesta.
Whitecat

3

A veces es más fácil escribir un pequeño script, por ejemplo, en Python:

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)

1

Puede sortear esa restricción mientras la sigue usando mvsi no le importa ejecutarla un par de veces.

Puede mover porciones a la vez. Digamos, por ejemplo, que tenía una larga lista de nombres de archivos alfanuméricos.

mv ./subdir/a* ./

Eso funciona. Luego elimine otro gran pedazo. Después de un par de movimientos, puede volver a usarmv ./subdir/* ./


0

Aquí están mis dos centavos, agregue esto a .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

Uso

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.