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.