views:

764

answers:

5

I'm writing a perl module called perl5i. Its aim is to fix a swath of common Perl problems in one module (using lots of other modules).

To invoke it on the command line for one liners you'd write: perl -Mperl5i -e 'say "Hello"' I think that's too wordy so I'd like to supply a perl5i wrapper so you can write perl5i -e 'say "Hello"'. I'd also like people to be able to write scripts with #!/usr/bin/perl5i so it must be a compiled C program.

I figured all I had to do was push "-Mperl5i" onto the front of the argument list and call perl. And that's what I tried.

#include <unistd.h>
#include <stdlib.h>

/*
 * Meant to mimic the shell command
 *     exec perl -Mperl5i "$@"
 *
 * This is a C program so it works in a #! line.
 */

int main (int argc, char* argv[]) {
    int i;
    /* This value is set by a program which generates this C file */
    const char* perl_cmd = "/usr/local/perl/5.10.0/bin/perl";
    char* perl_args[argc+1];
    perl_args[0] = argv[0];
    perl_args[1] = "-Mperl5i";

    for( i = 1;  i <= argc;  i++ ) {
        perl_args[i+1] = argv[i];
   }

   return execv( perl_cmd, perl_args );
}

Windows complicates this approach. Apparently programs in Windows are not passed an array of arguments, they are passed all the arguments as a single string and then do their own parsing! Thus something like perl5i -e "say 'Hello'" becomes perl -Mperl5i -e say 'Hello' and Windows can't deal with the lack of quoting.

So, how can I handle this? Wrap everything in quotes and escapes on Windows? Is there a library to handle this for me? Is there a better approach? Could I just not generate a C program on Windows and write it as a perl wrapper as it doesn't support #! anyway?

UPDATE: Do be more clear, this is shipped software so solutions that require using a certain shell or tweaking the shell configuration (for example, alias perl5i='perl -Mperl5i') aren't satisfactory.

A: 

If you were able to use C++ then perhaps Boost.Program_options would help:

http://www.boost.org/doc/libs/1_39_0/doc/html/program_options.html

Alex Black
Sorry, just bog standard ANSI C 89. I don't know what compiler they might have available (if any).
Schwern
+3  A: 

For Windows, use a batch file.

perl5i.bat

@echo off
perl -Mperl5i %*

%* is all the command line parameters minus %0.

On Unixy systems, a similar shell script will suffice.

Update:

I think this will work, but I'm no shell wizard and I don't have an *nix system handy to test.

perl5i

#!bash

perl -Mperl5i $@

Update Again:

DUH! Now I understood your #! comment correctly. My shell script will work from the CLI but not in a #! line, since #!foo requries that foo is a binary file.

Disregard previous update.

It seems like Windows complicates everything. I think your best there is to use a batch file.

You could use a file association, associate .p5i with perl -Mperl5i %*. Of course this means mucking about in the registry, which is best avoided IMO. Better to include instructions on how to manually add the association in your docs.

Yet another update

You might want to look at how parl does it.

daotoad
Thanks, I might just punt and use a batch file for Windows. A shell scripts won't suffice for Unix because #! will only honor compiled programs.
Schwern
However, one drawback to batch files on Windows -- ^C-ing out of a batch script will cause a "Terminate Batch Process" prompt. And for simple wrapper scripts like this, that prompt is near useless, since even if you choose "N", it still terminates perl.
MiffTheFox
In Unix your shell should have some form of alias function that makes it even unnecessary to create a shell script.
jmucchiello
Miff, as far as I know, there is no way to suppress the "Terminate" message.
daotoad
Thanks @jmucchiello but the purpose is not for my benefit but everyone else who uses the software. And I'm not content to have people mucking about with their shell configurations.
Schwern
You should enclose $@ in double quotes to prevent parameter split. perl -Mperl5i "$@" See bash manual 'Special Parameters' for details.
Hynek -Pichi- Vychodil
Use `perl5i.cmd` not `perl5i.bat`, unless you need it work on Win9x computers.
Brad Gilbert
@Brad Any particular reason why?
Schwern
If you'd rather use a perl/shell script anyway on *nix, you could use /usr/bin/env as a generic binary trampoline.
Inshallah
A: 

I can't reproduce the behaviour your describe:

/* main.c */

#include <stdio.h>

int main(int argc, char *argv[]) {
    int i;
    for (i = 0; i < argc; i++) {
     printf("%s\n", argv[i]);
    }
    return 0;
}

C:\> ShellCmd.exe a b c
ShellCmd.exe
a
b
c

That's with Visual Studio 2005.

hexten
Try args in quotes like '-e "say 'hello'"' and note how they get split up.
Schwern
Yeah, but that's normal for the Windows shell. I don't see how that makes your wrapper any different from perl itself.
hexten
The wrapper receives "-e", "say 'hello'" on argv. The quotes around "say 'hello'" have been stripped by the shell. If you just then pass that along to another program it will get "-e say 'hello'" which Windows doesn't know how to deal with. The wrapper (more likely the shell) strips the quotes so the internal program doesn't have the benefit of them. You'd see it if your program tried to call another one with argv.
Schwern
+1  A: 

Windows is always the odd case. Personally, I wouldn't try to code for the Windows environment exception. Some alternatives are using "bat wrappers" or ftype/assoc Registry hacks for a file extension.

Windows ignores the shebang line when running from a DOS command shell, but ironically uses it when CGI-ing Perl in Apache for Windows. I got tired of coding #!c:/perl/bin/perl.exe directly in my web programs because of portability issues when moving to a *nix environment. Instead I created a c:\usr\bin directory on my workstation and copied the perl.exe binary from its default location, typically c:\perl\bin for AS Perl and c:\strawberry\perl\bin for Strawberry Perl. So in web development mode on Windows my programs wouldn't break when migrated to a Linux/UNIX webhost, and I could use a standard issue shebang line "#!/usr/bin/perl -w" without having to go SED crazy prior to deployment. :)

In the DOS command shell environment I just either set my PATH explicitly or create a ftype pointing to the actual perl.exe binary with embedded switch -Mperl5i. The shebang line is ignored.

ftype p5i=c:\strawberry\perl\bin\perl.exe -Mperl5i %1 %*
assoc .pl=p5i

Then from the DOS command line you can just call "program.pl" by itself instead of "perl -Mperl5i program.pl"

So the "say" statement worked in 5.10 without any additional coaxing just by entering the name of the Perl program itself, and it would accept a variable number of command line arguments as well.

A: 

Use CommandLineToArgvW to build your argv, or just pass your command line directly to CreateProcess.

Of couse, this requires a separate Windows-specific solution, but you said you're okay with that, this is relatively simple, and often coding key pieces specifically to the target system helps integration (from the users' POV) significantly. YMMV.

If you want to run the same program both with and without a console, you should read Raymond Chen on the topic.

Roger Pate