【问题标题】:How to replace all the blanks within square brackets with an underscore using sed?如何使用 sed 用下划线替换方括号内的所有空格?
【发布时间】:2010-12-21 20:21:59
【问题描述】:

我发现为了将 [some name] 转换为 [some_name],我需要使用以下表达式:

s/\(\[[^ ]*\) /\1_/

即为任何以文字“[”开头的内容创建反向引用捕获,该文字包含任意数量的非空格字符,后跟一个空格,以替换为非空格字符后跟一个下划线。我还不知道如何改变这个表达式,以便它适用于大括号内的所有下划线,例如[几句话] 变成 [a_few_words]。

我感觉我已经很接近了,但我只是错过了一大块知识,这些知识将解锁使这个东西在一行中包含的第一组 [] 的限制内无限次工作的关键(在这种情况下是 SQL Server DDL)。

任何建议都非常感谢......

【问题讨论】:

    标签: regex sed backreference


    【解决方案1】:

    需要的诡计有两个部分:

    1. 当你到达一个右方括号时停止替换(但在行上重复做):

      s/\(\[[^] ]*\) /\1_/g
      

      这匹配一个左方括号,后跟零个或多个既不是空白也不是右方括号的字符。全局后缀意味着该模式适用于所有以开方括号开头的序列,最后是行上的空白或右方括号。另请注意,此正则表达式不会更改“[single-word] and context”,而原始表达式会将其转换为“[single-word]_and context”,这不是练习的对象。

    2. 获取 sed 以从该搜索开始的位置重复搜索。不幸的是,没有真正好的方法来做到这一点。 Sed 总是在被替换的文本之后继续搜索;这是我们不想要的一个场合。有时,您只需重复替换操作即可逃脱。在这种情况下,您必须在每次替换成功时重复一次,当没有更多替换时停止。

    sed 中两个鲜为人知的操作是“:label”和“t”命令。不过,它们出现在 Unix 的第 7 版(大约 1978 年)中,因此它们不是新功能。第一个简单地标识脚本中可以使用“b”(此处不需要)或“t”跳转到的位置:

    [2addr]t [label]
    

    如果在最近读取输入行或执行“t”函数后进行了任何替换,则分支到带有标签的“:”函数。如果没有指定标签,则跳转到脚本的末尾。

    太棒了:我们需要:

     sed -e ':redo; s/\(\[[^] ]*\) /\1_/g; t redo' data.file
    

    除了 - 它不能像那样在一行上全部工作(至少在 MacOS X 上不行)。不过,这确实奏效了:

    sed -e ':redo
            s/\(\[[^] ]*\) /\1_/g
            t redo' data.file
    

    或者,如 cmets 中所述,您可以编写三个单独的“-e”选项(适用于 MacOS X):

     sed -e ':redo' -e 's/\(\[[^] ]*\) /\1_/g' -e 't redo' data.file
    

    给定数据文件:

    a line with [one blank] word inside square brackets.
    a line with [two blank] or [three blank] words inside square brackets.
    a line with [no-blank] word inside square brackets.
    a line with [multiple words in a single bracket] inside square brackets.
    a line with [multiple words in a single bracket] [several times on one line]
    

    显示的 sed 脚本的输出是:

    a line with [one_blank] word inside square brackets.
    a line with [two_blank] or [three_blank] words inside square brackets.
    a line with [no-blank] word inside square brackets.
    a line with [multiple_words_in_a_single_bracket] inside square brackets.
    a line with [multiple_words_in_a_single_bracket] [several_times_on_one_line]
    

    最后,阅读问题中的细则,如果您只需要在每行的第一个方括号字段中完成此操作,那么我们需要确保在开始匹配的那个之前没有开放方括号.此变体有效:

    sed -e ':redo' -e 's/^\([^]]*\[[^] ]*\) /\1_/' -e 't redo' data.file
    

    ('g' 限定符已消失 - 考虑到循环,其他变体中可能不需要它;它的存在可能会使过程稍微更有效率,但很可能基本上不可能检测到这一点。模式现在锚定到行首(插入符号),并且在第一个开放方括号之前包含零个或多个不是开放方括号的字符。)

    样本输出:

    a line with [two_blank] or [three blank] words inside square brackets.
    a line with [no-blank] word inside square brackets.
    a line with [multiple_words_in_a_single_bracket] inside square brackets.
    a line with [multiple_words_in_a_single_bracket] [several times on one line]
    

    【讨论】:

    • 啊,巧妙地观察到[a b c] 在第一个 sub 之后变为[a_b c],因此可以递归处理。我正在考虑将[stuff] 拆分为单独的行,ying 这些行然后重新加入。
    • +1。它是否可以在 OS X 上执行此操作 sed -e ':redo' -e 's/\(\[[^] ]*\) /\1_/g' -e 't redo' 而不是将其拆分为多行?
    • @Dennis:是的(将 sed 脚本分成 3 个单独的 '-e' 选项有效)- 关键似乎是分号以某种方式搞砸了。我不确定这是否是 MacOS X sed 中的错误,还是更普遍的标签和分支问题。
    • sed 的某些版本只是不使用分号。当您尝试像echo hi | sed -e 's/h/H/;s/i/I/' 这样简单的操作时,您会收到什么错误消息?它会让你遗漏第一个(唯一的)-e吗?
    • @Dennis:无论是否使用“-e”,生成 HI 都很好。我只注意到分支有问题,而且我在 sed 脚本中使用分支的次数并不多,以至于之前遇到过这个问题。我没有收到错误消息 - 它根本没有做任何替换(好像整个命令被视为单个标签操作)。
    【解决方案2】:

    这在像 perl 这样具有“可执行”替换的语言中更容易:

    perl -wne 's/(\[.*?])/ do { my $x = $1; $x =~ y, ,_,; $x } /ge; print'
    

    或者更清楚地拆分它:

    sub replace_with_underscores {
        my $s = shift;
        $s =~ y/ /_/;
        $s
    }
    s/(\[.*?])/ replace_with_underscores($1) /ge;
    

    .*? 是非贪婪匹配(以避免将两个相邻的括号短语混淆在一起),而替换的e 标志会导致对其进行评估,因此您可以调用一个函数来完成内部工作。

    【讨论】:

    • 为什么要使用下划线替换名称“replace_with_slashes”?
    • 显然我的意思是使用斜线来替换与下划线。 ;-)
    • Ben,虽然我很欣赏您的解决方案的简洁和艺术优雅 - 不幸的是,我不得不让 Jonathon 获胜,因为他满足了我的每一项要求(包括使用 sed 来完成它)。无论如何,感谢您使线程更有趣和更普遍有用。
    猜你喜欢
    • 1970-01-01
    • 2018-06-11
    • 2023-03-23
    • 2014-11-27
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 2021-01-10
    • 2011-07-12
    相关资源
    最近更新 更多