Método rápido de dividir string de um arquivo de texto?

11

Eu tenho dois arquivos de texto: string.txt e lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Eu quero pegar o arquivo

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

Estou trabalhando com cerca de 28.000 entradas e elas variam entre 200 e 56.000 caracteres.

No momento, estou 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

Mas é muito ineficiente. Alguma idéia melhor?

    
por user3891532 12.08.2015 / 13:15

4 respostas

7

Você pode fazer

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

Requer alguma explicação:

A ideia principal é usar { head ; } <file e é derivado da resposta @mikeserv subestimada . No entanto, neste caso, precisamos usar muitos head s, então while loop é introduzido e um pouco de ajustes com descritores de arquivos para passar para head input de ambos os arquivos (arquivo String.txt como principal arquivo para processar e linhas de length.txt como um argumento para a opção -c ). A idéia é que o benefício na velocidade deve vir da necessidade de não buscar o String.txt sempre que um comando como head ou cut for chamado. O echo é apenas para imprimir nova linha após cada iteração.

O quanto é mais rápido (se houver) e a adição de >Entry_i entre linhas é deixada como um exercício.

    
por 12.08.2015 / 14:50
7

Geralmente, você não deseja usar loops de shell para processar texto . Aqui, eu usaria perl :

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

Isso é um comando, que lê (com buffer muito mais eficientemente do que o comando read do shell que lê um byte (ou alguns bytes para arquivos regulares) de cada vez) ambos os arquivos somente uma vez (sem armazená-los completos na memória), portanto, serão várias ordens de magnitude mais eficientes do que as soluções que executam comandos externos em um loop de shell.

(adicione a opção -C se esses números forem números de caracteres na localidade atual, ao contrário do número de bytes. Para caracteres ASCII como em sua amostra, isso não fará diferença alguma).

    
por 12.08.2015 / 15:01
6

bash, versão 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

saída

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz
    
por 12.08.2015 / 15:08
4

Que tal awk ?

Crie um arquivo chamado process.awk com 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)
        }
}

Salve e execute awk -f process.awk lengths.txt string.txt

    
por 12.08.2015 / 15:23