使用ex-command检查两行是否相同?


9

我一直在看这个问题,然后想知道如何使用纯 POSIX 实现我的答案 sedex

诀窍是,虽然sed我可以比较保持空间和模式空间以查看它们是否完全相等(用 G;/^\(.*\)\n\1$/{do something}),但是我不知道在中进行这种测试的方法ex

我知道在Vim中,我可以Y使第一行变短,然后键入 :2,$g/<C-r>0/d几乎执行我指定的操作-但是如果第一行包含除非常简单的字母数字文本之外的任何内容,这的确会带来麻烦,因为该行已作为正则表达式转储了进来。 ,而不仅仅是用于比较的字符串。(如果第一行包含正斜杠,则该行的其余部分将被解释为命令!)

因此,如果我想删除myfile与第一行相同的所有行,但又不想删除第一行,该如何使用ex?为此,我该如何使用vi

如果某行与另一行完全匹配,是否有POSIX方法删除该行?

可能是这样的虚构语法:

:2,$g/**lines equal to "0**/d

3
您可以构建命令,但是它需要一点vimscript,并且可能不是POSIX方式::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw

1
@saginaw,谢谢。到目前为止,已发生到我的唯一POSIX的做法是只使用sed一个过滤器,从内ex,并运行我的整个sed上整个缓冲区...哪个答案工作,当然,(而实际上是便携式不同sed -i)。
通配符

您说得对,我发现您的初始方法<C-r>0很好。我不确定仅使用Ex命令会更好,因为您必须保护特殊字符。如果没有POSIX兼容约束,我想您将使用非常不可思议的开关\V,然后使用第二个参数是一个包含您要转义/保护的所有字符的字符串\Vescape()函数来保护反斜杠(因为即使使用,它也会保留其特殊含义)。。
萨吉诺

但是,在上一个命令中,我也忘记了保护正斜杠,因为它对于全局命令也具有特殊含义,它是模式定界符。因此,正确的命令可能类似于::execute '2,$g/\V' . escape(getline(1), '\/') . '/d'或者您可以将另一个字符用作分号,例如分号。在这种情况下,您无需在模式中保护正斜杠。它会给出以下内容::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
saginaw '16

1
我发现您的第二种方法sed也很好。使用Vim,您经常将某些特殊任务委托给其他程序,sed这可能是一个很好的例子。顺便说一下,您不必sed在整个缓冲区上运行。如果只想在缓冲区的一部分上运行它,则可以指定一个范围。例如,如果您只想过滤50到100之间的行,则可以输入::50,100!<your sed command>
萨吉诺

Answers:


3

Vim

在Vim中,您可以使用匹配包括换行符在内的任何字符\_.。您可以使用它来构造一个模式,该模式匹配整行,任意数量的内容,然后匹配同一行:

/\(^.*$\)\_.*\n\1$/

现在,您要删除文件中与第一行匹配的所有行,而不包括第一行。删除与第一行匹配的最后一行的替换是:

:1 s/\(^.*$\)\_.*\zs\n\1$//

您可以使用:global确保替换重复足够多次以删除所有行:

:g/^/ 1s/\(^.*$\)\_.*\zs\n\1$//

POSIX前

@saginaw在Vim中对您的问题的注释中显示了一种更巧妙的方法来执行此操作,但是我们可以将上述技术应用于POSIX ex。

若要以兼容POSIX的方式执行此操作,必须禁止多行匹配,但仍可以使用反向引用。这需要一些额外的工作:

:g/^/ t- | s/^/@@@/ | 1t- | s/^/"/ | j! | s/^"\(.*\)@@@\1$/d/ | d x | @x

细目如下:

:g/^/                   for each line

t- |                    copy it above

s/^/@@@/ |              prefix it with something unique (@@@)
                        (do a search in the buffer first to make
                        sure it really is unique)

1t- |                   copy the first line above this one

s/^/"/ |                prefix with "

j! |                    join those two lines (no spaces)

s/^"\(.*\)@@@\1$/d/ |   if the part after the " and before the @@@
                        matches the part after the @@@, replace the line
                        with d

d x |                   delete the line into register x

@x                      execute it

因此,如果当前行与第1行重复,则寄存器x将包含d。执行它会删除当前行。如果不是重复项,则它将包含无意义的前缀",在执行时,此前缀将是空操作,因为它会" 开始注释。我不知道这是否是最简洁的方法,这只是我想到的第一个方法!

由于复制过程会暂时更改第1行,因此正好无法删除第一行。如果不是这种情况,则可以:g使用2,$范围作为前缀。

在Vim和ex-vi 4.0版中进行了测试。

编辑

还有一种更简单的方法,即转义特殊字符以创建搜索模式(带有'nomagic'set):global,然后构建命令,然后执行该命令:

:set nomagic
:1t1 | .g/^/ s#\[$^\/]#\\\&#g | s#\.\*#2,$g/^\&$/d# | d x
:@x
:set magic

但是,您不能以单线方式执行此操作,因为您有一个nested :global,这是不允许的。


2

看来,唯一的POSIX方法是使用外部过滤器,例如sed

例如,要仅在文件的第17行与第5行完全相同的情况下删除该文件的第17行,否则将其保留不变,则可以执行以下操作:

:1,17!sed '5h;17{G;/^\(.*\)\n\1$/d;s/\n.*$//;}'

(您可以sed在此处在整个缓冲区上运行,也可以仅在5-17行上运行它,但是在第一种情况下,您进行了不必要的过滤-没什么大不了的-在后一种情况下,您必须使用sed命令中的数字1和13,而不是5和17。)

由于sed仅执行一次前进,所以没有简单的方法来进行反向操作,并且仅当第5行与第17行相同时才删除它。出于好奇,我尝试了一段时间……这很棘手


突破 -您可以这样做:

:17t 5
:5,5+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

这实际上是更通用的方法。同样可以使用它来提供与第一个命令相同的结果(并且仅在与第5行相同的情况下才删除第17行),如下所示:

:5t 17
:17,17+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

对于更广泛的用途,例如删除文件的所有与第37行相同的行,而保留第37行不变,则可以执行以下操作:

:37,$!sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//'
:37t 0
:1,37!sed '1{h;d;};G;/^\(.*\)\n\1$/d;s/\n.*$//'

这里的结论是,以检查是否两条线是相同的,最好的工具 sed不是ex。但是正如DevSolar在评论中提到的那样,这并非不是- viex-它们旨在与Unix工具一起使用;这是一大优势。


多,更难的是:将在文件的最后一行,只有在订单已经不存在什么地方的文件中。
通配符

使用与我的答案相似的方法应该可行。我不认为这是单线的!
安东尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.