多行文本处理:编辑 sshd_config

多行文本处理:编辑 sshd_config

我的 sshd_config 文件末尾有多个块,如下所示:

Match User FOO
    ChrootDirectory /srv/www/FOO
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp

我怎样才能在 bash 脚本中删除这个块(而不是其他关于 FOO1、FOO2 等的块)?

答案1

假设所有Match块都位于文件末尾sshd_config
如果您的块由空行分隔,例如:

Match User FOO1
    PasswordAuthentication no

Match User FOO2
    PasswordAuthentication yes

Match User FOO
    ChrootDirectory /srv/www/FOO
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp

然后从行中删除直到Match User FOO(并包括)第一个空行:

sed '/^Match User FOO$/,/^$/d' sshd_config

如果它们没有分开,例如:

Match User BAZ
    PasswordAuthentication yes
Match User FOO
    ChrootDirectory /srv/www/FOO
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp
Match User FOO1
    PasswordAuthentication no
Match User FOO2
    PasswordAuthentication yes

然后删除Match User FOO最多(但不包括)以 开头的第一行Match

sed '/^Match User FOO$/,/^Match.*/{//!d;/^Match User FOO$/d;}' sshd_config

请注意,您必须使用sed -i ...就地编辑文件。检查您的sed手册以了解详细信息/备份选项。

答案2

您可以使用 send 和 awk 工具尝试此脚本:

matchedLine=$(awk '/Match User FOO1/{ print NR; exit }' $1) 


if [ "$matchedLine" = "" ];then
echo "Match not found"
exit 1;
fi

sed -e "$matchedLine,$(awk "BEGIN {print $matchedLine + 5 }";)d;" $1

该脚本将 ssh 配置文件的文件名作为其第一个参数。然后,它在文件中搜索第一次出现的匹配字符串(在本例中为“匹配用户 FOO1”)。如果找到匹配项,它将把行号保存到变量 $matchedLine 中。最后使用 sed 它将删除匹配文本的行和以下 5 行。该脚本不会编辑您的实际文件,它只会显示删除了匹配行的文件内容。通过这种方式,您可以检查脚本是否正常运行,没有任何风险。如果您想编辑实际文件,只需将 sed 命令重定向到您的文件即可。

答案3

sed '/^Match User FOO$/,/^  *F.*-sftp$/c\ 
    The whole of the above line range is \
    replaced with this one block of text.\
    Use an empty "c"hange command or a   \
    "d"elete command to replace it with  \
    nothing at all.' <infile >outfile

顺便说一下,看起来你可以依靠缩进。所以也许你可以这样做:

sed '$!N;/^Match User FOO\n/,/\n[[:upper:]]/!P;D
'    <infile >outfile

它将开始删除当前行,同时观察下一个输入行匹配遇到块,当下一个输入行以大写字母开头时停止删除。

上面的命令可能需要更好的解释。这是我所知道的最简单、最可靠的方法。

工作流程是这样的:

  1. !对于不是最后一个的每个输入行$,还将Next 输入行附加到模式空间。

    • 虽然sed通常知道一次只能工作一行,但这只是其默认行为 -sed的编辑溪流可以按照您的意愿进行定制。
    • 在这种情况下,Next 命令引入了一行展望在另外两个命令的帮助下,它在整个脚本过程中持续存在。
  2. 然后检查当前上下文是否属于由/2/,/address/ 范围指定的行数。

    • /^Match User FOO\n/- 范围开始当匹配的模式已经在整个周期的模式空间中并且否则将被P打印到标准输出时。这由模式空间的头部表示^以及\n模式中尾随的 ewline。该模式代表整个输入行 - 从头到尾 - 但仅一半任意时刻的模式空间。
    • /\n[[:upper:]]/- 这主要是基于我对示例的明显缩进样式和人 sshd_config这表明全部相关命令原语以大写字母开头。
    • \n与模式中的前导 ewline 转义相结合,仅当用ext 拉入的行以 1 开头sed时才结束范围的匹配上下文- 并且N不是与任何空白。
  3. 如果它!不在该范围内,则将模式空间中的P第一个 ewline 字符打印\n到标准输出,但如果在该范围内,则在当前周期中不打印任何内容。

  4. 最后,D删除\n模式空间中第一个出现的 ewline 并结束当前循环。

    • 这非常重要 - Delete 确实如此不是下一个周期开始时拉入新的输入线除非\n当前模式空间中有 0条行。
    • 当有\nelete 是模式空间中的一条ewline,D仅清除直到并包含它的内容,然后再用剩余的内容递归到新的循环中。事实上,它D删除的内容只是Print 在与您的范围不匹配的上下文中写入标准输出的内容。

