junte una gran cantidad de archivos juntos en el orden correcto

23

Tengo unos 15,000 archivos con nombres file_1.pdb, file_2.pdbetc. Puedo ordenar unos pocos miles de estos en orden haciendo:

cat file_{1..2000}.pdb >> file_all.pdb

Sin embargo, si hago esto para 15,000 archivos, me sale el error

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

He visto resolver este problema haciendo find . -name xx -exec xxesto, pero esto no preservaría el orden con el que se unen los archivos. ¿Cómo puedo conseguir esto?

nitrato de sodio
fuente
3
¿Cómo se llama el décimo archivo? (O cualquier archivo con más de un dígito ordenado numerado.)
roaima
Tengo (ahora) 15,000 de estos archivos en un directorio y su cat file_{1..15000}.pdbconstrucción funciona bien para mí.
roaima
11
Depende del sistema cuál es el límite. getconf ARG_MAXdebería decir.
ilkkachu
3
Considere cambiar su pregunta a "miles de" o "una gran cantidad de archivos". Podría hacer que la pregunta sea más fácil de encontrar para otras personas con un problema similar.
msouth

Respuestas:

49

Utilizando find, sorty xargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

El findcomando encuentra todos los archivos relevantes, luego imprime sus nombres de ruta para sorthacer un "ordenamiento de versiones" para obtenerlos en el orden correcto (si los números en los nombres de archivo hubieran sido rellenados con ceros a un ancho fijo, no hubiéramos necesitado -V). xargstoma esta lista de nombres de ruta ordenados y los ejecuta caten lotes tan grandes como sea posible.

Esto debería funcionar incluso si los nombres de archivo contienen caracteres extraños como líneas nuevas y espacios. Usamos -print0with findpara dar sortnombres terminados en nulos para ordenar, y los sortmaneja usando -z. xargslee también nombres terminados en nulo con su -0bandera.

Tenga en cuenta que estoy escribiendo el resultado en un archivo cuyo nombre no coincide con el patrón file_*.pdb.


La solución anterior utiliza algunos indicadores no estándar para algunas utilidades. Estos son compatibles con la implementación de GNU de estas utilidades y al menos con la implementación de OpenBSD y macOS.

Los indicadores no estándar utilizados son

  • -maxdepth 1, para hacer que findsolo ingrese el directorio superior pero no subdirectorios. POSIXY, usefind . ! -name . -prune ...
  • -print0, para findgenerar nombres de ruta terminados en nulo (esto fue considerado por POSIX pero rechazado). Se podría usar-exec printf '%s\0' {} + en lugar.
  • -z, para hacer sort tomar registros terminados en nulos. No hay equivalencia POSIX.
  • -V, para sortordenar, por ejemplo, 200después3 . No hay equivalencia POSIX, pero podría reemplazarse por una clasificación numérica en partes específicas del nombre de archivo si los nombres de archivo tienen un prefijo fijo.
  • -0, para hacer xargsregistros leídos con terminación nula. No hay equivalencia POSIX. POSIXY, uno necesitaría citar los nombres de archivo en un formato reconocido por xargs.

Si los nombres de ruta se comportan bien, y si la estructura del directorio es plana (sin subdirectorios), uno podría prescindir de estos indicadores, excepto -Vcon sort.

Kusalananda
fuente
1
No necesita terminación nula no estándar para esto. Estos nombres de archivo son extremadamente aburridos y las herramientas POSIX son completamente capaces de manejar en ese momento.
Kevin
66
También podría escribir esto más sucintamente con la especificación del autor de la pregunta como printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, o incluso con el punto de Kevin, echo file_{1..15000}.pdb | xargs cat. La findsolución tiene una sobrecarga considerablemente mayor ya que tiene que buscar esos archivos en el sistema de archivos, pero es más útil cuando algunos de los archivos pueden no existir.
kojiro
44
@Kevin aunque lo que dices es cierto, podría decirse que es mejor tener una respuesta que se aplique en circunstancias más generales. De las siguientes mil personas que tienen esta pregunta, es probable que algunas de ellas tengan espacios o lo que sea en sus nombres de archivo.
msouth
1
@chrylis Una redirección nunca es parte de los argumentos de un comando, y es xargsmás que cateso se redirige (cada catinvocación utilizará xargsla salida estándar). Si hubiéramos dicho, xargs -0 sh -c 'cat >all.pdb'entonces tendría sentido usarlo en >>lugar de >, si eso es lo que estás insinuando.
Kusalananda
1
Parece sort -n -k1.6que funcionaría (para el original, los file_nnnnombres de archivo o sort -n -k1.5para los que no tienen el guión bajo).
Scott
14

Con zsh(de dónde {1..15000}proviene ese operador):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

O para todos los file_<digits>.pdbarchivos en orden numérico:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(donde <x-y>es un operador global que coincide con los números decimales xay. Sin xni y, es cualquier número decimal. Equivalente a extendedglob's [0-9]##o kshglob' s +([0-9])(uno o más dígitos)).

