Perl - Pipes innerhalb Backticks
base
- programmiertechnik
Hallo zusammen,
ich habe kleines Perlproblem, bei dem ich etwas Unterstützung benötige.
Problem:
Ich möchte innerhalb eines Perl-Skriptes einen Shellbefehl mit mehreren Pipes
aufführen. Ich habe festgestellt das es nicht geht und würde gerne wissen,
wie es funktionieren könnte.
Befehl:
my @list=cat $filename | grep -i $pattern | sort | uniq
;
print @list;
Ist es auch ohne Umleitung in Temporärefiles möglich, wie im o.g. Beispiel
oder kann ich nur jeden Befehl einzeln ausführen.
Schonmal vielen Dank für eure Hilfe.
Grüße
Base
你好 base,
Ich möchte innerhalb eines Perl-Skriptes einen Shellbefehl mit mehreren Pipes
aufführen. Ich habe festgestellt das es nicht geht und würde gerne wissen,
wie es funktionieren könnte.
Warum soll das nicht gehen? Wo ist das Problem? Fehlermeldung?
Befehl:
my @list=
cat $filename | grep -i $pattern | sort | uniq
;
print @list;Ist es auch ohne Umleitung in Temporärefiles möglich, wie im o.g. Beispiel
oder kann ich nur jeden Befehl einzeln ausführen.
Folgendes geht bei mir ohne Probleme:
my $out = `cat /var/log/messages | grep "abc" | sort | uniq`;
print $out;
再见,
克里斯蒂安
Hi Christian Kruse,
das cat ist nicht notwendig. So sollte es auch ohne Probleme gehen.
my $out = `grep "abc" /var/log/messages | sort | uniq`;
print $out;
MfG
Otto
Moin Moin!
my @list=
cat $filename | grep -i $pattern | sort | uniq
;
print @list;
Mal so am Rande:
1. Fünf externe Prozesse starten (sh, cat, grep, sort und uniq), nur um Dinge zu erledigen, die Perl selbst in einem Prozess und mit ein paar serienmäßigen Funktionen erledigen kann, ist nicht sonderlich effizient.
2. Das $pattern ohne jedes Escaping SCHREIT nach Shell Injection. Was ist, wenn $pattern "x; rm -rf /" ist? Oder "x > /dev/null; cat /etc/passwd /etc/shadow"? Gleiches gilt für $filename.
3. Backticks erfassen nur STDOUT, nicht STDERR. Fehlermeldungen gehen verloren oder werden mit STDERR des Perl-Scripts vermischt.
4. Grep ist auf verschiedenen Systemen deutlich unterschiedlich implementiert, ein grep auf einem antiken Unix-Server benimmt sich deutlich anders als das moderne GNU grep auf einem aktuellen Linux-System.
5. Shells sind notorisch inkompatibel. Von System zu System verhalten sich die Shells unterschiedlich. Die meisten Shells verstehen "|", aber schon um auf eine Shell-Variable zuzugreifen, gibt es diverse Notationen ($var vs. %var%). Manche Shells haben sehr lästige Einschränkungen, so ist z.B. die Kommando-Länge auf 126 Bytes beschränkt (command.com) oder die STDERR-Umleitung 2> funktioniert nicht (command.com). Viele Systeme nutzen die Bash als /bin/sh, aber nicht alle. Einige Linux-Distributionen haben auf die Debian-Version der ash, dash, umgestellt. Bash-Konstrukte funktionieren damit nicht unbedingt. Bash 2 und bash 3 verhalten sich bei einigen Konstrukten deutlich unterschiedlich. Solaris, BSD und andere Unix-Derivate haben ganz andere Vorstellungen von /bin/sh als der typische Linux-User. Und so weiter, und so weiter. Über VMS will ich jetzt gar nicht nachdenken.
6. Ottos Kommentar bezüglich des unnötigen cat ist vollkommen richtig.
Wie geht es also sicherer, schneller und sauberer?
* Shell-Konstrukte so weit wie möglich vermeiden.
* Variablen für die Shell passend quoten. Die quotemeta-Funktion bzw. die Escape-Sequenz \Q...\E sollte für eine typische Unix-Shell reichen. Manche Shells (z.B. command.com / cmd.exe) haben aber ein deutlich anderes Verhalten als die typischen Unix-Shells, dort erzeugt quotemeta Kauderwelsch, den die Shell nicht oder falsch versteht.
* Backticks sind eine Abkürzung für open("... |"), und das ist selten sicher: besser geht es wie in "Safe Pipe Opens" in perlipc beschrieben. Das dort beschriebene Verfahren funktioniert auch gestaffelt, indem man die ganze Pipe-Klempnerei in Perl statt in einer externen Shell erledigt.
* "cat $filename" eleminieren und stattdessen Eingabe-Umleitung benutzen. grep und diverse andere Programme können alternativ auch den Dateinamen direkt annehmen, wie Otto es beschrieben hat.
* grep eleminieren, Perl hat eine hervorragende RE-Engine und die grep-Funktion, die dem grep-Kommando sogar noch überlegen ist.
* Gleiches gilt für sort und die [http://search.cpan.org/~nwclark/perl-5.8.9/pod/perlfunc.pod#sort-Funktion].
* uniq kann ebenfalls eleminiert werden, typischerweise nutzt man dafür eine Hilfsvariable %seen in Kombination mit grep.
* Bleibt das Öffnen der Quelldatei. Um Perls open-Funktion ihre "magischen" Tricks abzugewöhnen, wenn Sonderzeichen im Dateinamen enthalten sind, empfiehlt sich die Aufruf-Variante mit drei Parametern (Handle, Modus, Name).
Also:
local *FILE;
open FILE,'<',$filename or die "$filename: $!";
my %seen=();
my @list=grep { !$seen{$_}++ } sort grep /$pattern/i,<FILE>;
close FILE or die "$filename: $!";
print @list;
Alexander