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

时间:2014-10-26 01:30:12

标签: replace syntax sed

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

  • ab - > BC
  • bc - > AB

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

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

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

echo abbc | sed SED_COMMAND
bcab

修改: 实际上文本可能有两个以上的模式,我不知道我需要多少替换。由于有一个答案说sed是一个流编辑器,并且它的替换是贪婪的,我认为我需要使用一些脚本语言。

12 个答案:

答案 0 :(得分:256)

也许是这样的:

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

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

答案 1 :(得分:11)

我总是使用多个带有-e的语句

""" A simple example of how to use the MongoDB reader and writer. If you like, you can test it out with these commands (requires Docker and virtualenv for python2): $ virtualenv venv $ source venv/bin/activate $ pip install google-cloud-dataflow pymongo $ # The following line is optional if mongod is running already $ sudo service mongod stop $ docker run -p 27017:27017 --name dataflowtest --rm mongo:3.2 $ docker exec -it dataflowtest mongo > use mydb > db.mycollection.insert({ _id: ObjectId() }) > exit $ python -m simple $ # The following line is optional if mongod was shut down previously $ sudo service mongod start """ from __future__ import absolute_import import logging import apache_beam as beam from apache_beam.options.pipeline_options import PipelineOptions from beam_extended.io.mongodbio import ReadFromMongo, WriteToMongo def transform_doc(document): print(document) return {'_id': str(document['_id'])} def run(argv=None): """Main entry point; defines and runs the aggregation pipeline.""" connection_string = 'mongodb://localhost:27017' # Can also fetch a connection string from a Google Cloud Storage file. # This might be preferable to avoid pickling the mongodb connection string. # E.g. # connection_string = 'gs://my-bucket/mongo_connection_string.txt' # where "mongo_connection_string.txt" contains a single line with the connection string. # with beam.Pipeline(runner='DirectRunner', options=PipelineOptions()) as pipeline: options = PipelineOptions() with beam.Pipeline(options=options) as pipeline: (pipeline | 'read' >> ReadFromMongo(connection_string, 'mydb', 'mycollection', query={}, projection=['_id']) | 'transform' >> beam.Map(transform_doc) | 'save' >> WriteToMongo(connection_string, 'mydb', 'mycollection')) # | 'save' >> beam.io.WriteToText('./simple.txt')) if __name__ == '__main__': # logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.INFO) run()

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

答案 2 :(得分:10)

以下是ooga's answer的变体,适用于多个搜索和替换对,而无需检查值的重复使用方式:

21

以下是一个例子:

之前:

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附加到命令末尾的结果相同,但不需要指定路径两次。

