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

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

假设我有一个文件包含多次出现的 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 魔法的机会。 :)

答案1

如果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
}'

答案2

现在,我正在做类似的事情
......
这种方法的问题是它假设文件中没有出现 StringC 。

我认为你的方法很好,你应该使用其他东西而不是字符串,不能出现在一行中(在模式空间中)的东西。最好的候选者是\neline。
通常,模式空间中的输入行不会包含该字符,因此,要交换文件中所有出现的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

答案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对我来说似乎仍然比解决方案更简单。

答案4

我知道这是很久以前的事了,但在某些情况下你可能会使用:

echo foobar | sed -e 's/bar/foo/' -e 's/foo/bar/'

这是有效的,因为它替换了第一个出现的第一个bar,然后替换了第一个出现的foo,而第二个则保持foo不变。这假设知道它们发生的顺序,并且只发生一次。

这是一个更加不可知的版本:

echo foobar | sed 's/foo/tmp/g' | sed -e 's/bar/foo/g' -e 's/tmp/bar/g'
$ echo barbarfoobar | sed 's/foo/tmp/g' | sed -e 's/bar/foo/g' -e 's/tmp/bar/g'
foofoobarfoo

相关内容