Perl正则表达式匹配和删除

5

我有一个以//#...开头的字符串,直到换行符结束。我已经找出了匹配它的正则表达式,就是..#([^\n]*)

我的问题是,如果满足以下条件,如何从文件中删除此行:

9个回答

29

你的正则表达式有几个问题:

  1. 你使用了..来匹配两个斜杠,而不是具体匹配两个斜杠,可能是因为你不知道在使用斜杠作为分隔符时如何匹配斜杠。(实际上,点匹配几乎所有字符,如#3中所述。)

    在斜杠分隔的正则表达式文字中//,你可以通过用反斜杠保护它们来匹配斜杠,例如/\/\//。然而,更好的变体是使用更长的正则表达式文字,m//,其中可以选择分隔符,例如m!!。由于你使用的不是斜杠作为分隔符,因此可以直接写它们而不需要转义它们:m!//!。请参见 perldoc perlop

  2. 它没有锚定到字符串的开头,因此它将匹配任何地方。在前面使用^开头的断言。

  3. 你写了[^\n]来匹配“除换行符外的任何字符”,当有一种更简单的方法可以编写它,即使用点通配符.。它确实做到了这一点-匹配除换行符外的任何字符。

  4. 你正在使用括号来分组匹配的一部分,但该组既没有量化(您没有指定它可以匹配任意次数多于一次)也没有兴趣保留它。因此,括号是多余的。

总而言之,这使得它成为m!^//#.*!。但是,在正则表达式末尾放置一个未捕获的.* (或任何具有*量词的内容)毫无意义,因为它永远不会改变字符串是否匹配: *很高兴什么都不匹配。

所以,这让你只剩下m!^//#!

至于从文件中删除该行,如其他人所解释的那样,请逐行读取它并将您要保留的所有行打印回另一个文件。如果您不是在较大的程序内执行此操作,请使用perl的命令行开关轻松完成:

perl -ni.bak -e'print unless m!^//#!' somefile.txt

这里,-n 开关使perl在您提供的代码周围放置一个循环,该循环将按顺序读取您在命令行中传递的所有文件。-i 开关(用于“原地”)表示要从脚本收集输出并用其覆盖每个文件的原始内容。 .bak 参数是-i选项告诉perl保留原始文件的备份,备份文件名为原始文件名加上 .bak。有关所有这些内容,请参见perldoc perlrun

如果您想在更大的程序上下文中执行此操作,则最安全的方法是将文件打开两次,一次用于读取,另一次使用IO::AtomicFile单独用于写入。只有在成功关闭时,IO::AtomicFile才会替换原始文件。


5

要过滤掉文件中与特定正则表达式匹配的所有行:

perl -n -i.orig -e 'print unless /^#/' file1 file2 file3

在-i开关后面加上'.orig'可以创建一个带有给定扩展名(.orig)的文件备份。如果您不需要备份,可以跳过它(只需使用-i)。

-n开关会导致perl对文件中的每一行执行您的指令(-e '...')。该行存储在$_中(这也是许多指令的默认参数,在本例中是print和regex匹配)。

最后,-e开关的参数表示“打印该行,除非该行以#字符开头。

PS. 还有一个-p开关,其行为类似于-n,但始终打印行(适用于搜索和替换)。


2

正如其他人所指出的,如果最终目标仅是删除以//#开头的行,出于性能原因,您可能最好使用grepsed

grep -v '^\/\/#' filename.txt > filename.stripped.txt

sed '/^\/\/#/d' filename.txt > filename.stripped.txt

或者

sed -i '/^\/\/#/d' filename.txt

如果您喜欢就地编辑。

请注意,在Perl中,您的正则表达式应该是

m{^//#}

这个正则表达式匹配以两个斜杠跟随一个#开头的字符串。

请注意,使用匹配运算符m{pattern}而不是更常见的/pattern/可以避免“反斜杠病”。尽早训练自己使用这种语法,因为这是一种简单的避免过度转义的方法。你可以写m{^//#},效果与m%^//#%m#^//\##相同,具体取决于你想匹配什么。力求清晰 - 正则表达式已经很难理解了,不要让避免不必要的反斜杠影响可读性。说真的,m/^\/\/#/看起来像一只牙齿破碎和镶牙的鳄鱼或一个小的ASCII画的阿尔卑斯山。

你的脚本可能会遇到的一个问题是,如果整个文件都被读入一个字符串中,包括换行符等。为了防止这种情况发生,可以在正则表达式上使用/m(多行)修饰符:

m{^//#}m

这允许 ^ 在字符串开头和换行符后匹配。你可能认为有一种方法可以使用正则表达式修饰符 /g、/m 和 /s 剥离或匹配与 m{^\/\/#.*$} 匹配的行,但在将文件读入字符串而又不想复制它的情况下(这引出了首先将文件读入字符串的原因),这并不是一个好方法。理论上是可行的,但现在已经很晚了,我没有看到答案。然而,一种“简单”的方法是:

my $cooked = join qq{\n}, (grep { ! m{^//} } (split m{\n}, $raw));

即使这样做会创建一个副本而不是在原始字符串$raw上进行原地编辑。

1

你真的不需要使用 Perl 来完成这个。

sed '/^\/\/#/d' inputfile > outputfile

我喜欢sed。


0

逐行读取文件,并将不匹配正则表达式的行写入新文件,不能仅仅删除一行。


0

它是从行首开始还是可以出现在任何地方?如果是前者,s/old/new 就是你想要的。如果是后者,我就得想一下了。我猜反向引用可能可以以某种方式使用。


0

我认为你的正则表达式不正确。

首先,你需要以^开头,否则它会在行中的任何位置匹配此模式。

其次,.. 应该是 \/\/,否则它将匹配任意两个字符。

^\/\/#[^\n]* 可能是你想要的。

然后按照EricSchaefer所说,逐行读取文件,只写入不匹配的行。

--
bmb


0
尝试以下内容:
perl -ne 'print unless m{^//#}' input.txt > output.txt

如果你使用的是 Windows,就需要用双引号代替单引号。

在 grep 中同样适用。

grep -v -e '^//#' input.txt > output.txt

0

遍历文件中的每一行,如果匹配模式,则跳过该行:

my $fh = new FileHandle 'filename'
    or die "无法打开文件 - $!";
while (my $line = $fh->getline) { next if $line =~ m{^//#}; print $line; } close $fh;

这将打印文件中除以“//#”开头的行之外的所有行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接