如果您知道文件as jthill suggested中没有显示空值,则可以使用&& sed -i 's/________//g' path_to_your_files/*.txt\x0代替_\x0_

答案 3 :(得分:6)

sed是一个流编辑器。它贪婪地搜索和替换。做你要求的唯一方法是使用中间替换模式并最终将其更改回来。

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

答案 4 :(得分: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)被添加到行的开头,并用作在整个行的长度上沿着搜索进行碰撞的方法。一旦标记到达该行的末尾,该过程就完成并打印出查找表并丢弃标记。

N.B。查找表在最开始时准备好,并选择第二个唯一标记(在本例中为:),以免与替换字符串冲突。

有一些评论:

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

答案 5 :(得分:2)

Tcl对此

builtin
$ 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

答案 6 :(得分:1)

echo "C:\Users\San.Tan\My Folder\project1" | sed -e 's/C:\\/mnt\/c\//;s/\\/\//g'

替换

C:\Users\San.Tan\My Folder\project1

mnt/c/Users/San.Tan/My Folder/project1

以防有人需要将Windows路径的Windows路径替换为Linux(WSL)路径

答案 7 :(得分:0)

以下是基于oogas awk

sed
echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab

答案 8 :(得分:0)

对于出现单一模式,这可能是一种更简单的方法,您可以尝试如下操作:   回声“ 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

希望这会有所帮助!

答案 9 :(得分:0)

我相信这应该可以解决您的问题。我可能缺少一些极端情况,如果您注意到其中一个,请发表评论。

您需要一种从将来的模式中排除以前的替换的方法,这实际上意味着使输出可区分,并从搜索中排除这些输出,最后再次使输出变得不可区分。这与引用/转义过程非常相似,因此我将从中进行借鉴。

  • s/\\/\\\\/g会转义所有现有的反斜杠
  • s/ab/\\b\\c/g用原始ab代替转义的bc
  • s/bc/\\a\\b/g用原始bc代替转义的ab
  • s/\\\(.\)/\1/g将所有转义的X替换为原始X

我没有考虑ab或bc中的反斜杠,但直觉上,我将以相同的方式跳过搜索并替换术语-\现在匹配\\,而替换为\\显示为\

直到现在,我一直在使用反斜杠作为转义字符,但这不一定是最佳选择。几乎任何字符都可以使用,但是要谨慎选择需要在环境中转义,sed等的字符,具体取决于您打算如何使用结果。

答案 10 :(得分:0)

如果用变量替换字符串,则解决方案不起作用。 sed 命令需要用双引号代替单引号。

#sed -e "s/#replacevarServiceName#/$varServiceName/g" -e "s/#replacevarImageTag#/$varImageTag/g" deployment.yaml

答案 11 :(得分:0)

到目前为止发布的每个答案似乎都同意kuriouscoder在他的above post中所做的声明:

<块引用>

做您要求的唯一方法是使用中间件 替换模式并最终将其改回

但是,如果您打算这样做,并且您的用法可能涉及的不仅仅是一些简单的字符串(也许您正在过滤数据等),那么与 sed 一起使用的最佳字符是换行符。这是因为由于 sed 是 100% 基于行的,换行符是您在获取新行时保证永远不会收到的唯一字符(忘记 GNU 多行扩展用于本次讨论)。

首先,这里有一个非常简单的方法来解决您的问题,使用换行符作为中间分隔符:

echo "abbc" | sed -E $'s/ab|bc/\\\n&/g; s/\\nab/bc/g; s/\\nbc/ab/g'

简单带来了一些权衡……如果您有多个变量,就像在您的原始帖子中一样,您必须将它们全部输入两次。性能也可能会有所提高。

使用 sed 做更多事情会变得非常讨厌。即使有一些更高级的功能,如分支控制和保持缓冲区(IMO 真的很弱),您的选择也非常有限。

只是为了好玩,我想出了一个替代方案,但我认为我没有任何特别的理由推荐它而不是本文前面的那个......你必须基本上制定自己的“约定” " 用于分隔符,如果您真的想在 sed 中做任何花哨的事情。这对于您的原始帖子来说太过分了,但它可能会为遇到此帖子并遇到更复杂情况的人激发一些想法。

我的约定如下:使用多个换行符来“保护”或“取消保护”您正在处理的行的一部分。换行符表示单词边界。两个换行符表示候选替换的替代。我不会立即替换,而是在下一行列出候选替换。三个换行符意味着一个值被“锁定”,就像你最初的帖子方式试图对 abbc 做的那样。在那之后,进一步的替换将被撤消,因为它们受到换行符的保护。如果我自己不这么说,那就有点复杂了……! sed 的真正意义不只是基础知识。

# Newlines
NL=$'\\\n'
NOT_NL=$'[\x01-\x09\x0B-\x7F]'

# Delimiters
PRE="${NL}${NL}&${NL}"
POST="${NL}${NL}"

# Un-doer (if a request was made to modify a locked-in value)
tidy="s/(\\n\\n\\n${NOT_NL}*)\\n\\n(${NOT_NL}*)\\n(${NOT_NL}*)\\n\\n/\\1\\2/g; "

# Locker-inner (three newlines means "do not touch")
tidy+="s/(\\n\\n)${NOT_NL}*\\n(${NOT_NL}*\\n\\n)/\\1${NL}\\2/g;"

# Finalizer (remove newlines)
final="s/\\n//g"

# Input/Commands
input="abbc"
cmd1="s/(ab)/${PRE}bc${POST}/g"
cmd2="s/(bc)/${PRE}ab${POST}/g"

# Execute
echo ${input} | sed -E "${cmd1}; ${tidy}; ${cmd2}; ${tidy}; ${final}"