В чем разница между элементами конвейеризации или указанием их в качестве аргументов в однострочном Perl? - PullRequest
2 голосов
/ 24 сентября 2010

Изучая Perl, я также изучаю Linux (Ubuntu), так что здесь время потягивать огонь.

В чем разница между:

find . -type f | perl -nle '... #aka yada yada'

и

perl -nle '... # same yada yada' `find . -type f`

Первый передает файл NAMES в Perl, а второй передает файл СОДЕРЖАНИЕ, как кажется. Всегда ли это так под Unix или специальным свойством Perl?

Ответы [ 3 ]

7 голосов
/ 24 сентября 2010

Первый генерирует список файлов и «перенаправляет» его в perl. Затем Perl читает список, читая из стандартного ввода:

 while( <> ) { ... }

Это обычная вещь в оболочках Unix, поэтому вам вообще не нужно использовать perl:

 $ ifconfig | grep en0

Второй генерирует список имен файлов и превращает их в аргументы командной строки, которые затем отображаются в вашей программе в @ARGV:

 foreach( @ARGV ) { ... }

Эта функция также не относится к Perl. Оболочка предоставляет биты после команды в некоторой структуре данных, к которой программа может получить доступ. Другие языки имеют похожие конструкции, даже если они не выглядят одинаково.

Однако оператор diamond, <> автоматически пройдет по именам файлов, указанным в командной строке, так что цикл while все еще работает. Это особенность Perl.

Проблема со вторым подходом проявляется, когда у вас длинный список аргументов. Некоторые оболочки ограничивают количество вещей, которые могут отображаться в командной строке. Мне не так нравится вторая версия, просто по этой причине.

Однако вместо использования find (1) (версия оболочки) вы можете превратить его в автономную программу Perl:

$ find2perl . -type f

Выводом является программа Perl, которая не должна полагаться на какие-либо внешние команды.

3 голосов
/ 24 сентября 2010

Первый отправляет имена файлов, по одному на строку, в STDIN программы, что -n вызывает perl зацикливание (потому что не было аргументов командной строки).

Второй вызов perl со списком имен файлов в качестве аргументов.Если аргументы переданы в -n, то откроет каждый аргумент и прочитает каждую строку из каждого файла.

Таким образом, первый оперирует именами файлов, а второй оперирует содержимым файлов.

Вы можете увидеть код, который perl пишет для вас, используя B::Deparse:

perl -MO=Deparse -nle 'print'

производит

BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    print $_;
}
-e syntax OK

The *Блок 1022 * и chomp создаются параметром -l, а цикл while создается параметром -n.ARGV - это специальный файловый дескриптор, который выполняет магию чтения из STDIN, если нет аргументов, или открывает каждый из аргументов по очереди, если они есть.

Две формыопределенно не взаимозаменяемы.Один влияет на STDIN и другие аргументы командной строки.Если вы измените первый на find . -type f | xargs perl -nle '... #aka yada yada', тогда они будут в основном взаимозаменяемыми (версия xargs может запускать perl более одного раза, а версия с обратным ключом может просто взорваться из-за слишком длинной командной строки).

Многие программы UNIX действуют как фильтры.Правило для фильтров заключается в том, что они читают из STDIN, если в командной строке не было файлов, или из списка файлов, указанных в командной строке.Краткий список включает в себя cat, grep и sort.Perl 5 упрощает внедрение фильтра, как вы уже видели.Но будьте осторожны, способ, которым Perl 5 реализует это, не очень безопасен.Он использует устаревшую версию с двумя аргументами open, что означает, что определенные имена файлов могут иметь непредвиденные последствия:

perl -nle print "cat /etc/passwd|"

Эта команда фактически запускает cat /etc/passwd вместо открытия файла с именемcat /etc/passwd|.Чтобы предотвратить такое поведение, рекомендуется проверить @ARGV на наличие подозрительных имен или использовать модуль ARGV::readonly для очистки @ARGV для вас:

perl -MARGV::readonly -nle print "echo foo|"
Can't open < echo foo|: No such file or directory.
0 голосов
/ 25 сентября 2010

