Я ищу способ заменить строки-заполнители в файле шаблона на конкретные значения с помощью обычных инструментов Unix (bash, sed, awk, возможно perl). Важно, чтобы замена была выполнена за один проход, то есть то, что уже просканировано/заменено, не должно рассматриваться для другой замены. Например, эти две попытки не увенчались успехом:
echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA
echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA
Правильный результат в этом случае, конечно же, BA.
В общем случае решение должно быть эквивалентно сканированию ввода слева направо для нахождения самого длинного совпадения с одной из заданных строк замены, и для каждого совпадения, выполнению замены и продолжению с этой точки ввода (ни один из уже прочитанных вводов, ни выполненные замены не должны рассматриваться для совпадений). На самом деле, детали не важны, важно только то, что результаты замены никогда не рассматриваются для другой замены, полностью или частично.
ПРИМЕЧАНИЕЯ ищу только правильные общие решения. Пожалуйста, не предлагайте решения, которые не работают для определенных входных данных (входные файлы, пары поиска и замены), какими бы маловероятными они ни казались.
решение1
Хорошо, общее решение. Следующая функция bash требует 2k
аргументов; каждая пара состоит из заполнителя и замены. Вам нужно правильно заключить строки в кавычки, чтобы передать их в функцию. Если число аргументов нечетное, будет добавлен неявный пустой аргумент, который фактически удалит вхождения последнего заполнителя.
Ни заполнители, ни замены не могут содержать символы NUL, но вы можете использовать стандартные экранированные символы C, \
например, \0
если вам нужен NUL
s (и, следовательно, вам необходимо написать , \\
если вам нужен \
).
Для этого требуются стандартные инструменты сборки, которые должны присутствовать в posix-подобной системе (lex и cc).
replaceholder() {
local dir=$(mktemp -d)
( cd "$dir"
{ printf %s\\n "%option 8bit noyywrap nounput" "%%"
printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
} | lex && cc lex.yy.c
) && "$dir"/a.out
rm -fR "$dir"
}
Мы предполагаем, что \
уже экранировано, если необходимо, в аргументах, но нам нужно экранировать двойные кавычки, если они есть. Это то, что делает второй аргумент второго printf. Поскольку действие lex
по умолчанию — ECHO
, нам не нужно об этом беспокоиться.
Пример запуска (с указанием времени для скептиков; это всего лишь дешевый ноутбук):
$ time echo AB | replaceholder A B B A
BA
real 0m0.128s
user 0m0.106s
sys 0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null
real 0m0.118s
user 0m0.117s
sys 0m0.043s
Для больших входных данных может быть полезно предоставить флаг оптимизации для cc
, а для текущей совместимости с Posix лучше использовать c99
. Еще более амбициозная реализация может попытаться кэшировать сгенерированные исполняемые файлы вместо того, чтобы генерировать их каждый раз, но их генерация не так уж и затратна.
Редактировать
Если у вас естьтсс, вы можете избежать хлопот, связанных с созданием временного каталога, и наслаждаться более быстрым временем компиляции, что полезно для входных данных обычного размера:
treplaceholder () {
tcc -run <(
{
printf %s\\n "%option 8bit noyywrap nounput" "%%"
printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
} | lex -t)
}
$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null
real 0m0.039s
user 0m0.041s
sys 0m0.031s
решение2
printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
/\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
/\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
/\\n/!{x;d};s/\n//g;s/./\\&/g' |
xargs printf %b
###OUTPUT###
STRING2STRING2
STRING1STRING2
STRING1
Что-то вроде этого всегда будет заменять каждое вхождение ваших целевых строк только один раз, поскольку они встречаются в sed
's в потоке по одному байту на строку. Это самый быстрый способ, который я могу себе представить, чтобы вы это сделали. С другой стороны, я не пишу на C. Но этоделаетнадежно обрабатывать нулевые разделители, если вы этого хотите. Смотретьэтот ответдля того, как это работает. Это не имеет проблем с любыми содержащимися специальными символами оболочки или подобными - но этоявляетсяСпецифическая локаль ASCII, или, другими словами, od
не будет выводить многобайтовые символы на одной строке и будет выводить только один на. Если это проблема, вам нужно будет добавить в iconv
.
решение3
Решение perl
. Даже если некоторые утверждали, что это невозможно, я нашел одно, но в целом простое сопоставление и замена невозможны, и даже становится хуже из-за возврата NFA, результат может быть неожиданным.
В общем случае, и это следует отметить, задача дает разные результаты, которые зависят от порядка и длины заменяющих кортежей, а именно:
A B
AA CC
AAA
и результат ввода — BBB
или CCB
.
Вот код:
#!/usr/bin/perl
$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
$k.=$a.'|';
$v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';
eval "
while (<>) {
\$_ =~ s/($k)/{$v}/geco;
}";
print "\n";
__DATA__
A B
B A
abba baab
baab abbc
abbc aaba
Шашечник:
$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba