Чисто поміняйте всі випадки двох струн за допомогою sed


13

Припустимо, у мене є файл, який містить кілька входів як StringA, так і StringB. Я хочу замінити всі виникнення StringA на StringB, і (одночасно) всі входження StringB на StringA.

Зараз я щось роблю

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

Проблема такого підходу полягає в тому, що він передбачає, що StringC не виникає у файлі. Хоча це не є проблемою на практиці, це рішення все ще відчуває себе брудною - тобто, це відчуває як можливість дізнатися більше магії Unix. :)

Відповіді:


11

Якщо StringBі StringAне можуть з'являтися на тій же рядку введення, то ви можете сказати СЕД , щоб виконати заміну в одну сторону, і тільки спробувати інший шлях , якби не було ніяких входжень першого шукали рядок.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

У загальному випадку, я не думаю, що існує легкий метод у sed. До речі, зауважте, що специфікація неоднозначна, якщо StringAі StringBможе перетинатися. Ось рішення Perl, яке замінює лівий край будь-якого рядка і повторюється.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

Якщо ви хочете дотримуватися інструментів POSIX, awk - це шлях. Awk не має примітиву для загальної параметризованої заміни, тому вам потрібно прокрутити власну.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

Коли я запускаю першу команду, sed мені каже sed: can't read s/StringB/StringA/g: No such file or directory. Здається, -e t PATTERNце недостатньо зрозуміло.
Gyscos

1
@Gyscos -eПеред другою sкомандою було відсутнє . Я виправив свою відповідь.
Жил "ТАК - перестань бути злим"

8

Зараз я роблю щось на кшталт
...............
Проблема такого підходу полягає в тому, що він передбачає, що StringC не виникає у файлі.

Я думаю, що ваш підхід прекрасний, вам слід просто використовувати щось інше, а не рядок, те, що не може виникнути в рядку (у просторі шаблону). Найкращий кандидат - \newline.
Зазвичай жоден рядок вводу в просторі шаблону не буде містити цей символ, щоб замінити всі події THISта THATу файлі можна запустити:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

або, якщо ваш sed підтримує і \nв RHS:

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Це прекрасно. Я трохи плакала. Ще один спосіб зробити нові рядки RHS - це змінні оболонки - чи sedпідтримує певний вхід або не стає набагато менш важливим, якщо заздалегідь підготувати кілька макросів. Як, set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"начебто, дурно, тут, правда, але це має набагато більше сенсу, коли деякі інші часи - особливо для чарівних занять тощо.
mikeserv

Ну як щодо цього, чоловіче. Про це навіть є відповідь. Це було там, коли я зробив коментар? Щойно я побачив, як річ вискочила у нещодавно відредагованому списку (можливо), і верхній рядок верхньої відповіді був трохи відключений (якщо ви, мабуть, дбаєте про невбудований Linux, я думаю) . Я вважаю за краще там пропозицію Гілла - якщо ви не займаєтеся довгим бігом sed, постійні вилки на eголову - це кіндува кошмар. Інакше - я pasteцілий день граю . Я зробив варіант аналізатора - як би column. Це просто gens тире для введення рядків і рядків разом.
mikeserv

3

Я думаю, що цілком справедливо використовувати рядок "nonce" для заміни двох слів. Якщо ви хочете більш загальне рішення, ви можете зробити щось на кшталт:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Це дає

say me say you

Зауважте, що вам потрібні дві додаткові заміни тут, щоб уникнути заміни, x_xякщо у вас є рядки "x_x". Але навіть це все ще здається більш простим, ніж awkрішення для мене.


Мабуть, це те, що Аскер сказав, що вони вже роблять.
roaima

1
Так, я спершу помітив, що спочатку (див. Історію редагування), але моє рішення є іншим, оскільки воно працює навіть тоді, коли рядок заміни (тут "x_x") відбувається в початковій рядку, отже, більш загальний.
Девід Онгаро

Розумний, але є улов. Якщо StringA або StringB містять _, потрібно скорегувати _себе (вибрати інший символ) або проблемний рядок (виконати s/_/__/gна ньому заздалегідь, здається, краще). Таке рішення, яке воно є, не можна сліпо застосовувати для заміни довільних рядків.
Каміль Маціоровський

@KamilMaciorowski Я не розумію, що ти маєш на увазі? Я фактично подаю заявку s/_/__/gзаздалегідь. Можливо, просто покажіть тестовий зразок, який не вдається.
Девід Онгаро

@KamilMaciorowski ах, я думаю, зараз розумію. Ви маєте на увазі, якщо самі рядки заміни містять a _, так скажімо, замінюючи y_ouна me. Так, це правда, треба знати про це і вносити y__ouїх у вираз. Сценарій, який бере заміну як вхідні параметри, також повинен враховувати це.
Девід Онгаро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.