如何用sed一次替换多个模式?


231

假设我有'abbc'字符串,我想替换:

  • ab-> bc
  • 公元前-> ab

如果我尝试两次替换,结果不是我想要的:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

那么我可以使用什么sed命令来替换如下所示?

echo abbc | sed SED_COMMAND
bcab

编辑:实际上,文本可能有2种以上的模式,我不知道我需要多少替换。既然有一个回答说sed是一个流编辑器,并且它的替换很贪心,所以我认为我需要为此使用某种脚本语言。


您是否需要在同一条生产线上进行多次更换?如果不是,只需g从这两个s///命令中删除该标志即可。
伊斯坦·赖斯纳

你错过了我的问题。我的意思是您需要在同一条线上多次进行每个替换。是否有一个以上的匹配ab bc在原来的输入。
伊坦·赖斯纳

抱歉@EtanReisner我误会了,答案是肯定的。文本可以有多个替换。
DaniloNC

Answers:


342

也许是这样的:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

~您知道不会出现在字符串中的字符替换。


9
GNU sed的手柄完全无效,这样你就可以使用\x0~~
jthill 2014年

3
g必要的,它做什么?

12
@Lee g是全局的-它替换每一行中模式的所有实例,而不仅仅是第一行(这是默认行为)。
naught101 '09

1
请参阅我的答案stackoverflow.com/a/41273117/539149,了解可以同时替换多个组合的ooga答案的变化形式。
Zack Morris

3
您知道不会出现在字符串中对于生产代码,请不要对输入进行任何假设。对于测试来说,测试永远无法真正证明其正确性,但是,进行测试的一个好主意是:使用脚本本身作为输入。
hagello

33

我总是将多个语句与“ -e”一起使用

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

这将在所有AND,GROUP BY,UNION和FROM之前附加一个'\ n',而'&'表示匹配的字符串,而'\ n&'表示您要在'matched之前将匹配的字符串替换为'\ n' '


14

这是ooga答案的一种变体,可用于多个搜索和替换对,而不必检查如何重用值:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

这是一个例子:

之前:

some text AB some more text "BC" and more text.

后:

some text BC some more text "CD" and more text.

请注意,这\b表示单词边界,这是防止单词________干扰的原因(我在Ubuntu上使用GNU sed 4.2.2)。如果您没有使用单词边界搜索,则此技术可能不起作用。

还要注意,这与删除s/________//g和追加&& sed -i 's/________//g' path_to_your_files/*.txt到命令末尾具有相同的结果,但不需要两次指定路径。

如jthill建议的那样,对此的一般变型是使用\x0_\x0_代替________如果您知道文件中没有空值。


我同意hagello上面关于不对输入可能包含的内容进行假设的评论。因此,我个人认为这是最可靠的解决方案,除了将sed彼此sed 's/ab/xy/' | sed 's/cd/ab/' .....
叠置

12

sed是流编辑器。它搜索并替换贪婪。完成您所要求的唯一方法是使用中间替换模式并将其最终更改回去。

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

这可能对您有用(GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

这将使用一个查找表,该表已准备好并保存在保留空间(HS)中,然后附加到每一行。唯一的标记(在这种情况下为\n)位于行的开头,并用作在整个行的长度上沿搜索范围增加的方法。一旦标记到达该行的末尾,该过程就完成并打印出查找表,并且标记被丢弃。

注意:查找表从一开始就准备好了,并选择了第二个唯一标记(在这种情况下为:),以免与替换字符串冲突。

有一些评论:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

该表的工作方式如下:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

对于单个模式出现,这可能是一种更简单的方法,您可以尝试如下操作:echo'abbc'| sed's / ab / bc /; s / bc / ab / 2'

我的输出:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

对于多次出现的模式:

sed 's/\(ab\)\(bc\)/\2\1/g'

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

希望这可以帮助 !!


2

Tcl有一个内置

$ tclsh
% string map {ab bc bc ab} abbc
bcab

这是通过一次在字符串中遍历一个字符来进行的,以从当前位置开始进行字符串比较。

在perl中:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

0

这是awk基于oogas的sed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
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.