Szybka metoda podziału łańcucha z pliku tekstowego?

11

Mam dwa pliki tekstowe: string.txt i lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Chcę pobrać plik

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

Pracuję z około 28 000 wpisów, które różnią się między 200 a 56 000 znaków.

W tej chwili używam:

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

Ale to jest bardzo nieefektywne. Jakieś lepsze pomysły?

użytkownik3891532
źródło
Jak o str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txt..seems wystarczająco szybko, jak wykonać tylko skorupy ..
heemayl
Szczerze mówiąc, nie jest to dużo szybsze. To wciąż trwa dość długo. Jestem całkiem nowy w Linuksie / programowaniu, więc jeśli uważasz, że istnieje szybsza metoda nie tylko przy użyciu powłoki, jestem otwarty na pomysły.
user3891532,
4
Spróbować { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
jimmij
@jimmij, co powiesz na wpisanie tego w odpowiedź
iruvar,

Odpowiedzi:

7

Możesz to zrobić

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

Wymaga to wyjaśnienia:

Główną ideą jest użycie { head ; } <filei wywodzi się z niedocenianej odpowiedzi @mikeserv . Jednak w tym przypadku musimy użyć wielu heads, więc whilewprowadzono pętlę i trochę poprawiono za pomocą deskryptorów plików, aby przejść do headdanych wejściowych z obu plików (plik String.txtjako plik główny do przetworzenia, a wiersze length.txtjako argument do -copcji) . Chodzi o to, że korzyści płynące z prędkości powinny wynikać z braku konieczności przeszukiwania za String.txtkażdym razem, gdy polecenie takie jak headlub cutjest wywoływane. echoJest wydrukowanie nowej linii po każdej iteracji.

Ile to jest szybsze (jeśli w ogóle), a dodawanie >Entry_imiędzy liniami pozostawia się jako ćwiczenie.

jimmij
źródło
Staranne wykorzystanie przekierowania we / wy. Ponieważ znacznikiem jest Linux, można rozsądnie założyć, że powłoka to Bash i użyć read -u 3do odczytu z deskryptora 3.
Jonathan Leffler,
@JathanathanLeffler, Linux nie ma wiele do czynienia bash. Ogromna większość systemów opartych na Linuksie nie została bashzainstalowana (pomyśl Android i inne systemy wbudowane). bashponieważ jest to najwolniejsza powłoka ze wszystkich, przejście na bash prawdopodobnie obniży wydajność znacznie bardziej niż niewielki zysk, jaki może przynieść przejście z read <&3na read -u3(co w każdym razie będzie nieznaczne w porównaniu z kosztem uruchomienia zewnętrznego polecenia, takiego jak head). Przejście na headwbudowany ksh93 (i taki, który obsługuje niestandardową -copcję) poprawiłby znacznie wydajność.
Stéphane Chazelas,
Zauważ, że argumentem head -c(dla headimplementacji, w których dostępna jest ta niestandardowa opcja) jest liczba bajtów, a nie znaki. To by miało znaczenie w wielobajtowych lokalizacjach.
Stéphane Chazelas,
7

Ogólnie rzecz biorąc, nie chcesz używać pętli powłoki do przetwarzania tekstu . Tutaj użyłbym perl:

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

To jedno polecenie, które odczytuje (z buforowaniem o wiele bardziej wydajnie niż polecenie powłoki, readktóre odczytuje jeden bajt (lub kilka bajtów dla zwykłych plików) naraz) oba pliki tylko raz (bez przechowywania ich w pamięci), więc jest będzie kilka rzędów wielkości bardziej wydajnych niż rozwiązania uruchamiające zewnętrzne polecenia w pętli powłoki.

(dodaj -Copcję, jeśli liczby te powinny być liczbami znaków w bieżącym ustawieniu narodowym, a nie liczbą bajtów. W przypadku znaków ASCII, takich jak w twojej próbce, nie będzie to miało znaczenia).

Stéphane Chazelas
źródło
Jest to zawiłe ponowne użycie $_jako parametru wyjściowego i wejściowego do read, ale zmniejsza liczbę bajtów w skrypcie.
Jonathan Leffler,
W szybkim teście (próbka OP powtórzona 100 000 razy) stwierdzam, że to rozwiązanie jest około 1200 razy szybsze niż @ jimmij (0,3 sekundy vs 6 minut (z bash, 16 sekund z PATH=/opt/ast/bin:$PATH ksh93)).
Stéphane Chazelas,
6

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

wynik

>Entry_1
abcde
>Entry_2
fghi
>Entry_3
jklmnopqrs
>Entry_4
tuvwxyz
Glenn Jackman
źródło
4

Co awk?

Utwórz plik o nazwie process.awkz tym kodem:

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

Zapisz i uruchom awk -f process.awk lengths.txt string.txt

jcbermu
źródło
Na podstawie zastosowania PROCINFOnie jest to standard awk, ale gawk. W takim przypadku wolałbym inną gawkjedyną funkcję FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
manatwork