Замініть кілька рядків за один прохід


11

Я шукаю спосіб замінити рядки заповнювача у файлі шаблону конкретними значеннями, спільними інструментами 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

Правильний результат у цьому випадку, звичайно, бакалавр.

Загалом, рішення повинно бути еквівалентним скануванню вводу зліва направо на найдовший збіг до одного із заданих рядків заміни та для кожного матчу, виконуючи заміну та продовжуючи з цього моменту на вході (жоден з вже прочитаний вхід і заміни не повинні враховуватися для відповідностей). Насправді, деталі не мають значення, лише те, що результати заміни ніколи не враховуються для іншої заміни, повністю або частково.

ПРИМІТКА Я шукаю тільки правильні загальні рішення. Будь ласка, не пропонуйте рішення, які не спрацьовують для певних входів (файли введення, пошук і заміна пар), хоч вони малоймовірні.


Я припускаю, що вони довші одного персонажа? Для цього ви могли б скористатися tr AB BA.
Кевін

3
І, чесно кажучи, я не здивувався б, якби хтось вважав вашу замітку трохи грубою.
петерф

1
Як ви очікуєте "отримати правильні рішення" лише тоді, коли ви не надали зразок введення чи виводу?
jasonwryan

1
Боюся, вам потрібно буде робити це саме так, як ви його описуєте - розбирайте з початку і замінюйте по мірі руху, тобто не з регулярними виразами.
петерф

2
Це справедливе запитання, але відповідь - вам потрібен аналізатор державної машини , саме це дає відповідь rici (я думаю, що в справжньому стилі хакера). Іншими словами, ви недооцінюєте складність завдання, ала "Я хочу загально проаналізувати (HT | X) ML з регулярними виразами" -> Відповідь "НІ". Ви не можете (просто) використовувати sed. Ви не можете (просто) використовувати awk. AFAIK не існує жодного інструменту, який би це зробив нестандартно. Якщо експлуатувати Sans Rici, вам потрібно буде написати якийсь код.
goldilocks

Відповіді:


10

Гаразд, загальне рішення. Наступна функція bash вимагає 2kаргументів; кожна пара складається із заповнювача та заміни. Вам належним чином цитувати рядки, щоб передати їх у функцію. Якщо кількість аргументів непарна, буде доданий неявний порожній аргумент, який ефективно видалить входження останнього заповнювача.

Ні заповнювачі, ні заміни не можуть містити символів NUL, але ви можете використовувати стандартні C- \діаграми, наприклад, \0якщо вам потрібно NULs (і, отже, вам потрібно написати, \\якщо ви хочете \).

Для цього потрібні стандартні інструменти побудови, які повинні бути присутніми в системі, подібній до пози (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. Ще більш амбітна реалізація може намагатися кешувати згенеровані файли, а не створювати їх щоразу, але їх генерувати не зовсім дорого.

Редагувати

Якщо у вас є tcc , ви можете уникнути клопоту щодо створення тимчасового каталогу та насолоджуватися швидшим часом компіляції, який допоможе нормальним розмірам даних:

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

Я не впевнений, це жарт чи ні;)
Амброз Біжак

3
@ambrozbizjak: Це працює, це швидко для великих входів і прийнятно швидко для невеликих входів. Він може не використовувати інструменти, про які ви думали, але вони є стандартними інструментами. Чому це був би жарт?
rici

4
+1 За те, що не жарт! : D
goldilocks

Це був би портативний POSIX fn() { tcc ; } <<CODE\n$(gen code)\nCODE\n. Чи можу я поцікавитись - це дивовижна відповідь, і я відхилив її, як тільки прочитав - але я не розумію, що відбувається з масивом оболонок? Що "${@//\"/\\\"}"це робить?
mikeserv

@mikeserv: «Для кожного аргументу як значення котирування (" $ @ ") замініть усі (//) входження цитати (\") на (/) зворотний косий рядок (\\), а потім цитата (\ ") ». Див. Розширення параметрів у посібнику з bash.
rici

1
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в потоці в один біт на рядок. Це найшвидший спосіб, коли я можу уявити, що ти це зробиш. Знову ж , я не пишу C. Але це дійсно надійно обробляти порожні роздільники , якщо ви хочете його. Дивіться цю відповідь, як це працює. Це має ніяких проблем з якою - або не містять спеціальні символи оболонки або подібне - але це ASCII локалі конкретних, або, іншими словами, odне вихід мультибайтних символів на одній і тій же лінії і буде робити тільки один пров. Якщо це проблема, яку ви хочете додати iconv.


+1 Чому ви вважаєте, що це замінює лише "найчастіше появу цільових рядків"? На виході виглядає так, ніби він замінює їх усіх. Я не прошу бачити це, але чи можна це зробити так, не жорстко кодуючи значення?
goldilocks

@goldilocks - Так - але лише після їх виникнення. Можливо, я повинен це переробити. І так - ви можете просто додати середину sedі зберегти до нуля або чогось іншого, щоб sedзаписати цей сценарій; або покладіть його на функцію оболонки і дайте їй значення по одному шматочку за рядок, наприклад "/$1/"... "/$2/"- можливо, я теж напишу ці функції ...
mikeserv

Це не схоже на роботу в тому випадку , коли наповнювачі PLACE1, PLACE2і PLA. PLAзавжди виграє. ОП говорить: "еквівалентно скануванню вводу зліва направо на найдовший збіг з одним із заданих рядків заміни" (наголос додано)
rici

@rici - спасибі Тоді мені доведеться робити нульові роздільники. Назад мить.
mikeserv

@rici - Я збирався опублікувати ще одну версію, яка буде працювати з тим, що ти описуєш, але переглянувши її ще раз, і я не думаю, що слід. Він каже, що найдовше для однієї із заданих рядків заміни. Це робить це. Немає ознак того, що одна рядок є підмножиною іншої, лише те, що може бути замінене значення. Я також не вважаю, що повторення списку є вагомим способом вирішення проблеми. З огляду на проблему, наскільки я її розумію, це робоче рішення.
mikeserv

1

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

Checkerbunny:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.