现在,所有这些都是一种非常技术性的方式来表达我之前所说的内容 - 从匹配范围的开始直到其缩进级别结束,并且在行首找到以大写字母开头的新命令,没有任何内容被打印,而其他所有内容都被打印出来。因此,您的块将以一种非常简单的方式从输出中删除,该方式与缩进相关,并且使您能够仅通过缩进来控制其选择匹配像你已经做的那样阻止。

现在,要完全理解所有这些,您必须理解sed的循环,但如果深入了解l它的工作原理,它也很容易理解:

seq 10 | sed '$!N;/^4\n/,/\n9/!P;l;D'

上面的命令打印有两个不同的原因 - 第一个(适用时)!P范围匹配上下文 - 这就是上面建议的标准输出的内容。

第二个是我们l对工作原理的了解sed- 我告诉它打印每一个循环模式空间当前内容的明确表示。结果如下:

1             #This is Printed before we look at pattern space.
1\n2$         #This is our look - though only the 1st line is printed,
2             #each time one is, pattern space is actually 2 lines
2\n3$         #all of the time - it's our window into future output.
3
3\n4$         #3 prints, though 4 is in pattern space. But when pattern
4\n5$         #space matches ^4\n as it does now, nothing prints.
5\n6$
6\n7$         #And continues not to print...
7\n8$
8\n9$         #until the range ends here - when \n9 matches our future -
9             #now our current pattern space.
9\n10$        
10
10$

这使得控制范围(有些人可能会觉得不直观,因为它们需要特殊的开始和结束上下文以及控制这些的额外匹配)总体上变得更加容易,因为您可以在当前上下文中开始一个范围,并在未来的上下文中结束它。

换句话说,与其说...

/this_line/,/that_line/command-应用命令从 这行直到并包括那行

...您可以更直观地表述它,例如...

N;/this_line\n/,/\nthat_line/command;D-应用命令开始于这行直到那行

它正如您所希望的那样强大 - 因为除了编辑命令之外,没有其他方法可以将\newline 放入模式空间中sed(例如N分机)- 也是您可能希望的性能最高的解决方案之一。

所以我借用don的示例数据:

sed '$!N;/^Match User FOO\n/,/\n[[:upper:]]/!P;D' <<\IN
Match User BAZ
    PasswordAuthentication yes
Match User FOO
    ChrootDirectory /srv/www/FOO
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp
Match User FOO1
    PasswordAuthentication no
Match User FOO2
    PasswordAuthentication yes
IN

...和...

sed '$!N;/^Match User FOO\n/,/\n[[:upper:]]/!P;D' <<\IN
Match User FOO1
    PasswordAuthentication no

Match User FOO2
    PasswordAuthentication yes

Match User FOO
    ChrootDirectory /srv/www/FOO
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp

Match User FOO2
    PasswordAuthentication yes

IN

...将打印...

Match User BAZ
    PasswordAuthentication yes
Match User FOO1
    PasswordAuthentication no
Match User FOO2
    PasswordAuthentication yes

...和...

Match User FOO1
    PasswordAuthentication no

Match User FOO2
    PasswordAuthentication yes

Match User FOO2
    PasswordAuthentication yes

...分别。

不过,它不会在空行上退出范围上下文 - 该块后面的所有空行都被视为所写入的块的一部分。因此,第二个示例中输出行之间的空白是其前面的空白。它可以,比如:

sed '$!N;/^Match User FOO\n/,/\n\([[:upper:]].*\)*$/!P;D' <infile >oufile

...这将破坏范围上下文并开始再次写入以大写字符开头的空白行的输出。它还可以变得更普遍:

sed '$!N;/^Match User FOO\n/,/\n\([^[:blank:]].*\)*$/!P;D' <infile >oufile

...这将打破任何空白行或以任何非空白字符打开的行的范围。在所有情况下,突破模式都是您希望重新开始的模式,而不是您希望其保持停止的一系列模式中的最后一个模式。

答案4

尽管sed 是一种可能性,在 awk 中更容易使其稳健。以下脚本跳过从Match User FOO(或大小写和空格的任何变化)开始的行,并在任何其他Match User行处停止跳过,并打印所有未跳过的行。

awk '
    tolower($1)=="match" && tolower($2) == "user" {skip = $3 == "foo"}
    !skip
'

如果您的脚本被中断,请确保将输出写入临时文件,然后将其移动到位。请注意,如果其他人同时修改该文件,这仍然可能导致数据丢失。

awk … </etc/sshd_config >/etc/sshd_config.tmp &&
mv /etc/sshd_config.tmp /etc/sshd_config

相关内容