¿Método rápido para dividir cadenas de archivos de texto?

11

Tengo dos archivos de texto: string.txt y lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Quiero obtener el archivo

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz

Estoy trabajando con unas 28,000 entradas y varían entre 200 y 56,000 caracteres.

Por el momento, estoy usando:

start=1
end=0
i=0
while read read_l
do
    let i=i+1
    let end=end+read_l
    echo -e ">Entry_$i" >>outfile.txt
    echo "$(cut -c$start-$end String.txt)" >>outfile.txt
    let start=start+read_l
    echo $i
done <lengths.txt

Pero es muy ineficiente. ¿Alguna idea mejor?

usuario3891532
fuente
¿Qué tal ... str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txtparece lo suficientemente rápido como lo hace solo por shell ..
heemayl
No es mucho más rápido para ser honesto. Todavía lleva bastante tiempo. Soy bastante nuevo en Linux / programación, así que si crees que hay un método más rápido que no solo usa shell, estoy abierto a ideas.
user3891532
44
Tratar { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
jimmij
@jimmij, ¿qué tal si escribimos eso en una respuesta?
iruvar

Respuestas:

7

Tu puedes hacer

{
  while read l<&3; do
    {
      head -c"$l"
      echo
    } 3<&-
  done 3<lengths.txt
} <String.txt

Requiere alguna explicación:

La idea principal es usar { head ; } <filey se deriva de la respuesta subestimada @mikeserv . Sin embargo, en este caso, necesitamos usar muchos heads, por lo que whilese introduce un bucle y se ajusta un poco con los descriptores de archivo para pasar a la headentrada de ambos archivos (archivo String.txtcomo archivo principal para procesar y líneas desde length.txtcomo argumento a -copción) . La idea es que el beneficio de la velocidad debe provenir de no tener que buscar a través de la String.txtcada vez que un comando como heado cutse invoca. El echoes solo para imprimir nueva línea después de cada iteración.

Cuánto más rápido (si lo hay) y agregar >Entry_ientre líneas se deja como ejercicio.

jimmij
fuente
Uso ordenado de la redirección de E / S. Dado que la etiqueta es Linux, puede suponer razonablemente que el shell es Bash y usarlo read -u 3para leer el descriptor 3.
Jonathan Leffler
@ JonathanLeffler, Linux tiene poco que ver bash. La gran mayoría de los sistemas basados ​​en Linux no tiene bashinstalado (piense en Android y otros sistemas integrados). bashSiendo el caparazón más lento de todos, el cambio a bash probablemente degradará el rendimiento de manera más significativa que la poca ganancia que podría generar el cambio de read <&3a read -u3(que en cualquier caso será insignificante en comparación con el costo de ejecutar un comando externo como head). Cambiar a ksh93 que tiene headincorporado (y uno que admite la -copción no estándar ) mejoraría mucho más el rendimiento.
Stéphane Chazelas
Tenga en cuenta que el argumento de head -c(para las headimplementaciones donde está disponible esa opción no estándar) es un número de bytes, no caracteres. Eso marcaría la diferencia en configuraciones regionales de varios bytes.
Stéphane Chazelas
7

En general, no desea utilizar bucles de shell para procesar texto . Aquí, usaría perl:

$ perl -lpe 'read STDIN,$_,$_; print ">Entry_" . ++$n' lengths.txt < string.txt
>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz

Ese es un comando, que lee (con almacenamiento en búfer de manera mucho más eficiente que el readcomando del shell que lee un byte (o unos pocos bytes para archivos normales) a la vez) ambos archivos solo una vez (sin almacenarlos llenos en la memoria), así es serán varios órdenes de magnitud más eficientes que las soluciones que ejecutan comandos externos en un bucle de shell.

(agregue la -Copción si esos números deben ser números de caracteres en la configuración regional actual en lugar de número de bytes. Para caracteres ASCII como en su muestra, eso no hará ninguna diferencia).

Stéphane Chazelas
fuente
Esa es una reutilización enrevesada $_como parámetro de salida y entrada read, pero reduce el recuento de bytes en el script.
Jonathan Leffler
En una prueba rápida (la muestra del OP se repite 100000 veces), encuentro que esta solución es aproximadamente 1200 veces más rápida que la de @ jimmij (0.3 segundos frente a 6 minutos (con bash, 16 segundos con PATH=/opt/ast/bin:$PATH ksh93)).
Stéphane Chazelas
6

bash, versión 4

mapfile -t lengths <lengths.txt
string=$(< String.txt)
i=0 
n=0
for len in "${lengths[@]}"; do
    echo ">Entry_$((++n))"
    echo "${string:i:len}"
    ((i+=len))
done

salida

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz
Glenn Jackman
fuente
4

¿Qué hay de awk?

Cree un archivo llamado process.awkcon este código:

function idx(i1, v1, i2, v2)
{
     # numerical index comparison, ascending order
     return (i1 - i2)
}
FNR==NR { a[FNR]=$0; next }
{ i=1;PROCINFO["sorted_in"] = "idx";
        for (j in a) {
                print ">Entry"j;
                ms=substr($0, i,a[j])
                print ms
                i=i+length(ms)
        }
}

Guárdalo y ejecuta awk -f process.awk lengths.txt string.txt

jcbermu
fuente
Según el uso de PROCINFO, esto no es estándar awk, pero gawk. En ese caso, preferiría otra gawkcaracterística única, la FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
manatwork