Швидкий метод розщеплення рядка з текстового файлу?


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 до 56000 символів.

На даний момент я використовую:

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 / програмуванні, тому якщо ви думаєте, що існує більш швидкий метод не тільки використання оболонки, я відкритий для ідей.
користувач3891532

4
Спробуйте { while read l<&3; do head -c"$l"; echo; done 3<lengths.txt; } <String.txt.
jimmij

@jimmij, як щодо того, щоб дотримуватися цього відповіді
iruvar

Відповіді:


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будучи найповільнішою оболонкою з усіх, перехід на bash, швидше за все, погіршить продуктивність значно більше, ніж малий приріст, який може принести перехід read <&3на на read -u3(що в будь-якому випадку буде незначним порівняно з вартістю виконання зовнішньої команди на зразок head). Перехід на headвбудований ksh93 (і той, що підтримує нестандартний -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

bash, версія 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.