Вы спросили: «Первый передает файл NAMES в Perl, а второй передает файл СОДЕРЖАНИЕ, как кажется. Всегда ли это так в Unix или в специальном свойстве Perl?»Это поведение не является специфичным для Perl.Часть этого делается Unix.Это более широко соблюдаемая конвенция.Поведение конвейера (команды, сопровождаемые |) выполняется ОС.То, что программа делает со своим вводом командной строки или выводом, которое она производит, зависит от команды.

Примеры.Пожалуйста, выполните на вашем компьютере в Bash.

$ mkdir pipetestdir; cd pipetestdir    
$ for f in {a..z}; do printf "%s\n" "File: $f, line: "{1..1000} > $f.txt; done

Это создаст пустой каталог, перейдите в него и создайте 26 файлов по 1000 строк в каждом пустом каталоге.

С утилитой Ubuntu / Linux cat *.txt ваша банкапосмотреть содержимое файлов.*.txt - это , расширенный с помощью Bash до всех 26 .txt файлов.с помощью wc -l *.txt вы можете проверить количество строк всех 26 файлов.Вы можете использовать форму wc -l {a..e}.txt, где Bash использует расширения brace .Вы можете преобразовать эти формы в канал и использовать cat *.txt | wc -l, чтобы просто получить счетчик строк из всех 26 файлов.В первом примере wc -l *.txt открывает 26 файлов, считает строки и отображает результат.Во втором примере cat *.txt | wc -l программа cat открывает 26 файлов и создает объединенный текстовый поток для STDOUT;| превращает это в канал, который направлен на следующую программу;в этом случае wc -l, который получает этот вывод на свой STDIN и считает строки этого без какого-либо отношения к отдельным файлам.

С помощью вкладышей Perl one вы можете легко искать эти файлы.Пример:

$ perl -lne 'print if /^.*666/' *.txt    # the devil's line from 26 files...

Вы можете использовать egrep или awk, чтобы сделать то же самое:

$ egrep '^.*666$' *.txt
$ awk "/^.*666$/ {print}" *.txt

Если вы превратите эту форму в трубу, вы работаете с OUTPUTпредыдущей команды слева от Perl (или awk или egrep).Вывод STDOUT предыдущей части передается в STDIN Perl.Если эта команда создает имена файлов, вы работаете с именами файлов:

$ ls *.txt | perl -lne 'print if /c|d|z/'
$ find . -name '*.txt' | perl -lne 'print if /c|d|z/'

Если вы не расширили их сначала с помощью cat:

$ cat *.txt | perl -lne 'print if /^.*?(c|d|z).*?666$/'

Что выводится аналогично этому:

$ perl -lne 'print if /^.*?(c|d|z).*?666$/' *.txt

Возможно, именно здесь вы запутались в том, что формы взаимозаменяемы?Они не!Происходят две совершенно разные вещи.Если вы используете cat *.txt | perl '...', все файлы объединяются в один длинный текстовый поток и отправляются на следующий этап в конвейере;в этом случае perl '...'.Perl не сможет различить, какой текст и из какого файла.Только потому, что мы ставим отметку в каждом файле, когда создаем их, мы можем видеть, какой файл какой.

В другой форме, perl '...' *.txt, perl открывает файлы и имеет полный контроль над каждым текстовым потоком и файлом.Вы можете контролировать, открываете ли вы файл или нет, печатаете имя файла или нет, и т.д ...

Избегайте, однако, конкретной формы cat a.txt | perl '...' (то есть используйте cat для одного файла), чтобыИзбегайте страшных Бесполезное использование премии Кэт : -}

Вы спрашивали конкретно о форме:

$ perl -nle '... # same yada yada' `find . -type f`

Как указал Брайан Д. Фой , есть ограничения на длину командной строки, и вы должны быть осторожны с этой формой.Вы также можете неожиданно изменить имена файлов с помощью обратных тиков.Вместо формы обратной галочки используйте find с xargs:

$ find . -type f -print0 | xargs -0 perl -nle 'print if /^.*666$/'

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

$ mv z.txt "file name with spaces" 
$ perl -ple '' `find . -name "file*"`       #fails...
$ find . -name "file*" -print0 | xargs -0 perl -ple '' #works...
$ find . -type f -exec perl -wnl -e '/\s1$/ and print' {} + #alternative
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...