Perl регулярное выражение отрицательно-lookbehind обнаружить файл не хватает окончательной перевод строки - PullRequest
1 голос
/ 30 июня 2019

Следующий код использует tail, чтобы проверить, не завершается ли последняя строка файла новой строкой (перевод строки, LF).

> printf 'aaa\nbbb\n' | test -n "$(tail -c1)" && echo pathological last line
> printf 'aaa\nbbb'   | test -n "$(tail -c1)" && echo pathological last line
pathological last line 
>

Можно проверить одно и то же условие, используя perl, регулярное выражение с положительным взглядом и unless следующим образом. Это основано на том, что, если файл заканчивается символом новой строки, символ, непосредственно предшествующий концу файла, будет \n по определению.

(Напомним, что флаг -n0 приводит к тому, что perl «обдувает» весь файл как одну запись. Таким образом, существует только один $ конец файла.)

> printf 'aaa\nbbb\n' | perl -n0 -e 'print "pathological last line\n" unless m/(?<=\n)$/;'
> printf 'aaa\nbbb'   | perl -n0 -e 'print "pathological last line\n" unless m/(?<=\n)$/;'
pathological last line
>

Есть ли способ сделать это, используя if вместо unless и отрицательный взгляд назад? Следующее не удается, поскольку регулярное выражение, по-видимому, всегда соответствует:

> printf 'aaa\nbbb\n' | perl -n0 -e 'print "pathological last line\n" if m/(?<!\n)$/;'
pathological last line
> printf 'aaa\nbbb'   | perl -n0 -e 'print "pathological last line\n" if m/(?<!\n)$/;'
pathological last line
>

Почему мое регулярное выражение всегда совпадает, даже если перед концом файла стоит символ новой строки? Я пытаюсь проверить конец файла, который не , которому предшествует символ новой строки.

Ответы [ 3 ]

2 голосов
/ 30 июня 2019

/(?<=\n)$/ - странный и дорогой способ сделать /\n$/.

/\n$/ означает /\n(?=\n?\z)/, так что это странный и дорогой способ сделать /\n\z/.

Несколько подходов:

perl -n0777e'print "pathological last line\n" if !/\n\z/'

& # x20;

perl -n0777e'print "pathological last line\n" if /(?<!\n)\z/'

& # x20;

perl -n0777e'print "pathological last line\n" if substr($_, -1) ne "\n"'

& # x20;

perl -ne'$ll=$_; END { print "pathological last line\n" if $ll !~ /\n\z/ }'

Последнее решение позволяет избежать потери всего файла.


Почему мое регулярное выражение всегда совпадает, даже если перед концом файла стоит новая строка?

Потому что вы ошибочно думаете, что $ совпадает только в конце строки. Для этого используйте \z.

0 голосов
/ 01 июля 2019

Скрытым контекстом моего запроса был Perl-скрипт для «очистки» текстового файла, используемого в среде TeX / LaTeX. Вот почему я хотел выпить. (Я ошибочно подумал, что «лазерная фокусировка» на проблеме, рекомендованной stackoverflow, означает редактирование контекста.)

Благодаря ответам, вот улучшенный черновик скрипта:

#!/usr/bin/perl
use strict; use warnings; use 5.18.2;
# Loop slurp: 
$/ = undef;     # input record separator: entire file is a single record.
# a "trivial line" looks blank, consists exclusively of whitespace, but is not necessarily a pure newline=linefeed=LF.
while (<>) {
    s/^\s*$/\n/mg;          # convert any trivial line to a pure LF. Unlike \z, $ works with /m multiline.
    s/[\n][\n]+/\n\n/g; # exactly 2 blank lines (newlines) separate paragraphs. Like cat -s
    s/^[\n]+//;             # first line is visible or "nontrivial."
    s/[\n]+\z/\n/;      # last  line is visible or "nontrivial."
    print STDOUT;
    print "\n" unless m/\n\z/; # IF detect pathological last line, i.e., not ending in LF, THEN append LF. 
}

А вот как это работает, когда назван zz.pl. Сначала грязный файл, затем, как он выглядит после того, как zz.pl справится с этим:

bash: printf '  \n \r   \naaa\n \t \n  \n  \nbb\n\n\n\n    \t' 


aaa



bb



        bash: 
bash: 
bash: printf '  \n \r   \naaa\n \t \n  \n  \nbb\n\n\n\n    \t' | zz.pl
aaa

bb
bash: 
0 голосов
/ 30 июня 2019

У вас есть веская причина использовать регулярное выражение для своей работы?Практикуете регулярные выражения, например?Если нет, я думаю, что более простой подход состоит в том, чтобы просто использовать цикл while, который проверяет на eof и запоминает последние прочитанные символы.Нечто подобное может сделать эту работу.

 perl -le'while (!eof()) { $previous = getc(\*ARGV) } 
          if ($previous ne "\n") { print "pathological last line!" }'

PS: комментарий ikegami о том, что мое решение медленное, хорошо принят.(Спасибо за полезное редактирование, тоже!) Так что я подумал, есть ли способ прочитать файл в обратном направлении.Как оказалось, у CPAN есть модуль именно для этого.После установки я придумал следующее:

perl -le 'use File::ReadBackwards; 
          my $bw = File::ReadBackwards->new(shift @ARGV);
          print "pathological last line" if substr($bw->readline, -1) ne "\n"'

Это должно работать эффективно, даже очень большие файлы.И когда я вернусь, чтобы прочитать его год спустя, я пойму это с большей вероятностью, чем с подходом регулярных выражений.

...