Что должен знать каждый Perl-хакер о Perl -ne?

Я использую командную строку Perl с -ne возможность в течение многих лет, в основном для обработки текстовых файлов способами, которые sed не может. Пример:

cat in.txt | perl -ne "s/abc/def/; s/fgh/hij/; print;" > out.txt

Я понятия не имею, где я это узнал, и только сегодня прочитал perlrun и обнаружил, что есть другие формы (perl -pe например).

Что еще я должен знать о perl -ne?

7 ответов

Решение

perl -ne 'CODE' эквивалентно программе

while (<>) {
    CODE
}

perl -ane 'CODE' а также perl -F/PATTERN/ -ane также хорошие идиомы, чтобы знать о. Они эквивалентны

while (<>) {
    @F = split /\s+/, $_;
    CODE
}

а также

while (<>) {
    @F = split /PATTERN/, $_;
    CODE
}

Пример: продвинутый grep:

perl -ne 'print if/REGEX1/&&!/REGEX2/&&(/REGEX3/||/REGEX4/&&!/REGEX5/)' input

perl -F/,/ -ane 'print if $F[2]==4&&$F[3]ge"2009-07-01"&&$F[3]lt"2009-08-01"' file.csv


Особенно умный пример, который использует несоответствующие скобки, здесь.

Есть одна важная вещь, которую нужно знать о perl -ne а также perl -pe скрипты: они неявно используют <>,

"Почему это важно?" Вы можете спросить.

Магия <> Оператор использует форму 2 arg open. Если вы помните, 2 arg open включает спецификацию режима с именем файла в одном аргументе. Старый стиль вызова open FILE, $foo уязвим для манипулирования файловым режимом. Особенно интересным режимом в этом контексте является | - вы открываете дескриптор канала для процесса, который выполняете.

Вы можете подумать "Большое дело!", Но это так.

  • Представьте себе задачу cron, выполняемую пользователем root для запуска файлов журналов в некотором каталоге.
  • Сценарий вызывается как script *,
  • Представьте себе файл в этом каталоге с именем |rm -rf /,

Что просходит?

  1. Оболочка расширяет * и мы получаем script file_1 file_2 '|rm -rf /' file_4
  2. Скрипт обрабатывает file_1 а также file_2,
  3. Затем он открывает дескриптор STDIN из rm -rf /,
  4. Много дисковой активности следует.
  5. file_4 больше не существует, поэтому мы не можем его открыть.

Конечно, возможности безграничны.

Вы можете прочитать больше обсуждения этого вопроса в Perlmonks.

Мораль этой истории: будьте осторожны с <> оператор.

FWIW, я только что подтвердил, что это все еще проблема с Perl 5.10.0.

Вы можете указать более одного предложения -e. Иногда у меня появляется командная строка, которая начинает расти, когда я уточняю операцию поиска / извлечения / манипуляции. если вы что-то наберете неправильно, вы получите "номер строки", сообщающий вам, какая ошибка у -e.

Конечно, некоторые могут возразить, что если у вас есть более одного или двух предложений -e, возможно, вам следует поместить в скрипт все, что есть, но некоторые вещи просто отбрасываются, так что зачем беспокоиться.

perl -n -e 'if (/good/)' -e '{ system "echo $_ >> good.txt"; }' \
-e 'elsif (/bad/)' -e '{ system "echo $_ >> bad.txt"; }' \
-e 'else' -e '{ system "echo $_ >> ugly.txt"; }' in.txt another.txt etc.txt

Предположительно, вы бы сделали что-то менее тривиальное, чем grep / egrep, в 3 файла:-)

-i опция позволяет вам сделать изменения в строке:

 perl -i -pe 's/abc/def/; s/fgh/hij/' file.txt

или сохраните резервную копию:

 perl -i.bak -pe 's/abc/def/; s/fgh/hij/' file.txt

Мне нравится думать о perl -n как выбирая конкретные биты ввода и perl -p как map для всех строк ввода.

Как вы заметили, можно получить эффект -p с -n и мы можем подражать наоборот:

 $ echo -e "1 \ n2 \ n3" | perl -pe '$ _ = "" if $ _% 2 == 0'
1
3 

Пропуск строк с next казалось бы более естественным, но -p оборачивает код в

LINE:
while (<>) {
    ...     # your program goes here
} continue {
    print or die "-p destination: $!\n";
}

По дизайну, next работает continue блоки:

Если есть continue BLOCK, он всегда выполняется непосредственно перед тем, как условная оценка собирается быть оценена снова. Таким образом, его можно использовать для увеличения переменной цикла, даже если цикл был продолжен через next заявление.

-l Переключатель имеет два полезных эффекта:

  1. С -n а также -p автоматически chomp каждая входная запись.
  2. Задавать $\ так что каждый print неявно добавляет терминатор.

Например, чтобы захватить первые 10 портов UDP, упомянутых в /etc/services ты можешь

 perl -ane 'print $F[1], если $F[1] =~ /udp/' /etc/services | голова 

но упс

 7/udp9/udp11/udp13/udp17/udp19/udp37/udp39/udp42/ уд... 

Лучше:

 $ perl -lane 'print $ F [1], если $F[1] =~ /udp/' /etc/services | голова
7 / UDP
9 / UDP
11 / UDP
13 / UDP
17 / UDP
19 / UDP
37 / UDP
39 / УДП
42 / UDP
53/ UDP 

Помни что -n а также -p также может быть в строке shebang, чтобы сохранить вышеупомянутый oneliner в виде скрипта:

#! /usr/bin/perl -lan

BEGIN {
  @ARGV = ("/etc/services") unless @ARGV;
  open STDOUT, "|-", "head" or die "$0: head failed";
}

print $F[1] if $F[1] =~ /udp/

Моя любимая ссылка на Perl one liners (и самый популярный в Google для этой фразы) охватывает perl -ne: http://novosial.org/perl/one-liner/

Я часто использую sed или же awk но мне очень нравится это perl функция сопоставления шаблонов:

$ cat my-input.txt
git 111 HERE 2222 voila 333
any 444 HERE none start 555 HERE 6
svn 777 aaaa 8888 nothing
two 222 HERE 9999 HERE 0000

$ perl -nle 'print $a if (($a)=/HERE ([0-9]+)/)' my-input.txt
2222
6
9999
Другие вопросы по тегам