【问题标题】:Regular Expression in Linux command sedLinux 命令 sed 中的正则表达式
【发布时间】:2012-02-22 03:45:16
【问题描述】:

我有一个 shell 变量:

all_apk_file="a 1 2.apk x.apk y m.apk"

我想用TEST替换a 1 2.apk,使用命令:

echo $all_apk_file | sed 's/(.*apk ){1}/TEST/g'

.*apk 表示以apk 结尾,{1} 表示只匹配一次,但不起作用;我只得到了原始变量作为输出:a 1 2.apk x.apk y m.apk

谁能告诉我为什么?

【问题讨论】:

  • 简短的回答是 sed 是贪婪的。由于您输入了.*,它会将前两个“apk”组合作为.* 的一部分,因此仅将最后一个apk 识别为最终组合。

标签: regex linux shell sed


【解决方案1】:

首先,要启用你熟悉的sed中的正则表达式,你需要使用-r开关(sed -r ... ):

echo $all_apk_file | sed -r 's/(.*apk ){1}/TEST/g'
# returns TESTy m.apk

看看它返回了什么:TESTy m.apk。这是因为.*贪婪,所以它尽可能匹配。也就是说,.*a 1 2.apk x 匹配,并且您说过要替换 .*apk,将 a 1 2.apk x.apk 替换为“TEST”,从而得到 TESTy m.apk(注意“.apk”后面的空格在您的正则表达式中,这就是为什么匹配不会一直延伸到最后一个“.apk”,它后面没有空格)。

通常可以将 .* 更改为 .*? 以使其非贪婪,但 sed 不支持此行为。

因此,要修复它,您只需使您的正则表达式更具限制性。

很难说出您想要做什么 - 删除前三个单词,其中第三个单词以“.apk”结尾并替换为“TEST”?在这种情况下,可以使用正则表达式:

[a-z0-9]+ +[a-z0-9]+ +[a-z0-9]+\.apk

结合“i”开关(不区分大小写)。

您必须提供您的逻辑来决定删除什么(前三个单词、直到第一个“.apk”单词的任意数量的单词等),以便我们进一步帮助您使用正则表达式。

其次,您已在正则表达式中添加了“g”开关。这意味着 所有 匹配模式将被替换,而您似乎只想替换第一个。所以去掉'g'开关。

最后,所有这些结合起来:

echo $all_apk_file | sed -r 's/[a-z0-9]+ +[a-z0-9]+ +[a-z0-9]+\.apk/TEST/i'
# TEST x.apk y m.apk

【讨论】:

  • 如果可以选择切换到 perl,这可以在 perl 中使用echo $all_apk_file | perl -pe 's/^(.*?\.apk)/TEST/' 完成。
【解决方案2】:

这可能对你有用:

echo "$all_apk_file" | sed 's/apk/\n/;s/.*\n/TEST/'
TEST x.apk y m.apk

至于您的正则表达式为何不起作用,请参阅 @mathematical.coffee 和 @Jonathan Leffler 的出色解释。

s/apk/\n/s/apk/\n/1 的同义词,这意味着将apk 的第一次出现替换为\n。由于 sed 使用 \n 作为记录分隔符,我们知道它不会出现在传递给 sed 命令的任何初始字符串中。有了这两个事实,我们就可以分道扬镳了。

注意如果您想替换到第二个apk,那么s/apk/\n/2 将符合要求。当然,最后一次出现 apk 然后 .*apk 开始发挥作用。

【讨论】:

    【解决方案3】:

    问题的一部分是在常规sed 中,(){} 是模式中的普通字符,直到用反斜杠转义。由于变量的值中没有括号,因此正则表达式永远不会匹配。使用 GNU sed,您还可以使用 -r 标志启用扩展正则表达式。如果你解决了这个问题,你就会遇到.* 是贪婪的问题,而g 修饰符实际上并没有改变任何东西:

    $ echo $all_apk_file | sed 's/\(.*apk \)\{1\}/TEST/g'
    TESTy m.apk
    $ echo $all_apk_file | sed -r 's/(.*apk ){1}/TEST/g'
    TESTy m.apk
    $ echo $all_apk_file | sed -r 's/(.*apk ){1}/TEST/'
    TESTy m.apk
    $
    

    它只是停在那里,因为在变量的回显值中m.apk 之后没有空格。

    现在的问题是:您要替换的是什么?这听起来像是“直到并包括在单词末尾第一次出现apk 的所有内容。这可能最容易通过 Perl 正则表达式中的尾随上下文或非贪婪匹配来完成。如果可以选择切换到 Perl,请这样做。如果不是,则在普通的sed 正则表达式中并非易事。

    $ echo $all_apk_file | sed 's/^[^.]* [^.][^.]*\.apk /TEST /'
    TEST x.apk y m.apk
    $
    

    这会查找其中没有点的任何内容,然后是空格,然后再没有点,然后是.apk;这意味着允许的第一个点是2.apk 中的点。它适用于样本数据;如果变量包含以下内容,它将不起作用:

    all_apk_file="a 1.2 2.apk m.apk y.apk 37"
    

    您需要对其进行调整以满足您的要求。

    【讨论】:

      猜你喜欢
      • 2012-10-29
      • 2013-08-07
      • 1970-01-01
      • 1970-01-01
      • 2020-05-21
      • 2012-06-21
      • 2018-02-27
      相关资源
      最近更新 更多