Schnelle Methode zum Teilen von Zeichenfolgen aus Textdateien?

11

Ich habe zwei Textdateien: string.txt und lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Ich möchte die Datei erhalten

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

Ich arbeite mit ungefähr 28.000 Einträgen und sie variieren zwischen 200 und 56.000 Zeichen.

Im Moment benutze ich:

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

Aber es ist sehr ineffizient. Irgendwelche besseren Ideen?

user3891532
quelle
Wie str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txtwäre es .. scheint schnell genug, wie nur von Shell gemacht ..
heemayl
Es ist nicht viel schneller, um ehrlich zu sein. Es dauert immer noch ziemlich lange. Ich bin ziemlich neu in Linux / Programmierung. Wenn Sie also glauben, dass es eine schnellere Methode gibt, die nicht nur Shell verwendet, bin ich offen für Ideen.
user3891532
4
Versuchen Sie es { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
Jimmyij
@jimmij, wie wäre es, das in eine Antwort zu
stecken

Antworten:

7

Du kannst tun

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

Es bedarf einiger Erklärung:

Die Hauptidee ist die Verwendung { head ; } <fileund wird aus der unterschätzten Antwort von @mikeserv abgeleitet . In diesem Fall müssen wir jedoch viele heads verwenden, daher wird eine whileSchleife eingeführt und ein wenig an den Dateideskriptoren angepasst, um die headEingabe aus beiden Dateien zu übergeben (Datei String.txtals zu verarbeitende Hauptdatei und Zeilen length.txtals Argument zur -cOption). . Die Idee ist, dass der Geschwindigkeitsvorteil dadurch entstehen sollte, dass nicht String.txtjedes Mal, wenn ein Befehl gefällt headoder cutaufgerufen wird, nach ihm gesucht werden muss. Das echoist nur, um nach jeder Iteration eine neue Zeile zu drucken.

Wie viel es schneller ist (falls vorhanden) und das Hinzufügen >Entry_izwischen Zeilen bleibt als Übung übrig.

jimmij
quelle
Ordentliche Verwendung der E / A-Umleitung. Da das Tag Linux ist, können Sie davon ausgehen, dass die Shell Bash ist und read -u 3zum Lesen aus Deskriptor 3 verwendet werden.
Jonathan Leffler
@ JonathanLeffler, Linux hat wenig damit zu tun bash. Die große Mehrheit der Linux-basierten Systeme ist nicht bashinstalliert (denken Sie an Android und andere eingebettete Systeme). bashDa es sich um die langsamste Shell von allen handelt, wird das Umschalten auf Bash die Leistung wahrscheinlich stärker beeinträchtigen als der geringe Gewinn, den das Umschalten von read <&3auf mit read -u3sich bringen könnte (was in jedem Fall unbedeutend ist im Vergleich zu den Kosten für die Ausführung eines externen Befehls wie head). Ein Wechsel zu ksh93, das bereits headintegriert ist (und die nicht standardmäßige -cOption unterstützt), würde die Leistung erheblich verbessern.
Stéphane Chazelas
Beachten Sie, dass das Argument von head -c(für die headImplementierungen, bei denen diese nicht standardmäßige Option verfügbar ist) eine Anzahl von Bytes und keine Zeichen ist. Dies würde bei Multi-Byte-Gebietsschemas einen Unterschied machen.
Stéphane Chazelas
7

Im Allgemeinen möchten Sie keine Shell-Schleifen zum Verarbeiten von Text verwenden . Hier würde ich verwenden perl:

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

Das ist ein Befehl, der liest (mit Pufferung viel effizienter als die der Shellread beide Dateien nur einmal liest (ohne dass sie vollständig im Speicher gespeichert sind) Dies wird mehrere Größenordnungen effizienter sein als Lösungen, die externe Befehle in einer Shell-Schleife ausführen.

(Fügen Sie die -COption hinzu, wenn diese Zahlen die Anzahl der Zeichen im aktuellen Gebietsschema und nicht die Anzahl der Bytes sein sollen. Bei ASCII-Zeichen wie in Ihrem Beispiel macht dies keinen Unterschied.)

Stéphane Chazelas
quelle
Dies ist eine komplizierte Wiederverwendung $_sowohl als Ausgabe- als auch als Eingabeparameter read, reduziert jedoch die Anzahl der Bytes im Skript.
Jonathan Leffler
In einem Schnelltest (die OP-Probe wurde 100000 Mal wiederholt) stelle ich fest, dass diese Lösung ungefähr 1200 Mal so schnell ist wie die von @ jimmij (0,3 Sekunden gegenüber 6 Minuten (mit bash, 16 Sekunden mit PATH=/opt/ast/bin:$PATH ksh93)).
Stéphane Chazelas
6

Bash, Version 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

Ausgabe

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

Was ist mit awk?

Erstellen Sie eine Datei process.awkmit dem folgenden Code:

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

Speichern Sie es und führen Sie es aus awk -f process.awk lengths.txt string.txt

jcbermu
quelle
Aufgrund der Verwendung von PROCINFOist dies jedoch kein Standard awk, aber gawk. In diesem Fall würde ich eine andere gawkeinzige Funktion bevorzugen , die FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
Manatwork