skendric has asked for the wisdom of the Perl Monks concerning the following question:
I'm writing semi-portable code (Linux / Windows).
I use 'system' to execute an external binary ('tshark', part of the Wireshark collection).
I handle slashes like this:
use File::Which qw(which);
$tshark_binary = which('tshark');
$tshark_binary =~ s/\\/\\\\/g if $tshark_binary =~ /\\/;
The last line functions essentially as a "If this is Windows, escape the slashes in the path name", such that 'C:/Program Files/Wireshark/tshark.EXE' turns into 'C://Program Files//Wireshark//tshark.EXE'.
And then, when I actually execute tshark, I use the following:
system("\"$tshark_binary\" -r $pcap -e frame.number -e frame.time_epo
+ch -e ip.src -e ip.id -T text -T fields -E separator=, > $temp_file")
+;
The key 'portability' change being the escaped quotes around $tshark_binary, which aren't necessary in Linux but are needed under Windows, to dodge the:
"C:\Program" is not an executable
errors.
This is fine, and it works. But is there a more elegant / common way to accomplish these two tasks?
--sk
Re: portability / system / elegance (updated)
by haukex (Archbishop) on Dec 04, 2016 at 15:58 UTC
|
Hi skendric,
In my experience, the best thing is to use modules to help you. A quick glance at the source of File::Which shows that it uses File::Spec internally, so the filenames that which is returning should already be in the native format. You can test this via use Data::Dumper; $Data::Dumper::Useqq=1; print Dumper($tshark_binary); (although keep in mind that the output will be in Perl's notation, so e.g. $VAR1 = "C:\\Foo\\Bar.txt" means that the string is actually C:\Foo\Bar.txt).
As for calling external commands, I would not recommend the system(STRING) form, but instead the system(LIST) form (Update: that is, system called with more than one argument, or even better, the system PROGRAM LIST form), or even better, a module such as IPC::Run3, which allows you to dodge most shell quoting and escaping issues (on Linux, it can avoid the shell completely, and on Windows, it automatically uses Win32::ShellQuote).
Unfortunately I can't test this on Windows at the moment, but I think this should work (it works on Linux): Updated: Tested and works on both Linux and Windows. On Windows, which('tshark') does indeed return "C:\\Program Files\\Wireshark\\tshark.EXE" as I suspected above. Also, as I mentioned below, if tshark is already in your PATH, you don't need the which, it works fine without (run3 ['tshark', ...) on both OSes.
use File::Which qw/which/;
use IPC::Run3 qw/run3/;
run3 [which('tshark'), '-r', $pcap, qw/ -e frame.number
-e frame.time_epoch -e ip.src -e ip.id -T text
-T fields -E /,'separator=,'], undef, $temp_file;
Although, if tshark is already in your $ENV{PATH}, why use which at all?
Hope this helps, -- Hauke D | [reply] [Watch: Dir/Any] [d/l] [select] |
|
I like this:
Although, if tshark is already in your $ENV{PATH}, why use which at all?
Just do a tshark command. If it fails then tshark was not in a path to an executable and you get "Command not found".
| [reply] [Watch: Dir/Any] |
Re: portability / system / elegance
by ikegami (Patriarch) on Dec 05, 2016 at 01:40 UTC
|
Avoid the shell. In addition to the usual benefits, this problem goes away.
You should use IPC::Run3 or IPC::Run, but low-level IPC::Open3 will be easy enough to use here.
use File::Spec::Functions qw( devnull );
use IPC::Open3 qw( open3 );
{
my @cmd = (
$tshark_binary => (
-r => $pcap,
-e => "frame.number",
-e => "frame.time_epoch",
-e => "ip.src",
-e => "ip.id",
-T => "text",
-T => "fields",
-E => "separator=,"
),
);
open(local *CHILD_STDIN, '<', devnull) or die $!;
open(local *CHILD_STDOUT, '>', $file_temp) or die $!;
my $pid =
open3('<&CHILD_STDIN', '>&CHILD_STDOUT', '>&STDERR', @cmd);
waitpid($pid, 0);
}
| [reply] [Watch: Dir/Any] [d/l] |
Re: portability / system / elegance
by ww (Archbishop) on Dec 04, 2016 at 14:03 UTC
|
Minimally more elegant (for some values of 'elegant'): use an alternative to the forward slash as a delimiter to change the path-separator in a code segment that ascertains the OS upon which the program is running (if ($^O eq 'MSWin32') { ... (adapted from below); } elsif ($^O eq 'linux') { ....
D:\_STM_work>perl -E "my $foo='/path/path/somepgm.exe'; if ($^O eq 'MS
+Win32') {$foo =~ s?/?\/\/?g;} say $foo;"
//path//path//somepgm.exe
Proofreading all the leaning toothpicks is a practice fraught with possibility of error; for elegance, stuff the separator changing into a sub which is called only when the script ascertains that the OS is some (recent) flavor of Windows.
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
Would not the more "elegant" solution be to use a module that is designed for this purpose, File::Spec? From the documentation for the module, File::Spec requires an appropriate module (File::Spec::Cygwin, File::Spec::Unix, and File::Spec::Win32, among others), which alters such functions as catfile in an appropriate manner. Taking from ww's example (and setting the path value in a sub) using this code:
perl -MFile::Spec -le 'sub tshark_path { my @filename = qw( / usr bin
+tshark ); if ( $^O eq q{MSWin32} ) { @filename = ( q{c:}, q{Program F
+iles}, q{Wireshark}, q{tshark.exe} ); } return @filename; } my @file
+= tshark_path(); print File::Spec->catfile( @file );'
gives the following results when executed on Cygwin (under Win10), Win10, and linux:
# Result under Cygwin
/usr/bin/tshark
# Result under Win10 (Powershell)
C:\Program Files\Wireshark\tshark.exe
# Result from linux
/usr/bin/tshark
Hope that helps. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
|