Beefy Boxes and Bandwidth Generously Provided by pair Networks RobOMonk
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Running Under Some Shell

by Xiong (Hermit)
on Feb 24, 2010 at 19:23 UTC ( #825147=perlquestion: print w/ replies, xml ) Need Help??
Xiong has asked for the wisdom of the Perl Monks concerning the following question:

Here's a construct stinking to me of the multiplatform workpla¢e. This is the top of a cpan script newly created (by $ /lab/bin/perl -MCPAN -eshell) for the purpose of downloading modules into my development perl only under Linux:

#!/lab/bin/perl eval 'exec /lab/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; #!/usr/bin/perl

... followed by what looks like reasonable Perl code.

Two questions:

1. Practically, can I replace this gubbins with one clean hashbang line?

2. Academically, what exactly is going on here?

perlrun (near -S):

#!/usr/bin/perl eval 'exec /usr/bin/perl -wS $0 ${1+"$@"}' if $running_under_some_shell;

and the even more baroque:

eval '(exit $?0)' && eval 'exec perl -wS $0 ${1+"$@"}' & eval 'exec /usr/bin/perl -wS $0 $argv:q' if $running_under_some_shell;

Gloss on above from MadMongers is notable for the hints:

Yeah, that's a winx thing... ...the first part of the script is both (ba)sh and perl acceptable...

Something Google sucked into its archives uses a similar deal several times in one file, if i read aright:

sed "s/^X//" >'dbdump' <<'END_OF_FILE' X: #!/usr/bin/perl Xeval 'exec /usr/bin/perl -S $0 ${1+"$@"}' X if $running_under_some_shell; X#!/usr/local/bin/perl

The Camel 24.5.1. Generating Other Languages in Perl (so I guess this is official):

print &q(<<"EOT"); : #!$bin/perl : eval 'exec $bin/perl -S \$0 \${1+"\$@"}' : if \$running_under_some_shell; : EOT

Grokbase startperl insanity:

print OUT <<"!GROK!THIS!"; $Config{startperl} -- # -*- perl -*- eval 'exec $Config{perlpath} -S \$0 \${1+"\$@"}' if \$running_under_some_shell; !GROK!THIS!

This last is noteworthy in that -*- (seen in perlrun) is explained to be not merely a whimsical example but a bonafide workaround for some terrible weirdness:

By the time there's the -*- perl -*- in place, the #! redispatcher code is happy, in that it doesn't attempt to re-exec. Adding another #!perl and an -x earlier doesn't seem to make a difference. Taking out the -*- perl -*- causes an infinite loop

This page also has the charming metasyntactic variable I employ with relish in this post.

Finally, from an older copy of the cpan script on my very own machine:

#!/usr/bin/perl eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; # not running under some shell

This annoys me thrice. There's something blatant about if 0; the comment seems to invert the test relative to previous examples; this script is generated to run on a Linux box known to interpret hashbangs correctly.

I gather that the test, both here and in other examples, will not execute under most shells but the preceeding eval will; while perl will execute the test and ignore the eval.

I also gather that command-line arguments are being passed through to perl when the shell executes; but then why the 1+? How, in the 21st Century, did this 19th Century springloaded contraption come to spray tiny oily sprockets across my workbench?

My conclusions are: Yes, I can lop off this gubbins and replace it with one honest hashbang; and Blame Micro$oft. Please correct, expand, and illuminate.

Update:

Corion, thank you++ for that detail. Of course this still leaves open the question of why such an obscure approach is required. In a way, it's worse, all in the family. Can't all shell authors agree on one idiom for dispatching scripts?

rubasov, thank you++ for the extremely detailed breakdown. Practically, I don't have to know what this thing does to delete it. It's a curiosity, though; an itch I had to scratch.

One wonders how long a script could be written that would execute (differently but without error) under how many shells and interpreters. Upvote ambrus for a strong effort but I was thinking of something a tad bit more audacious. Topic for another node...

And yes, thank you++ shmem. It's always good to hear the other side of the issue. Complacency is a greater threat to excellence than ignorance.

Comment on Running Under Some Shell
Select or Download Code
Re: Running Under Some Shell
by Corion (Pope) on Feb 24, 2010 at 22:23 UTC

    While your conclusion is correct, Microsoft is not at all to blame for this. The extended hashbang is there so you can run your Perl script with almost any /bin/sh under the sun, even a shell/kernel that does not honor the hashbang and it will still launch perl in the end.

    There is a similar approach for Windows and cmd.exe that makes a Perl program appear to be a valid .cmd file as well, but that approach is distinct from the approach taken for unixish environments.

Re: Running Under Some Shell
by rubasov (Friar) on Feb 24, 2010 at 22:51 UTC
    These "gubbins" are needed only when you encounter a platform which does not handle shebang lines (#!...). Those platforms generally will pass anything to the sh interpreter what does not look like a binary executable file (== does not have the format of an a.out or ELF ...). Therefore if you're sure you or your users won't execute those programs on such platforms then you can replace them safely with the simple shebang.

    Let's investigate this (the first case coming from perlrun):

    #!/usr/bin/perl eval 'exec /usr/bin/perl -wS $0 ${1+"$@"}' if $running_under_some_shell;

    This code will be passed first to sh. sh will interpret the shebang line as a comment, then will look at the second line as a command terminated by the newline, so the if line is not relevant in this case.

    If only sh would interpret this code, then the eval would not be needed and this would suffice:

    exec /usr/bin/perl -wS $0 ${1+"$@"}

    However later this line will be seen by perl also, that's why we should find a construct that is syntactically valid both in sh and in perl. The eval '...' construct fulfills this.

    At this point sh is interpreting the eval line, not looking ahead what will follow in the source file, and never will encounter the if line because it is replacing itself with a perl process by using exec.

    perl is called with two options (-w and -S), you already know the meaning of those. Then in the perl command line follows the name of the actual script ($0) and all the command line arguments passed to this script (${1+"$@"}).

    "$@" expands to the positional parameters in sh terminology, so it is equivalent to "$1" "$2" "$3" ...

    The ${parameter:+word} syntax is explained in the man page of bash (in the Parameter Expansion section):

    ${parameter:+word}
    Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
    And omitting the colon from the construct above is also explained there:
    ... bash tests for a parameter that is unset or null; omitting the colon results in a test only for a parameter that is unset

    As I see ${1+"$@"} can be simplified to "$@". The only difference is that in the first case only $1 will be checked whether it is set.

    Okay, if the exec succeeded then now we have a running perl processing the same file getting the same arguments. The shebang line is a comment for perl also, but the newline is not a statement separator in perl, so the first statemant is eval '...' if $running_under_some_shell;. As this variable will be always false, this is semantically a no-op, so perl will happily skip it, and execute the rest of the file.

    If you write eval '...' if 0; it has the same meaning, but you won't get the warning $running_under_some_shell used only once.

    I'm not really knowledgeable in the c shell, so I can't explain now the gubbin designed for sh and csh at the same time.

    I hope this helps understanding what is going on.
      As I see ${1+"$@"} can be simplified to "$@". The only difference is that in the first case only $1 will be checked whether it is set.
      Yes, that's my idea as well. If there's at least one argument, $1 is set and ${1+"$@"} first expands to "$@" which then expands to $1 $2 $3 .... If $1 isn't set ${1+"$@"} expands to nothing. In either case, it's the same as "$@". There might be some shell somewhere that makes a difference, but according my reading of a manual found on the Interwebs, even in the Bourne Shell on System 7 both ${1+"$@"} and "$@" are identical. The POSIX standard also says that in the absence of positional parameters, "$@" expands to nothing.
      I'm not really knowledgeable in the c shell, so I can't explain now the gubbin designed for sh and csh at the same time.
      I'd be surprised if the gubbin actually worked on csh. AFAIK, it doesn't do ${foo+bar} style parameter expansion (but I don't have a csh laying around to try it on).

      Perhaps the ${1+"$@"} is just a piece of cargo cult. Noone is really sure if it's going to break on some system somewhere, and just leaves it as is, instead of replacing it with "$@". It's not that there's a huge savings.

        I'd be surprised if the gubbin actually worked on csh.
        Oh sorry. I wanted to refer to another gubbin, namely the second example in perlrun, that's the "more baroque" in the OP.

        Perhaps the ${1+"$@"} is just a piece of cargo cult.
        Exactly the same thought here.

Re: Running Under Some Shell
by shmem (Canon) on Feb 24, 2010 at 22:53 UTC
    Please correct, expand, and illuminate.

    With regard to the big picture: you've spent time and brain to avoid your system's perl in a development environment, because you've been bitten by installs via cpan, and you want to achieve compatibility between your development and productive environments.

    You've gone a long way (and learned much in the process, I bet.) Time to step back and meditate...

    Why, Oh Why, don't you just package CPAN modules according to the requirements of the package manager of your OS, forget about cpan, use the perl shipped with your OS and put your wit into things that matter?

      Why install a second perl with its own module library?

      I've learned much, which should be justification enough for any effort; nor do I think my education is finished. Primarily, I feel comfortable with this separation of church and state. I enjoy the rare feeling of actually going along with the majority opinion.

      I get to experiment freely, even rashly, with the development environment without fearing to wake up to funny green squiggles marching across my screen. There's a limit to this, I guess, but a develenviron pushes that limit a bit further away.

      Compiling locally is good preparation for building the production environment on remotehost. I have just about got comfortable with the idea of throwing out remotehost's perl 5.8.8 entirely and installing fresh to match localhost:/lab, thereby blowing away the version compatibility questions, the module version questions, and the cargo cult module loader prescribed for lesser mortals.

      I've compiled my dev perl as 5.10.1, which beats Ubuntu's default install of 5.10.0; I've also now got threading and debugging support. I might could substitute my new, hot-rod perl for system use. It's also possible that I could try and end up with blue flames shooting from the vents.

      On the gripping hand, my nice clean Ubuntu system works; and my nice new dev perl works.

Re: Running Under Some Shell
by ambrus (Abbot) on Feb 25, 2010 at 18:58 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://825147]
Approved by sweetblood
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (9)
As of 2014-04-19 06:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (478 votes), past polls