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*.

Attachment: signature.asc
Description: Digital signature

Reply via email to