如何删除文件中除第一个匹配行之外的重复行

问题描述 投票:0回答:2

在以下配置文件中

/etc/fine-tune.conf

我们有重复的行

clean_history_in_os=true

我们要删除所有包含 clean_history_in_os=true 的行 除了文件中的第一个匹配行

到目前为止我所做的是

  sed  -i '/clean_history_in_os=true/d' /etc/fine-tune.conf

但问题是 sed 删除所有“clean_history_in_os=true”行

我很乐意得到解决这个问题的想法,

bash shell perl sed
2个回答
3
投票

使用 Perl

perl -i -ne'next if /clean_history_in_os=true/ && ++$ok > 1; print' file

这会在该行上增加计数器,如果

> 1
它会跳过该行,否则打印


问题是如果我们将模式作为 shell 变量,如何将模式传递给 Perl。下面我假设 shell 变量

$VAR
包含字符串
clean_history...

在所有这些中,shell 变量的值直接用作正则表达式中的模式。如果它是问题中的文字字符串,那么下面的代码将按给定的方式进行。但是,如果可能存在特殊字符,则应将其转义;因此,在正则表达式中使用时,您可能需要在模式前面加上

\Q
。作为一般说明,应注意不要使用 shell 中的输入来运行代码(例如在
/e
下)。

  • 将其作为参数传递,然后可以在 @ARGV

    中使用
    perl -i -ne'
        BEGIN { $qr=shift; }; 
        next if /$qr/ && ++$ok > 1; print
    ' "$VAR" file
    

    其中

    BEGIN
    在运行时之前的
    BEGIN
    阶段运行(因此不适用于以下迭代)。其中,shift
    @ARGV
    中删除第一个元素,在上面的调用中,它是
    $VAR
    中的值,首先由shell插值。然后文件名
    file
    保留在
    @ARGV
    中,因此可以在
    -n
    下进行处理(文件被打开并且其行被迭代)

  • 使用

    -s
    开关,它可以为程序启用命令行开关

    perl -i -s -ne'next if /$qr/ && ++$ok > 1; print' -- -qr="$VAR" file
    

    --
    (在
    ''
    下的一行程序之后)标记程序参数的开始;然后
    -qr
    将变量
    $qr
    引入到程序中,并按上述方式为其分配值(仅使用
    -qr
    ,变量
    $qr
    就会获得值
    1
    ,因此也是一个标志)。

    任何此类选项都必须位于可能的文件名之前,并且它们将从

    @ARGV
    中删除,以便程序可以正常处理提交的文件。

  • 导出 bash 变量,使其成为环境变量,然后可以通过

    %ENV
    hash

    在 Perl 程序中访问该环境变量
    export VAR="clean_history..."
    perl -i -ne'next if /$ENV{VAR}/ && ++$ok > 1; print' file
    

    或者,如果

    $VAR
    仅在此命令中使用,可以使用较短的(必须全部在一行上)

    VAR="clean_history..."  perl -i -ne'...' file
    

    我宁愿推荐前两个选项中的任何一个,而不是这个。

这些是将输入传递到完全在命令行(one-liner)上输入的 Perl 程序的方法,无需

STDIN
或文件。使用脚本更好地使用库,首先Getopt::Long


评论中给出的问题的细化指定,如果短语

clean_...
#
开头,则应完全跳过该行。最简单的是单独测试

next if /#$qr/; next if /$qr/ && ++$ok > 1; print

或者,依靠短路

next if /#$qr/ || (/$qr/ && ++$ok > 1); print

第一个版本不太容易出错,而且可能更清晰。


2
投票

您可以使用此

awk
删除除第一行之外的所有匹配行:

awk '!(/clean_history_in_os=true/ && n++)' file

要将文件保存到位,您可以使用此

gnu awk
命令:

awk -i inplace '!(/clean_history_in_os=true/ && n++)' file

否则使用临时文件:

awk '!(/clean_history_in_os=true/ && n++)' file > $$.tmp && mv $$.tmp file

这里有一个

sed
解决方案可以实现同样的效果:

sed -i -n '0,/clean_history_in_os=true/p;/clean_history_in_os=true/!p' file
© www.soinside.com 2019 - 2024. All rights reserved.