仅对文件的特定部分执行搜索和替换的最佳方法是什么?

What Is the Best Way to Perform a Search and Replace On only Specific Sections of a File?

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

例如,假设我想用 # Section 1# Section 3 下的 bar 替换 foo 所有 个实例,和 # Section 4 保持 # 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 是否能够检查上下文以允许我正在寻找的内容。

您可以使用这个 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

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

类似

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将更改写回磁盘。

这是一个 sed 版本:

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

您始终可以使用 -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

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

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

如果您想从替换中排除 header:

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

sed

的解决方案

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

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

这个从替换中排除了 headers:

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