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 printf
está integrado bash
, no existe execve()
en ese momento. Y es xargs
trabajo dividir la lista de argumentos para evitar ese límite. Aquí usando la -t
opción específica de GNU de GNU mv
.
Con zsh
, puedes cargar el mv
incorporado:
zmodload zsh/files
mv -- * ..
O use su zargs
ayuda 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 oN
calificador global para omitir la clasificación de nombres de archivos, o el N
calificador 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 stacksize
lí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 mv
todos y cada uno de los nombres individualmente).
O ponte find
a 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 mv
posible.
Sin GNU mv
, pero con un sistema que aún conoce los predicados y find
no 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.