Быстрый метод разделения строки из текстового файла?


11

У меня есть два текстовых файла: string.txt и lengths.txt

String.txt:

abcdefghijklmnopqrstuvwxyz

lengths.txt

5
4
10
7

Я хочу получить файл

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

Я работаю с около 28 000 записей, и они варьируются от 200 до 56 000 символов.

На данный момент я использую:

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

Но это очень неэффективно. Есть идеи получше?


Как насчет ... str="$(cat string.txt)"; i=0; while read j; do echo "${file:$i:$j}"; i=$((i+j)); done <length.txtкажется достаточно быстрым, как это делает только снаряд ..
Heemayl

Это не намного быстрее, если честно. Это все еще занимает довольно много времени. Я довольно новичок в Linux / программировании, так что если вы думаете, что есть более быстрый метод не только с использованием оболочки, я открыт для идей.
user3891532

4
Попробуй { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
Джимми

@jimmij, как насчет того, чтобы
вставить

Ответы:


7

Ты можешь сделать

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

Это требует некоторого объяснения:

Основная идея заключается в использовании { head ; } <fileи получена из недооцененного ответа @mikeserv . Однако в этом случае нам нужно использовать много heads, поэтому whileвведен цикл и немного доработаны с файловыми дескрипторами, чтобы перейти к headвходным данным из обоих файлов (файл String.txtв качестве основного файла для обработки и строки из length.txtв качестве аргумента для -cопции) , Идея заключается в том, что выигрыш в скорости должен заключаться в том, что вам не нужно искать String.txtкаждый раз, когда команда headили команда cutвызывается. echoПросто напечатать строку после каждой итерации.

Насколько это быстрее (если есть) и сложение >Entry_iмежду строками оставлено в качестве упражнения.


Аккуратное использование перенаправления ввода / вывода. Поскольку тег - это Linux, вы можете разумно предположить, что оболочкой является Bash и использовать read -u 3для чтения из дескриптора 3.
Джонатан Леффлер,

@JonathanLeffler, Linux имеет мало общего с bash. Подавляющее большинство систем на основе Linux не bashустановлено (например, Android и другие встроенные системы). bashбудучи самой медленной оболочкой всего, переход на баш, скорее всего , к снижению производительности более существенно , чем небольшой прирост , что переход от read <&3к read -u3могут принести (который в любом случае будет незначительным по сравнению со стоимостью запуска внешней команды , как head). Переключение на ksh93 со headвстроенной (и поддерживающей нестандартную -cопцию) значительно улучшило бы производительность.
Стефан Шазелас

Обратите внимание, что аргументом head -c(для headреализаций, где доступна эта нестандартная опция) является количество байтов, а не символов. Это будет иметь значение в многобайтовых локалях.
Стефан Шазелас

7

Как правило, вы не хотите использовать циклы оболочки для обработки текста . Здесь я бы использовал perl:

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

Это одна команда, которая читает (с буферизацией намного более эффективно, чем команда оболочки, readкоторая читает один байт (или несколько байтов для обычных файлов) за раз) оба файла только один раз (без сохранения их в памяти), поэтому будет на несколько порядков эффективнее решений, которые запускают внешние команды в цикле оболочки.

(добавьте -Cопцию, если эти числа должны быть числами символов в текущей локали, а не количеством байтов. Для символов ASCII, как в вашем примере, это не будет иметь никакого значения).


Это запутанное повторное использование $_как выходного, так и входного параметра read, но это уменьшает количество байтов в скрипте.
Джонатан Леффлер

В быстром тесте (образец ОП повторяется 100000 раз), я обнаружил, что это решение примерно в 1200 раз быстрее, чем @ jimmij (0,3 секунды против 6 минут (с bash, 16 секунд с PATH=/opt/ast/bin:$PATH ksh93)).
Стефан Шазелас

6

Баш, версия 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

выход

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

4

Как насчет awk?

Создайте файл process.awkс таким кодом:

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

Сохраните его и выполните awk -f process.awk lengths.txt string.txt


На основании использования PROCINFO, это не стандартно awk, но gawk. В этом случае я бы предпочел еще одну gawkособенность FIELDWIDTHS:awk -vFIELDWIDTHS="$(tr '\n' ' ' < lengths.txt)" '{for(i=1;i<=NF;i++)print">Entry"i ORS$i}' string.txt
manatwork
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.