Hello, I've been writing more and more utilities in Perl and using them both in Windows and GNU/Linux. Since I'm so used to UNIX shells and terminals, cmd.exe obviously leaves much to be desired, but I don't care for Cygwin or MSYS environments. They each have their own strengths and weaknesses and none of them are fully usable.
So anyway I've been using PyCmd as a wrapper around cmd.exe. It basically adds sensible tab completion and history recall. One thing that it doesn't do is consider .pl files (or extensionless files with a shebang, naturally) executable in Windows. So you can't tab complete these programs. The Windows shell itself seems to associate .pl with Strawberry Perl and it does seem to work via the PATH environment variable. There's just no tab completion. So what I usually do (and it looks like ExtUtils::MakeMaker does too) is wrap the Perl programs in a .bat or .cmd script. The problem then is obviously preserving arguments with whitespace or special characters when passing them onward to the Perl program. In e.g., bash you can just do "$@" and it will expand to quoted arguments, correctly passing them on, but no such mechanism exists for cmd.exe that I am aware of. %* is substituted for the command line arguments, but they aren't quoted or escaped so they are not preserved. You could hard-code up to 9 arguments with "%1" "%2" ... "%n", but then you'll be passing always 9 arguments, whether there are 3 or 12. So I'm curious what other Perl users do when they're stuck in Windows (or similar platforms with sub-par shells). Does anybody have any tricks that work well, or know of any existing CPAN modules to make this easier? The other day I found myself having trouble with a simple little program that "sanitizes" file names by removing whitespace and certain special characters (things that are inconvenient from the command shell). It just processes @ARGV, but I'd find myself passing it things like "foo\bar baz.txt". Naturally, the wrapper .cmd program would pass that on as "foo\bar" "baz.txt" and the Perl program wouldn't work. For this particular case I decided to write some code to try to "guess" what was originally meant and stitch the arguments back together. For my amusement I'll include the WIP module that it resulted in for your code review and criticism. I just hacked it together over a couple of days in a few hours. It basically does a few things: it tries to guess when something is a file path and stitch it back together, it tries to preserve options as individual arguments, and it tries to simulate single-quoting and join arguments based on single-quote wrappers. So if it finds an argument that looks like a filename with an extension then it will join it with previous arguments to form a full path. ('foo', 'bar', 'baz.txt') will become 'foo bar baz.txt'. If it finds anything that begins with a '-' then it considers it an option and leaves it on its own. ('foo', '-bar', 'baz.txt') will not be modified because '-bar' is preserved as a lone argument. If an argument begins with a single-quote then it considers it a single-quoted argument and joins it with subsequent arguments until it finds an argument that ends with a single-quote. So ("foo", "'bar", "baz'", "bak.txt") will become ('foo', 'bar baz', 'bak.txt'). As a special rule, if it finds ++ then it stops guessing and considers the subsequent arguments to be correct as is. Take a look and let me know what you think about it. And keep an eye out for velociraptors[1]. #!/usr/bin/env perl # # h4x: Attempt to stitch arguments with spaces in them back together. # Obviously will result in a lot of false positives. # package GuessArgs; use v5.016; use strict; use utf8; use version; use warnings; BEGIN { our $VERSION = version->declare('0.0.1'); } use Data::Dumper; use File::Glob qw/:bsd_glob/; my $debug = 0; my $main = sub { $debug = 1; guess_args(\@ARGV); say Dumper \@ARGV; }; $main->() unless caller(); # Attempts to stich arguments back together for platforms^H that # 'struggle' to preserve arguments with whitespace. sub guess_args($) { my ($orig_args) = @_; return @$orig_args unless $debug || $^O eq 'MSWin32'; my @args; my $arg; my $literal = 0; my $quoted = 0; my $append_arg = sub { if(defined $arg) { $arg .= ' ' . $_; } else { $arg = $_; } }; my $flush_arg = sub { if(defined $arg) { push @args, $arg; undef $arg; } }; for (@$orig_args) { # h4x: ++ is used to terminate guessing and process literally. if($literal) { $append_arg->(); $flush_arg->(); } elsif($_ eq '++') { $flush_arg->(); $literal = 1; next; # h4x: Single-quoted argument. } elsif($quoted) { QUOTED: $append_arg->(); # We have the tail. if(/'$/) { $arg = substr($arg, 1, length($arg) - 2); $flush_arg->(); $quoted = 0; } # Looks like single-quoted argument begins. } elsif(/^'/) { $flush_arg->(); $quoted = 1; goto QUOTED; # Looks like an option. Assume not continuation. } elsif(/^-/) { $flush_arg->(); $append_arg->(); $flush_arg->(); next; # Looks like the tail of a file path. } elsif(/\.[^\s\.]+$/) { $append_arg->(); $flush_arg->(); # Unknown. Assume partial for now. } else { $append_arg->(); } } $flush_arg->(); @$orig_args = map glob, @args; } # Did I say import? I meant modify your @main::ARGV! >:) sub import { $debug = grep $_ eq ':debug', @_; guess_args(\@main::ARGV) unless grep $_ eq ':disabled', @_; } 1; __END__ Regards, -- Brandon McCaig <bamcc...@gmail.com> <bamcc...@castopulence.org> Castopulence Software <https://www.castopulence.org/> Blog <http://www.bamccaig.com/> perl -E '$_=q{V zrna gur orfg jvgu jung V fnl. }. q{Vg qbrfa'\''g nyjnlf fbhaq gung jnl.}; tr/A-Ma-mN-Zn-z/N-Zn-zA-Ma-m/;say' [1] http://xkcd.com/292/. Because I *CAN*.
signature.asc
Description: Digital signature