使用sed干净地交换两个字符串的所有出现


13

假设我有一个包含StringA和StringB多次出现的文件。我想用StringB替换所有出现的StringA,并(同时)用StringA替换所有出现的StringB。

现在,我正在做类似的事情

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

这种方法的问题在于它假定StringC在文件中没有出现。尽管实际上这不是问题,但是此解决方案仍然让人感到不舒服-也就是说,这就像是学习更多unix魔术的机会。:)

Answers:


11

如果StringBStringA不能出现在同一输入行上,则可以告诉sed以一种方式执行替换,并且如果没有第一个搜索到的字符串出现,则只能尝试另一种方式。

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

在一般情况下,我认为sed中没有简单的方法。顺便说一句,注意,规格是,如果不明确StringAStringB可以重叠。这是一个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'17-10-1

1
@Gyscos -e在第二条s命令之前没有任何内容。我已经解决了。
吉尔斯(Gilles)'所以

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换行符的另一种方法是shell变量- sed如果事先准备好一些宏,是否支持某些转义就变得不那么重要了。像set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"-有点笨这里,不可否认,但它使很多更有意义,当一些其他时间-尤其是对于焦炭类和类似。
mikeserv

那好吗,伙计。甚至还有答案。我发表评论时在那儿吗?我只是在最近编辑过的列表上看到了这个东西(也许),并且最上面的答案的第一行有些偏离(我想,如果您只关心非嵌入式linux)。我喜欢Gilles的建议-除非您长时间运行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我的解决方案简单。


这似乎就是Asker所说的他们已经在做的事情。
roaima

1
是的,起初我忽略了这一点(请参阅编辑历史记录),但是我给定的解决方案有所不同,因为即使替换字符串(此处为“ x_x”)出现在原始字符串中也可以使用,因此它更通用。
David Ongaro '16

聪明,但是有一个陷阱。如果StringA或StringB包含_,则需要调整_自身(选择另一个字符)或麻烦的字符串(s/_/__/g事先对其执行,似乎更好)。实际上,您的解决方案不能盲目地应用于交换任意字符串。
卡米尔Maciorowski

@KamilMaciorowski我不明白你的意思吗?我实际上是s/_/__/g事先申请的。也许只是显示一个失败的测试用例。
David Ongaro

@KamilMaciorowski啊,我想我现在明白了。你的意思是,如果替换字符串本身含有_,所以说更换y_oume。是的,确实是必须要意识到这一点并将其放入y__ou表达式中。将替换作为输入参数的脚本也必须考虑到这一点。
David Ongaro
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.