Как обрабатывать персонажи файлов один за другим эффективно

62
4

Есть ли способ перебора символа файла по символу и выборочной замены символов на основе определенных условий?

Я нашел способ, используя while цикл и sed полезность:

while IFS= read -r -N 1 old; do
...
sed -i 's/'$old'/'$new'/g' "$1"
done < "$1"

Я думаю, что этот способ очень медленный для больших файлов.

Есть ли способ, которым я могу достичь этого более эффективно?

спросил(а) 2021-01-25T20:21:41+03:00 4 месяца, 2 недели назад
1
Решение
107

В вашем подходе есть 2 убийцы производительности:

    Использование цикла оболочки для обработки данных.

    Вызов внешней утилиты (sed) на каждой итерации этого цикла.

      В частности, нет причин переписывать файл на каждой итерации вашего цикла. Кроме того, как уже указывалось, вы заменяете файл, который вы читаете, на каждой итерации, которая не будет работать.

Альтернативы:

    Как было предложено, sed может быть всем, что вам нужно, потому что он поддерживает цепочку нескольких вызовов s///;), а также использование наборов символов и диапазонов в данном вызове.

    tr - эффективная утилита, которая также поддерживает наборы и диапазоны, но она ограничена отображением символов 1 к 1 (вы не можете сопоставить данный символ с несколькими выходными символами).

Если вам действительно нужно обработать символ по символу, используйте утилиту обработки текста, а не код оболочки; например, с awk:

$ awk -F'\0' '{ for(i=1;i<=NF;++i) { printf "[%s]", $i }; print "" }' <<<$'abc\ncde'
[a][b][c]
[c][d][e]

    -F '\0' говорит Awk разбивать каждую строку на отдельные символы, с $1 представляющей $1 -й символ,... и NF отражающий количество символов в строке.

    Команда example просто включает каждый символ. в [...] чтобы продемонстрировать, что обработка каждого символа работает; print "" в конце просто испускает конечный \n.

    Чтобы объединить это с обновлением на месте (свободно говоря), используйте:
    awk -F'\0' '{... }' "$1" > "$1.$$" && mv "$1.$$" "$1"

      С помощью GNU Awk v4. 1+ вы также можете использовать -i inplace чтобы получить то же поведение, что и с sed -i.

ответил(а) 2021-01-25T20:21:41+03:00 4 месяца, 2 недели назад
45

Было бы намного быстрее загрузить весь файл за один раз, сгенерировать желаемый результат, а затем записать все сразу.

Вы можете сделать что-то вроде:

input=$(<"$1")
output=''
for ((i=0; i<${#input}; i++)); do
old=${input:i:1}
...
output+=$new
done

printf '%s' "$output" > "$1"

ответил(а) 2021-01-25T20:21:41+03:00 4 месяца, 2 недели назад
-4

Я наконец нашел то, что искал! Я написал следующий код с одним, когда строки для чтения цикла, и один для считывания цикла каждым символом в конкретной строке. Этот путь быстрее, и новая линия остается нетронутой! Я был бы рад, если бы этот ответ помог другим парням тоже!

#!/bin/bash
lineCounter=1
while IFS='' read -r line || [[ -n "$line" ]]; do
output=''
for (( i=0; i<${#line}; i++ )); do

oldChar=$( printf "${line:$i:1}" )

...Compute newChar...

output+=$newChar

done
line2=""
line2+=$lineCounter
line2+="s"
sed -i "$line2/.*/$output/" "$1"
lineCounter=$((($lineCounter) +1))
done < "$1"

ответил(а) 2021-01-25T20:21:41+03:00 4 месяца, 2 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема