Re: Execute command with spaces in perl
by Corion (Patriarch) on Aug 27, 2018 at 10:22 UTC
|
Your quoting does not match how the Windows shell (cmd.exe) processes quotes.
cmd.exe does not recognize a backslash before a space as the intention to quote that space. A working invocation of firefox.exe could be:
my $ff_exe = q{C:\\Program Files\\Mozilla Firefox\\firefox.exe};
my @cmd = ($ff_exe, '--version');
my $shell_command = join " ", # separate by spaces
map { /\s/ ? qq{"$_"} : $_ } # enclose in double q
+uotes if needed
@cmd;
print $shell_command;
Note that under Windows, the --version command line switch does not output anything. | [reply] [d/l] [select] |
|
|
C:\usr\local\share\PassThru\perl>"C:\Program Files\Mozilla Firefox\fir
+efox.exe" --version
C:\usr\local\share\PassThru\perl>"C:\Program Files\Mozilla Firefox\fir
+efox.exe" --version | perl -e "print $_ for <>"
Mozilla Firefox 61.0.2
C:\usr\local\share\PassThru\perl>"C:\Program Files\Mozilla Firefox\fir
+efox.exe" --version > out.txt
C:\usr\local\share\PassThru\perl>type out.txt
Mozilla Firefox 61.0.2
I actually first noticed it inside perl, using the piped open described elsewhere, while writing Re^5: Execute command with spaces in perl:
C:\usr\local\share\PassThru\perl>perl -Mwarnings -Mstrict -e "my $cmd
+= q|C:\Program Files\Mozilla Firefox\firefox.exe|; open my $fh, '-|',
+ $cmd, '--version' or die $!; my $out = do { local $/; <$fh> }; close
+ $fh or die $! ? $! : qq(\$?=$?); print $out"
Mozilla Firefox 61.0.2
| [reply] [d/l] [select] |
Re: Execute command with spaces in perl
by haukex (Archbishop) on Aug 27, 2018 at 10:48 UTC
|
I would strongly recommend you use a module to do this for you, instead of trying to do it yourself. For example, there's capturex from IPC::System::Simple, or IPC::Run3, which uses Win32::ShellQuote under the hood on Windows:
use warnings;
use strict;
use IPC::Run3 'run3';
my $cmd = 'C:\Program Files\Mozilla Firefox\firefox.exe';
run3 [$cmd,'--version'], undef, \my $out or die "run3: $!";
die "\$?=$?" if $?;
chomp $out;
If you want to stick with piped open, you could also use Win32::ShellQuote directly to do the quoting for you. I wrote about the above options, as well as the pitfalls with running external commands, at length here, with example code. In this case, since you're running a fixed external command with one or more arguments, you can also use the LIST form of open:
use warnings;
use strict;
my $cmd = 'C:\Program Files\Mozilla Firefox\firefox.exe';
open my $fh, '-|', $cmd, '--version' or die $!;
my $out = do { local $/; <$fh> }; # slurp
close $fh or die $! ? $! : "\$?=$?";
chomp $out;
Note how I am also checking close for errors, this is necessary for a piped open. I'm also slupring the entire output of the command into my variable $out; in your code you're only fetching the first line of output, despite that you've named your variable $aarray. | [reply] [d/l] [select] |
|
|
use warnings;
use strict;
## works: no command line options
my $cmd1 = 'C:\Program Files\7-Zip\7z.exe';
## works: Double quotes tells Windows where the first argument, the co
+mmand, ends.
my $cmd2 = '"C:\Program Files\7-Zip\7z.exe" l Win32-Console-0.10.tar.g
+z';
## doesn't work: gives message about 'C:\Program' is not recognized...
my $cmd3 = 'C:\Program Files\7-Zip\7z.exe l Win32-Console-0.10.tar.gz'
+;
foreach my $cmd ( $cmd1, $cmd2, $cmd3 ) {
open my $fh, "$cmd|" or die $!;
my $out = do { local $/; <$fh> }; # slurp
close $fh or die $! ? $! : "\$?=$?";
print "<<<$out>>>\n";
}
__DATA__
<<<
7-Zip 18.05 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-04-30
Usage: 7z <command> [<switches>...] <archive_name> [<file_names>...]
<Commands>
a : Add files to archive
b : Benchmark
d : Delete files from archive
e : Extract files from archive (without using directory names)
h : Calculate hash values for files
i : Show information about supported formats
l : List contents of archive
rn : Rename files in archive
t : Test integrity of archive
u : Update files to archive
x : eXtract files with full paths
<Switches>
-- : Stop switches parsing
@listfile : set path to listfile that contains file names
-ai[r[-|0]]{@listfile|!wildcard} : Include archives
-ax[r[-|0]]{@listfile|!wildcard} : eXclude archives
-ao{a|s|t|u} : set Overwrite mode
-an : disable archive_name field
-bb[0-3] : set output log level
-bd : disable progress indicator
-bs{o|e|p}{0|1|2} : set output stream for output/error/progress line
-bt : show execution time statistics
-i[r[-|0]]{@listfile|!wildcard} : Include filenames
-m{Parameters} : set compression Method
-mmt[N] : set number of CPU threads
-mx[N] : set compression level: -mx1 (fastest) ... -mx9 (ultra)
-o{Directory} : set Output directory
-p{Password} : set Password
-r[-|0] : Recurse subdirectories
-sa{a|e|s} : set Archive name mode
-scc{UTF-8|WIN|DOS} : set charset for for console input/output
-scs{UTF-8|UTF-16LE|UTF-16BE|WIN|DOS|{id}} : set charset for list fi
+les
-scrc[CRC32|CRC64|SHA1|SHA256|*] : set hash function for x, e, h com
+mands
-sdel : delete files after compression
-seml[.] : send archive by email
-sfx[{name}] : Create SFX archive
-si[{name}] : read data from stdin
-slp : set Large Pages mode
-slt : show technical information for l (List) command
-snh : store hard links as links
-snl : store symbolic links as links
-sni : store NT security information
-sns[-] : store NTFS alternate streams
-so : write data to stdout
-spd : disable wildcard matching for file names
-spe : eliminate duplication of root folder for extract command
-spf : use fully qualified file paths
-ssc[-] : set sensitive case mode
-sse : stop archive creating, if it can't open some input file
-ssw : compress shared files
-stl : set archive timestamp from the most recently modified file
-stm{HexMask} : set CPU thread affinity mask (hexadecimal number)
-stx{Type} : exclude archive type
-t{Type} : Set type of archive
-u[-][p#][q#][r#][x#][y#][z#][!newArchiveName] : Update options
-v{Size}[b|k|m|g] : Create volumes
-w[{path}] : assign Work directory. Empty path means a temporary dir
+ectory
-x[r[-|0]]{@listfile|!wildcard} : eXclude filenames
-y : assume Yes on all queries
>>>
<<<
7-Zip 18.05 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-04-30
Scanning the drive for archives:
1 file, 29375 bytes (29 KiB)
Listing archive: Win32-Console-0.10.tar.gz
--
Path = Win32-Console-0.10.tar.gz
Type = gzip
Headers Size = 33
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ -----------------
+-------
2013-11-28 16:16:37 ..... 153600 29375 Win32-Console-0.1
+0.tar
------------------- ----- ------------ ------------ -----------------
+-------
2013-11-28 16:16:37 153600 29375 1 files
>>>
With $cmd3 the following goes to STDERR:'
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
$?=256 at C:\usr\pm\pipe.pl line 11.
| [reply] [d/l] |
|
|
See Re^2: Having to manually escape quote character in args to "system"? for an interesting discussion on the issue of command-line arguments on Windows. Basically, command line arguments are not passed as a list, but a single big string, and it's up to the called command, not the shell, to interpret that string, and different commands may do that differently.
| [reply] |
|
|
Thanks for your suggestion. The '-|", LIST pipe seems not working on Windows. My script should be able to execute seamlessly between Linux and Windows.
Instead when I use the STRING pipe, as mentioned here: https://www.perlmonks.org/bare/?node_id=902198,
it again breaks splitting on the space:
'C:\Program' is not recognized as an internal or external command, operable program or batch file.
my $cmd = 'C:\Program Files\Mozilla Firefox\firefox.exe --version';
chomp($cmd);
open my $fh2, "$cmd|" or die $!;
my $out = do { local $/; <$fh2> }; # slurp
close $fh2 or die $! ? $! : "\$?=$?";
chomp $out;
| [reply] [d/l] |
|
|
The '-|", LIST pipe seems not working on Windows.
open my $fh2, "$cmd|" or die $!;
it again breaks splitting on the space
I tested my code before posting, and the '-|', LIST form should work on Windows (Update: at least on Perl 5.22 and up). If it's not working for you, please show the code you're trying and the exact error messages you're getting (How do I post a question effectively? and Short, Self-Contained, Correct Example). The code you posted here is not the '-|', LIST form, which it why it isn't working - why aren't you using the code I showed? With a single string argument, you need to do the quoting correctly; I explained the difference between the two in the node I linked to earlier.
If you need --version to be variable as well, you could do this:
use warnings;
use strict;
my @cmd = ('C:\Program Files\Mozilla Firefox\firefox.exe',
'--version');
die "\@cmd must have more than one element" unless @cmd>1;
open my $fh, '-|', @cmd or die $!;
my $out = do { local $/; <$fh> }; # slurp
close $fh or die $! ? $! : "\$?=$?";
chomp $out;
Note I've added the check for @cmd having more than one element to make sure the LIST form is being used.
If for whatever reason you really need the string form, then you'll have to use better quoting, like what is provided by Win32::ShellQuote, or the other modules I named.
My script should be able to execute seamlessly between Linux and Windows.
The code I showed should work fine on both OSes, except of course for the filename of the Firefox executable; if you've having trouble on Linux, again, please show exactly what that trouble is. For platform-independent filename handling, I recommend the core module File::Spec, or with a nicer interface Path::Class. | [reply] [d/l] [select] |
|
|
|
|
|
|
|
|