Чисто поменяйте местами все строки двух строк, используя 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не может появиться в одной и той же строке ввода, вы можете указать sed выполнить замену одним способом, а попробовать только другим, если в первой найденной строке не было вхождений.

<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 не встречается в файле.

Я думаю, что ваш подход хорош, вы должны просто использовать что-то другое вместо строки, то, что не может произойти в строке (в пространстве шаблона). Лучший кандидат - электронная \nлиния.
Как правило, ни одна строка ввода в пространстве шаблона не будет содержать этот символ, поэтому, чтобы поменять местами все вхождения THISи THATв файле, вы можете выполнить:

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

или, если ваш sed также поддерживает \nRHS:

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"- немного глупо здесь, по общему признанию, но это имеет намного больше смысла, когда в некоторые другие времена - особенно для классов char и подобных.
mikeserv

Ну, как насчет этого, чувак. Там даже есть ответ об этом. Это было там, когда я сделал комментарий? Я только что видел, как эта вещь всплыла в недавно отредактированном списке (возможно), и верхняя строка верхнего ответа была немного отклонена (я думаю, если вы заботитесь только о не встроенном Linux) . Я предпочитаю предложение Жиля там - если вы не делаете длительный sedпериод, постоянные развилки с накладными расходами e- это кошмар Киндувы. С другой стороны - я играю с ним pasteцелый день. Я сделал вариант синтаксического анализатора - как columnвид. Это просто штрихи для ввода строк и прочее.
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решение для меня.


Похоже, именно это Аскер сказал, что они уже делают.
Ройма

1
Да, я сначала упустил это из виду (см. Историю редактирования), но мое решение отличается, так как оно работает, даже если строка замены (здесь «x_x») встречается в исходной строке, следовательно, она более общая.
Дэвид Онгаро

Умный, но есть подвох. Если StringA или StringB содержит _, нужно настроить _саму себя (выбрать другой символ) или проблемную строку (выполнить s/_/__/gее заранее, кажется, лучше). Ваше решение не может быть применено вслепую для замены произвольных строк.
Камиль Мачоровский

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

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