-bash: /usr/bin/mv: Lista de argumentos demasiado larga

-bash: /usr/bin/mv: Lista de argumentos demasiado larga

Cuando uso este comando mv * .., aparece el error -bash: /usr/bin/mv: Argument list too long. Entiendo que la razón por la que esto ocurre es porque bash en realidad expande el asterisco a cada archivo coincidente, lo que produce una línea de comando muy larga. ¿Cómo puedo solucionar este error?

Respuesta1

El límite no está en bash sino en la execve()llamada al sistema utilizada para ejecutar comandos externos. Podrías hacerlo:

printf '%s\0' * | xargs -r0 mv -t .. --

Dado que printfestá integrado bash, no existe execve()en ese momento. Y es xargstrabajo dividir la lista de argumentos para evitar ese límite. Aquí usando la -topción específica de GNU de GNU mv.

Con zsh, puedes cargar el mvincorporado:

zmodload zsh/files
mv -- * ..

O use su zargsayuda para hacer la división:

autoload -Uz zargs # best in ~/.zshrc
zargs -r -- ./* -- mv -t ..

Puede reemplazar ./*con ./*(D)para mover también archivos ocultos o agregar el oNcalificador global para omitir la clasificación de nombres de archivos, o el Ncalificador global (con zargs -r) para evitar el error si no hay ningún archivo coincidente.

zargs -r -- ./*(ND) -- mv -t ..

Igual que:

print -rNC1 ./*(ND) | xargs -r0 mv -t ..

Pero sin la dependencia de GNU xargs.

En Linux (el kernel que se usa normalmente en Ubuntu), también puedes aumentar ese execve()límite aumentando el stacksizelímite de recursos:

bash-5.1$ /bin/true {1..200000}
bash: /bin/true: Argument list too long
bash-5.1$ ulimit -s unlimited
bash-5.1$ /bin/true {1..200000}
bash-5.1$ 

No es completamente ilimitado (al menos no en las versiones actuales del kernel):

bash-5.1$ /bin/true {1..2000000}
bash: /bin/true: Argument list too long

Tenga en cuenta que el límite está en el tamaño acumulado de los argumentos y las variables de entorno pasadas execve(), pero el cálculo no es solo la suma de los bytes allí y la forma en que se hace varía según el sistema operativo y la versión del mismo.

Respuesta2

Podrías hacerlo en dos o más pasos:

mv [a-k]* ..    # or some other pattern matching a subset of the files
mv -- * ..

O en un bucle,

for name in *; do
    mv -- "$name" ..
done

(Pero esto requeriría mvtodos y cada uno de los nombres individualmente).

O ponte finda ayudarte:

find . -mindepth 1 -maxdepth 1 -exec mv -t .. -- {} +

Esto encontraría todos los nombres en el directorio actual y, usando GNU mv, los movería al directorio de arriba con la menor cantidad de invocaciones mvposible.

Sin GNU mv, pero con un sistema que aún conoce los predicados y findno estándar ,-mindepth-maxdepth

find . -mindepth 1 -maxdepth 1 -exec sh -c 'mv -- "$@" ..' sh {} +

A ninguna de estas variaciones le importan las colisiones de nombres. Debe realizar pruebas con datos que tengan una copia de seguridad adecuada.

información relacionada