Metodo rapido per dividere la stringa dal file di testo?


11

Ho due file di testo: string.txt e lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Voglio ottenere il file

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

Sto lavorando con circa 28.000 voci e variano tra 200 e 56.000 caratteri.

Al momento, sto 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

Ma è molto inefficiente. Qualche idea migliore?


Che ne dici di ... str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txtsembra abbastanza veloce come fatto solo da shell ..
heemayl

Non è molto più veloce per essere onesti. Ci vuole ancora molto tempo. Sono abbastanza nuovo per Linux / programmazione, quindi se pensi che ci sia un metodo più veloce non solo usando la shell, sono aperto alle idee.
user3891532

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

@jimmij, che ne dici di inserirla in una risposta
iruvar

Risposte:


7

Tu puoi fare

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

Richiede alcune spiegazioni:

L'idea principale è quella di utilizzare { head ; } <fileed è derivata dalla risposta @mikeserv sottovalutata . Tuttavia, in questo caso dobbiamo usare molti heads, quindi whileviene introdotto un ciclo e un po 'di modifica con i descrittori di file per passare headall'input da entrambi i file (file String.txtcome file principale da elaborare e linee da length.txtcome argomento a -copzione) . L'idea è che il vantaggio in termini di velocità dovrebbe derivare dal non dover cercare String.txtogni volta che un comando gradisce heado cutviene invocato. Il echoè solo per la stampa a capo dopo ogni iterazione.

Quanto è più veloce (se presente) e l'aggiunta >Entry_itra le righe viene lasciata come un esercizio.


Uso corretto del reindirizzamento I / O. Poiché il tag è Linux, puoi ragionevolmente supporre che la shell sia Bash e che usi read -u 3per leggere dal descrittore 3.
Jonathan Leffler

@JonathanLeffler, Linux ha poco a che fare con bash. La grande maggioranza dei sistemi basati su Linux non è bashinstallata (pensate ad Android e ad altri sistemi embedded). bashessendo il guscio più lento di tutti, il passaggio a bash probabilmente ridurrà le prestazioni in modo più significativo rispetto al piccolo guadagno che il passaggio da read <&3a read -u3potrebbe portare (che in ogni caso sarà insignificante rispetto al costo di eseguire un comando esterno come head). Passare a ksh93 che ha headincorporato (e uno che supporta l' -copzione non standard ) migliorerebbe molto di più le prestazioni.
Stéphane Chazelas,

Si noti che l'argomento di head -c(per le headimplementazioni in cui è disponibile l'opzione non standard) è un numero di byte, non caratteri. Ciò farebbe la differenza nelle localizzazioni multi-byte.
Stéphane Chazelas,

7

In genere, non si desidera utilizzare i loop di shell per elaborare il testo . Qui, vorrei usare perl:

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

Questo è un comando, che legge (con il buffering in modo molto più efficiente del readcomando della shell che legge un byte (o pochi byte per i file regolari) alla volta) entrambi i file una sola volta (senza archiviarli in memoria), quindi è molti ordini di grandezza saranno più efficienti delle soluzioni che eseguono comandi esterni in un loop di shell.

(aggiungi l' -Copzione se quei numeri dovrebbero essere numeri di caratteri nella locale corrente anziché numero di byte. Per i caratteri ASCII come nel tuo esempio, ciò non farà alcuna differenza).


Questo è un riuso contorto di $_entrambi i parametri di output e input read, ma riduce il conteggio dei byte nello script.
Jonathan Leffler,

In un test rapido (il campione del PO ripetuto 100000 volte), trovo che questa soluzione sia circa 1200 volte più veloce di quella di @ jimmij (0,3 secondi contro 6 minuti (con bash, 16 secondi con PATH=/opt/ast/bin:$PATH ksh93)).
Stéphane Chazelas,

6

bash, versione 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

produzione

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

4

Che dire awk?

Crea un file chiamato process.awkcon questo codice:

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)
        }
}

Salvalo ed eseguilo awk -f process.awk lengths.txt string.txt


Basato sull'uso di PROCINFO, questo non è standard awk, ma gawk. In tal caso, preferirei un'altra gawkcaratteristica unica, il FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
manatwork del
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.