【问题标题】:What Is the Best Way to Perform a Search and Replace On only Specific Sections of a File?仅对文件的特定部分执行搜索和替换的最佳方法是什么?
【发布时间】:2021-04-05 21:45:47
【问题描述】:

我有一个 markdown 文件,其中的部分由标题分隔。我只想在特定部分执行搜索和替换;但是,每个部分都有相似的内容,因此全局搜索和替换最终会影响所有部分。因此,我需要以某种方式将搜索和替换限制在文件的某些部分。

例如,假设我想在# Section 1# Section 3# Section 4 下用bar 替换foo所有 个实例,而# Section 2# Section 5 保持不变,如下图

示例输入:

# Section 1

- foo
- foo
- Unimportant Item
- foo
- Unimportant Item

# Section 2

- foo
- Unimportant Item

# Section 3

- foo
- Unimportant Item

# Section 4

- foo
- Unimportant Item
- foo

# Section 5

- foo
- foo

样本输出

# Section 1

- bar
- bar
- Unimportant Item
- bar
- Unimportant Item

# Section 2

- foo
- Unimportant Item

# Section 3

- bar
- Unimportant Item

# Section 4

- bar
- Unimportant Item
- bar

# Section 5

- foo
- foo

如果我不必担心各个部分,则使用全局搜索和替换将是微不足道的

sed -i 's/foo/bar/g' <input_file>

但我不确定sed 是否能够检查上下文以允许我正在寻找的内容。

【问题讨论】:

    标签: bash awk sed substitution


    【解决方案1】:

    这是一个 sed 版本:

    sed -E '/^#[^#]\s*Section\s+[134]\s*$/, // s/foo/bar/' input.md
    

    【讨论】:

    • 用逗号分隔单独的正则表达式有什么作用?
    • 用逗号分隔的两个正则表达式表示sed中的一个地址范围。 sed 忽略该范围之外的任何内容。范围的结尾是一个空行。
    • 我刚刚尝试了 /#/ 作为范围的结尾,这也有效。
    • 我预计您可能需要调整范围内的 re 模式。我会玩它
    • 我已经更新了 sed 范围内的正则表达式。这有帮助吗?我根据您更新的样本对其进行了测试。
    【解决方案2】:

    你可以使用这个awk:

    awk 'p {sub(/foo$/, "bar")} /^#/ {p = / (Section [134])$/} 1' file
    # Section 1
    
    - bar
    - bar
    - Unimportant Item
    - bar
    - Unimportant Item
    
    # Section 2
    
    - foo
    - Unimportant Item
    
    # Section 3
    
    - bar
    - Unimportant Item
    
    # Section 4
    
    - bar
    - Unimportant Item
    - bar
    
    # Section 5
    
    - foo
    - foo
    

    为了使其更具可读性:

    awk 'p {                          # if p==1 and current line # == n
       sub(/foo$/, "bar")             # replace foo with bar
    }
    /^#/ {                            # if line starts with #
       p = / (Section [134])$/        # set p = 1/0 if it matches sections
    } 1' file
    

    【讨论】:

    • @Kalcifer 根据您的评论,我更新了我的答案。请检查
    • 这个答案不起作用。它只在节标题之后的第二行进行替换,而不是在整个节上。
    • 我已经意识到这个答案的一个缺陷:如果一个部分中有多个foo 实例,它将失败。我已经相应地更新了问题。
    • EDIT:我已经删除了所有过时的 cmets。
    • @Kalcifer:我已根据最新编辑的问题修改了答案
    【解决方案3】:

    为了完成,这个 awk 答案将在整个部分中进行替换,包括标题:

    awk '/^#/ { in_section = /Section [1|3|4]/ } in_section { sub(/foo/, "bar") } 1' input.md
    

    如果您想从替换中排除标题:

    awk ' /^#/ { in_section = /Section [1|3|4]/; header_line = NR }
          in_section && (NR > header_line) { sub(/foo/, "bar") } 1' input.md
    

    详情

    awk '/^#/ {                              # if in section header
            in_section = /Section [1|3|4]/;    # determine if section of interest (1/0)
            header_line = NR;                # value of header line to exclude
        }
        in_section && (NR > header_line) {   # if in section of interest and after header line
            sub(/foo/, "bar");               # substitute text
        } 1' input.md                        # 1 is to print all lines
    

    【讨论】:

    • 到目前为止,这是最准确、最普遍的答案!
    【解决方案4】:

    每当您考虑sed -i 时,我通常的建议是改用它的哥哥ed,这与sed 不同,它从一开始就旨在编辑文件(它也是POSIX 标准,与@987654324 不同@,因此更便携。)

    类似

    ed -s input.md <<EOF
    /Section 1/;/Section/s/foo/bar/g
    /Section 3/;/Section/sg
    w
    EOF
    

    翻译:在从包含Section 1 的第一行开始到下一个Section 行结束的块中,将foo 替换为bar。然后在Section 3 块中执行相同的s替换。最后,w将更改写回磁盘。

    【讨论】:

      【解决方案5】:

      您始终可以使用-e 选项向sed 提供多个命令,这样即使节一个接一个地进行替换:

      sed  -e '/# Section 1/,/#/ s/foo/bar/' -e '/# Section 2/,/#/ s/foo/bar/' input.md
      

      多个命令也可以放在一个“sed脚本文件”中:

      # content of script.sed
      /# Section 1/,/#/ s/foo/bar/
      /# Section 2/,/#/ s/foo/bar/
      

      你是这样执行的:

      sed  -f script.sed input.md
      

      【讨论】:

      • 这是一个选项;但是,不幸的是,如果我有数百个部分,它会变得非常乏味。
      • @Kalcifer,仅当您想要更改一个接一个的部分时。您始终可以使用正则表达式作为范围的第一部分。例如,如果您想更改第 1、2 和 3 节,您可以这样做:sed -e '/# Section [13]/,/#/ s/foo/bar/' -e '/# Section 2/,/#/ s/foo/bar/' input.md,但我明白您的意思。它没有那么优雅和统一。我仍在考虑如何使它与一个命令一起工作。如果涉及到我,我会添加另一个答案。
      【解决方案6】:

      sed的解决方案

      密钥在范围内。第一个寻址模式匹配我们希望开始替换的标头,第二个匹配除第一个寻址模式中的标头之外的所有标头。请注意,替换命令包括范围中的第一行和最后一行(即标题)。

      sed -E '/^# Section [134]/, /^# Section [^134]/ s/foo/bar/' input.md
      

      这一项从替换中排除了标题:

      sed -E '/^# Section [134]/, /^# Section [^134]/ { /^#/!s/foo/bar/ }' input.md
      

      【讨论】:

      • 虽然我确实喜欢这个解决方案的优雅,但如果部分没有很好的编号,它就不能作为通用解决方案。
      • @Kalcifer,是的,你是对的。您将不得不为您的部分结尾处理“相反”的正则表达式,它可能不像您的示例中那么简单。但是,如果它很简单,那么效果很好,所以我想我会指出它以防它对某人有所帮助。另外,经过数小时分析@cole-tierney 回复中的“空”正则表达式// 后,我想到了这一点。我现在明白了,为什么它不起作用!
      • RE:“空的”正则表达式,您可能会对this 答案感兴趣。
      猜你喜欢
      • 1970-01-01
      • 2010-11-15
      • 2016-08-21
      • 2010-11-14
      • 2019-01-03
      • 1970-01-01
      • 2021-06-26
      • 2013-11-11
      • 2015-05-14
      相关资源
      最近更新 更多