У меня есть файл, содержащий список пар замены (около 100 из них), которые используются sed
для замены строк в файлах.
Пары выглядят так:
old|new
tobereplaced|replacement
(stuffiwant).*(too)|\1\2
И мой текущий код:
cat replacement_list | while read i
do
old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex
new=$(echo "$i" | awk -F'|' '{print $2}')
sed -r "s/`echo "$old"`/`echo "$new"`/g" -i file
done
Не могу не думать, что есть более оптимальный способ выполнения замен. Я попытался повернуть цикл, чтобы сначала просмотреть строки файла, но это оказалось намного дороже.
Есть ли другие способы ускорить этот скрипт?
ИЗМЕНИТЬ
Спасибо за все быстрые ответы. Прежде чем выбрать ответ, позвольте мне попробовать различные предложения.
Одна вещь, которую нужно прояснить: мне также нужна функциональность подвыражений / групп. Например, мне может понадобиться одна замена:
([0-9])U|\10 #the extra brackets and escapes were required for my original code
Некоторые подробности об улучшениях (будут обновлены):
- Метод: время обработки
- Исходный скрипт: 0.85 сек.
cut
вместоawk
: 0,71 с- метод анубхавы: 0,18 с
- метод chthonicdaemon: 0,01 с
9 ответов
Вы можете использовать sed
для создания правильно отформатированного ввода sed
:
sed -e 's/^/s|/; s/$/|g/' replacement_list | sed -r -f - file
sed: -e expression #1, char 17: unknown option to 's'
. символ 17 оказывается | разделитель в моем файле замен
file
в конце на <( whatever your command to generate input )
Недавно я протестировал различные методы замены строк, в том числе пользовательскую программу, sed -e
, perl -lnpe
и, вероятно, не очень известная утилита командной строки MySQL, replace
. replace
оптимизирован для замены строк. был почти на порядок быстрее, чем sed
. Результаты выглядели примерно так (сначала самые медленные):
custom program > sed > LANG=C sed > perl > LANG=C perl > replace
Если вам нужна производительность, используйте replace
. Однако, чтобы он был доступен в вашей системе, вам необходимо установить некоторый дистрибутив MySQL.
Из replace.c:
Заменить строки в текстовом файле
Эта программа заменяет строки в файлах или с stdin на stdout. Он принимает список пар от строки до строки и заменяет каждое вхождение строки от соответствующей строкой до. Сопоставляется первое вхождение найденной строки. Если существует более одной возможности для замены строки, более длинные совпадения предпочтительнее, чем более короткие совпадения.
...
Программы создают из строк конечный автомат DFA, и скорость не зависит от количества заменяемых строк (только от количества замен). Предполагается, что строка заканчивается \ n или \ 0. Нет ограничений на длину строк, кроме памяти.
Подробнее о sed. Вы можете использовать несколько ядер с sed, разделив ваши замены на группы #cpus, а затем перенаправив их через команды sed
, примерно так:
$ sed -e 's/A/B/g; ...' file.txt | \
sed -e 's/B/C/g; ...' | \
sed -e 's/C/D/g; ...' | \
sed -e 's/D/E/g; ...' > out
Кроме того, если вы используете sed
или perl
и ваша система имеет настройку UTF-8, то это также повышает производительность, если поместить LANG=C
перед командами:
$ LANG=C sed ...
-e
или N числом единичных команд sed? Когда N > 100.
N
замен в одной команде sed
, чем N
количество команд sed
. Помню, меня немного удивило, что параллельное выполнение нескольких сотен процессов не слишком сильно снизило производительность.
replace
может заменять только фиксированные строки. sd — аналогичный инструмент в rust.
Вы можете сократить ненужные вызовы awk и использовать BASH для разрыва пар имя-значение:
while IFS='|' read -r old new; do
# echo "$old :: $new"
sed -i "s~$old~$new~g" file
done < replacement_list
IFS = '|' даст разрешение на чтение для заполнения значения имени в двух разных переменных оболочки old
и new
.
Предполагается, что ~
не присутствует в ваших парах "имя-значение". Если это не так, не стесняйтесь использовать альтернативный разделитель sed.
([0-9])U|\\10
.
Вот что я бы попробовал:
- сохраните вашу пару поиска-замены
sed
в массиве Bash, например: - создайте свою команду sed на основе этого массива, используя расширение параметра
- Команда запуска.
patterns=(
old new
tobereplaced replacement
)
pattern_count=${#patterns[*]} # number of pattern
sedArgs=() # will hold the list of sed arguments
for (( i=0 ; i<$pattern_count ; i=i+2 )); do # don't need to loop on the replacement…
search=${patterns[i]};
replace=${patterns[i+1]}; # … here we got the replacement part
sedArgs+=" -e s/$search/$replace/g"
done
sed ${sedArgs[@]} file
Это приведет к этой команде:
sed -e s / старый / новый / g -e s / tobereplaced / замена / g файл
Вы можете попробовать это.
pattern=''
cat replacement_list | while read i
do
old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex
new=$(echo "$i" | awk -F'|' '{print $2}')
pattern=${pattern}"s/${old}/${new}/g;"
done
sed -r ${pattern} -i file
Это запустит команду sed только один раз для файла со всеми заменами. Вы также можете заменить awk
на cut
. cut
может быть более оптимизирован, чем awk
, хотя я в этом не уверен.
old=`echo $i | cut -d"|" -f1`
new=`echo $i | cut -d"|" -f2`
cut
действительно ускорил процесс, но бит шаблона на самом деле не работал. По какой-то причине первый символ имени файла, переданного sed
, был удален. Пытаюсь понять почему.
cat
и несколько ошибки цитирования не сулит ничего хорошего для этого ответа. Действовать с осторожностью.
Возможно, вы захотите проделать все это на awk:
awk -F\| 'NR==FNR{old[++n]=$1;new[n]=$2;next}{for(i=1;i<=n;++i)gsub(old[i],new[i])}1' replacement_list file
Составьте список старых и новых слов из первого файла. next
гарантирует, что остальная часть скрипта не будет запущена для первого файла. Для второго файла прокрутите список замен и выполните их каждую по очереди. 1
в конце означает, что строка напечатана.
sed
.
gensub
{ cat replacement_list;echo "-End-"; cat YourFile; } | sed -n '1,/-End-/ s/$/³/;1h;1!H;$ {g
t again
:again
/^-End-³\n/ {s///;b done
}
s/^\([^|]*\)|\([^³]*\)³\(\n\)\(.*\)\1/\1|\2³\3\4\2/
t again
s/^[^³]*³\n//
t again
:done
p
}'
Больше удовольствия от кода через sed. Попробуйте, может быть, какое-то время, потому что это запускает только 1 рекурсивный sed.
Для posix sed (так что --posix
с GNU sed)
< Сильный > объяснений
- скопируйте список замены перед содержимым файла с разделителем (для строки с
³
и для списка с-End-
) для упрощения обработки sed (трудно использовать \ n в символе класса в posix sed. - поместить всю строку в буфер (добавить разделитель строки для списка замены и -End- перед)
- если это
-End-³
, удалите строку и перейдите к окончательной печати - заменить каждый первый образец (группа 1) в тексте вторым образцом (группа 2)
- если найдено, перезапустите (
t again
) - удалить первую строку
- перезапустить процесс (
t again
). T необходим, потому чтоb
не сбрасывает тест, а следующийt
всегда истинен.
Спасибо @miku выше;
У меня есть файл размером 100 МБ со списком строк замены 80k.
Я пробовал различные комбинации sed последовательно или параллельно, но не увидел, что пропускная способность становится меньше, чем примерно 20-часовое время выполнения.
Вместо этого я помещаю свой список в последовательность сценариев вроде «cat in | replace aold anew bold bnew cold cnew ...> out; rm in; mv out in».
Я случайным образом выбрал 1000 замен для каждого файла, поэтому все происходило так:
# first, split my replace-list into manageable chunks (89 files in this case)
split -a 4 -l 1000 80kReplacePairs rep_
# next, make a 'replace' script out of each chunk
for F in rep_* ; do \
echo "create and make executable a scriptfile" ; \
echo '#!/bin/sh' > run_$F.sh ; chmod +x run_$F.sh ; \
echo "for each chunk-file line, strip line-ends," ; \
echo "then with sed, turn '{long list}' into 'cat in | {long list}' > out" ; \
cat $F | tr '\n' ' ' | sed 's/^/cat in | replace /;s/$/ > out/' >> run_$F.sh ;
echo "and append commands to switch in and out files, for next script" ; \
echo -e " && \\\\ \nrm in && mv out in\n" >> run_$F.sh ; \
done
# put all the replace-scripts in sequence into a main script
ls ./run_rep_aa* > allrun.sh
# make it executable
chmod +x allrun.sh
# run it
nohup ./allrun.sh &
.. который работал менее чем за 5 минут, намного меньше чем за 20 часов!
Оглядываясь назад, я мог бы использовать больше пар для каждого сценария, если бы определил, сколько строк составят предел.
xargs --show-limits </dev/null 2>&1 | grep --color=always "actually use:"
Maximum length of command we could actually use: 2090490
Так что чуть меньше 2 МБ; сколько пар это было бы для моего скрипта?
head -c 2090490 80kReplacePairs | wc -l
76923
Кажется, я мог бы использовать 2 * 40000-строчных фрагментов
Чтобы расширить решение chthonicdaemon
#! /bin/sh
# build regex from text file
REGEX_FILE=some-patch.regex.diff
# test
# set these with "export key=val"
SOME_VAR_NAME=hello
ANOTHER_VAR_NAME=world
escape_b() {
echo "$1" | sed 's,/,\\/,g'
}
regex="$(
(echo; cat "$REGEX_FILE"; echo) \
| perl -p -0 -e '
s/\n#[^\n]*/\n/g;
s/\(\(SOME_VAR_NAME\)\)/'"$(escape_b "$SOME_VAR_NAME")"'/g;
s/\(\(ANOTHER_VAR_NAME\)\)/'"$(escape_b "$ANOTHER_VAR_NAME")"'/g;
s/([^\n])\//\1\\\//g;
s/\n-([^\n]+)\n\+([^\n]*)(?:\n\/([^\n]+))?\n/s\/\1\/\2\/\3;\n/g;
'
)"
echo "regex:"; echo "$regex" # debug
exec perl -00 -p -i -e "$regex" "$@"
Строки с префиксом -+/
допускают пустые значения «плюс» и защищают начальные пробелы от ошибочных текстовых редакторов.
Пример ввода: some-patch.regex.diff
# file format is similar to diff/patch
# this is a comment
# replace all "a/a" with "b/b"
-a/a
+b/b
/g
-a1|a2
+b1|b2
/sg
# this is another comment
-(a1).*(a2)
+b\1b\2b
-a\na\na
+b
-a1-((SOME_VAR_NAME))-a2
+b1-((ANOTHER_VAR_NAME))-b2
Образец вывода
s/a\/a/b\/b/g;
s/a1|a2/b1|b2/;;
s/(a1).*(a2)/b\1b\2b/;
s/a\na\na/b/;
s/a1-hello-a2/b1-world-b2/;
Этот формат регулярного выражения совместим с sed и perl
Поскольку miku
упомянул mysql replace
: замена фиксированных строк регулярным выражением нетривиальна, так как вы должны экранировать все символы регулярных выражений, но вы также должны обрабатывать экранирование обратной косой черты...
Наивный беглец:
echo '\(\n' | perl -p -e 's/([.+*?()\[\]])/\\\1/g'
\\(\n
Похожие вопросы
Связанные вопросы
Новые вопросы
bash
Этот тег предназначен для вопросов о сценариях, написанных для командной оболочки Bash. Сценарии оболочки с синтаксисом или другими ошибками, пожалуйста, проверьте их на https://shellcheck.net, прежде чем публиковать здесь. Вопросы об интерактивном использовании Bash, скорее всего, будут актуальны на Unix & Linux Stack Exchange или Super User, чем на Stack Overflow.