Con ksh93, usando su catcomando incorporado (por lo que no se ve afectado por ese límite de la execve()llamada del sistema ya que no hay ejecución ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

Con bash/ zsh/ ksh93(que ayuda zsh's {x..y}y tienen printfincorporado):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

En un sistema GNU o compatible, también puede usar seq:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

Para las xargssoluciones basadas, se debe tener especial cuidado con los nombres de archivos que contienen espacios en blanco, comillas simples o dobles o barras invertidas.

Me gusta para -It's a trickier filename - 12.pdb, use:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb
Stéphane Chazelas
fuente
El seq -f | xarg cat > es la solución más elegante y eficaz. (EN MI HUMILDE OPINIÓN).
Hastur
Verifique el nombre de archivo más complicado ... '"./-It'\''s a trickier filename - %.17g.pdb"'¿ tal vez ?
Hastur
@Hastur, ¡Uy! Sí, gracias, lo he cambiado a una sintaxis alternativa de citas. El tuyo también funcionaría.
Stéphane Chazelas
11

Un bucle for es posible, y muy simple.

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

La desventaja es que invocas catmuchas veces. Pero si no puede recordar exactamente cómo hacer las cosas findy la sobrecarga de la invocación no es tan mala en su situación, entonces vale la pena tenerlo en cuenta.

Entidad omnipotente
fuente
A menudo agrego un echo $i;en el cuerpo del bucle como un "indicador de progreso"
Rolf
3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb
LarryC
fuente
1
awk puede hacer el trabajo de ss ss aquí y puede hacer el trabajo de awk: seq -f file_%.10g.pdb 15000. Tenga en cuenta que seqno es un comando estándar.
Stéphane Chazelas
Gracias Stéphane. Creo que seq -f es una excelente forma de hacerlo. Lo recordaré.
LarryC
2

Premisa

No debe incurrir en ese error solo para 15k archivos con ese formato de nombre específico [ 1 , 2 ] .

Si está ejecutando esa expansión desde otro directorio y tiene que agregar la ruta a cada archivo, el tamaño de su comando será mayor y, por supuesto, puede ocurrir.

La solución ejecuta el comando desde ese directorio.

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

La mejor solución Si en cambio adiviné mal y lo ejecutas desde el directorio en el que están los archivos ... En
mi humilde opinión, la mejor solución es la de Stéphane Chazelas :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

con printf o seq; probado en archivos de 15k con solo su número dentro de la memoria caché previa, es incluso el más rápido (en la actualidad, excepto el OP del mismo directorio en el que se encuentran los archivos).

Algunas palabras mas

Debería poder pasar a sus líneas de comando de shell más tiempo.
Su línea de comando tiene 213914 caracteres y contiene 15003 palabras.
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... incluso agregar 8 bytes para cada palabra es 333 938 bytes (0.3M) muy por debajo del 2097142 (2.1M) reportado ARG_MAXen un kernel 3.13.0 o el 2088232 ligeramente más pequeño reportado como "Longitud máxima de comando que podríamos realmente utilizar " porxargs --show-limits

Eche un vistazo en su sistema a la salida de

getconf ARG_MAX
xargs --show-limits

Solución guiada de la pereza

En casos como este, prefiero trabajar con bloques, incluso porque generalmente sale una solución eficiente en el tiempo.
La lógica (si la hay) es que soy demasiado vago para escribir 1 ... 1000 1001..2000, etc., etc.
Así que le pido a un script que lo haga por mí.
Solo después de comprobar que la salida es correcta, la redirijo a un script.

... pero la pereza es un estado mental .
Como soy alérgico a xargs(realmente debería haberlo usado xargsaquí) y no quiero comprobar cómo usarlo, termino puntualmente para reinventar la rueda como en los ejemplos a continuación (tl; dr).

Tenga en cuenta que, dado que los nombres de los archivos están controlados (sin espacios, líneas nuevas ...), puede seguir fácilmente algo como el script a continuación.

tl; dr

Versión 1: pase como parámetro opcional el primer número de archivo, el último, el tamaño del bloque, el archivo de salida

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

Versión 2

Llamando a bash para la expansión (un poco más lento en mis pruebas ~ 20%).

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

Por supuesto, puede avanzar y deshacerse por completo de seq [ 3 ] (de coreutils) y trabajar directamente con las variables en bash, o usar python, o compilar un programa de CA para hacerlo [ 4 ] ...

Hastur
fuente
Tenga en cuenta que %ges la abreviatura de %.6g. Representaría 1,000,000 como 1e + 06 por ejemplo.
Stéphane Chazelas
Las personas realmente perezosas usan las herramientas diseñadas para la tarea de evitar esa limitación de E2BIG como xargs, zsh's zargso ksh93's command -x.
Stéphane Chazelas
seqno es un bash incorporado, es un comando de GNU coreutils. seq -f %g 1000000 1000000salidas 1e + 06 incluso en la última versión de coreutils.
Stéphane Chazelas
@ StéphaneChazelas La pereza es un estado mental. Es extraño decirlo, pero me siento más cómodo cuando puedo ver (y verifico visualmente la salida de un comando serializado) y solo luego redirijo a la ejecución. Esa construcción me da que pensar menos que xarg... pero entiendo que es personal y tal vez solo está relacionado conmigo.
Hastur
@ StéphaneChazelas Gotcha, a la derecha ... Corregido. Gracias. Probé solo con los archivos de 15k dados por el OP, mi mal.
Hastur
0

Otra forma de hacerlo podría ser

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
glglgl
fuente