Lista de argumentos demasiado larga al copiar archivos

Respuestas:

36

cp *.prj ../prjshp/es el comando correcto, pero ha encontrado un caso raro en el que se encuentra con una limitación de tamaño. El segundo comando que probaste no tiene ningún sentido.

Un método es ejecutar cplos archivos en fragmentos. El findcomando sabe cómo hacer esto:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find atraviesa el directorio actual y los directorios debajo de él de forma recursiva.
  • -maxdepth 1 significa detenerse a una profundidad de 1, es decir, no recurrir a subdirectorios.
  • -name '*.prj'significa actuar solo en los archivos cuyo nombre coincide con el patrón especificado. Tenga en cuenta las comillas alrededor del patrón: será interpretado por el findcomando, no por el shell.
  • -exec … {} +significa ejecutar el comando especificado para todos los archivos. Invoca el comando varias veces si es necesario, teniendo cuidado de no exceder el límite de la línea de comando.
  • mv -t ../prjshpmueve los archivos especificados a ../prjshp. La -topción se usa aquí debido a una limitación del findcomando: los archivos encontrados (simbolizados por {}) se pasan como el último argumento del comando, no puede agregar el destino después.

Otro método es usar rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpcopia el directorio actual en forma ../prjshprecursiva.
  • --include='*.prj' --exclude='*'significa copiar archivos que coinciden *.prjy excluir todo lo demás (incluidos los subdirectorios, por .prjlo que no se encontrarán archivos en subdirectorios).
Gilles 'SO- deja de ser malvado'
fuente
3
rsync, con mucho, la solución más fácil aquí.
ntk4
Para ser algo quisquilloso, el segundo comando cp * | grep '\.prj$' ../prjshp/ no tiene ningún sentido, pero puede ser sintácticamente válido, si se *expande a la lista de archivos, siendo el último un directorio (también conocido como cp SOURCE1 SOURCE2....DEST). La tubería no tiene ningún sentido, claro, pero también sigue siendo sintácticamente válida en lo que respecta al shell: dup()los descriptores de archivos estarán bien, es solo que el extremo del lector de la tubería no obtendrá ningún dato porque cpno escribe ninguno .
Sergiy Kolodyazhnyy
Tanto find como rsync produjeron el mismo error de lista de argumentos demasiado largo para mí. El bucle for fue la solución más simple.
Meezaan-ud-Din
De hecho, rsync es la forma de hacer una copia masiva, aunque estoy perplejo por lo lejos que hemos llegado con Linux y tenemos un error / error tonto como este y sí, lo consideraría un error / error.
MitchellK
22

Este comando copia los archivos uno por uno y funcionará incluso si hay demasiados para *expandirlos en un solo cpcomando:

for i in *; do cp "$i" ../prjshp/; done
ccshields
fuente
Esto funciona para mi.
1rq3fea324wre
1
Simple y efectivo. Tuve un problema similar al eliminar ~ 1/4 millones de jpegs que había extraído de un video para un proyecto. Este es el enfoque que utilicé.
Élder Geek
5

Hay 3 puntos clave a tener en cuenta al enfrentar un Argument list too longerror:

  • La longitud de los argumentos de la línea de comandos está limitada por la ARG_MAXvariable, que según la definición POSIX es "... [m] longitud máxima de argumento para las funciones ejecutivas, incluidos los datos del entorno" (énfasis agregado) ". Es decir, cuando el shell ejecuta un no -compilado, tiene que llamar a uno de ellos exec()para generar el proceso de ese comando, y ahí es donde ARG_MAXentra en juego. Además, el nombre o la ruta al comando en sí (por ejemplo /bin/echo) juega un papel importante.

  • Los comandos integrados de Shell se ejecutan mediante shell, lo que significa que el shell no utiliza la exec()familia de funciones y, por lo tanto, no se ve afectado por la ARG_MAXvariable.

  • Ciertos comandos, como xargsy findson conscientes de la ARG_MAXvariable y realizan acciones repetidamente por debajo de ese límite

De los puntos anteriores y como se muestra en la excelente respuesta de Kusalananda a la pregunta relacionada, esto Argument list too longtambién puede ocurrir cuando el entorno es grande. Por lo tanto, teniendo en cuenta que el entorno de cada usuario puede variar, y el tamaño del argumento en bytes es relevante, es difícil encontrar un solo número de archivos / argumentos.

¿Cómo manejar tal error?

La clave es centrarse no en la cantidad de archivos, sino en si el comando que va a utilizar implica una exec()familia de funciones y tangencialmente: el espacio de la pila.

Use incorporados en shell

Como se discutió anteriormente, las funciones integradas de shell son inmunes al ARG_MAXlímite, es decir, cosas como forloop, whileloop, built-in echoy built-in printf: todas ellas funcionarán lo suficientemente bien.

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

En la pregunta relacionada sobre la eliminación de archivos, había una solución como tal:

printf '%s\0' *.jpg | xargs -0 rm --

Tenga en cuenta que esto usa el shell incorporado printf. Si llamamos a lo externo printf, eso implicará exec(), por lo tanto, fallará con una gran cantidad de argumentos:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

matrices de bash

Según una respuesta de jlliagre, bashno impone límites a las matrices, por lo que también se puede construir una matriz de nombres de archivo y usar segmentos por iteración de bucle, como se muestra en la respuesta de danjpreron :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

Esto, sin embargo, tiene la limitación de ser específico de bash y no POSIX.

Aumenta el espacio de la pila

A veces puede ver que la gente sugiere aumentar el espacio de la pila con ulimit -s <NUM>; en Linux, el valor ARG_MAX es 1/4 del espacio de pila para cada programa, lo que significa que aumentar el espacio de pila aumenta proporcionalmente el espacio para argumentos.

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

De acuerdo con la respuesta de Franck Dernoncourt , que cita Linux Journal, también se puede recompilar el kernel de Linux con mayor valor para páginas de memoria máxima para argumentos, sin embargo, eso es más trabajo del necesario y abre el potencial para explotaciones como se indica en el artículo citado de Linux Journal.

Evitar la cáscara

Otra forma, es usar pythono python3que viene por defecto con Ubuntu. El ejemplo de python + here-doc a continuación, es algo que personalmente utilicé para copiar un gran directorio de archivos en algún lugar en el rango de 40,000 elementos:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

Para recorridos recursivos, puede usar os.walk .

Ver también:

Sergiy Kolodyazhnyy
fuente
2

En mi humilde opinión, las herramientas óptimas para tratar con hordas de archivos son findy xargs. Ver man find. Ver man xargs. find, con su modificador -print0, produce una NULlista separada de nombres de archivo (los nombres de archivo pueden contener cualquier execpt de caracteres NULo /) que xargscomprenda, utilizando el modificador -0. xargsluego construye el comando más largo permitido (la mayoría de los nombres de archivo, sin medio nombre de archivo al final) y lo ejecuta. xargsrepite esto hasta findque no proporcione más nombres de archivo. Corre xargs --show-limits </dev/nullpara ver los límites.

Para resolver su problema, (y después de verificar man cppara encontrar --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
Waltinator
fuente