stackspace has asked for the wisdom of the Perl Monks concerning the following question:

My RHEL 5 box has 2 perl versions on it, say X and Y. The OS comes with /usr/bin/perl and /usr/bin/perlX, which are the same inode, and we've installed /path/to/perlY in /usr/local. So we can assume /usr/bin/perlX is guaranteed to exist.

I want to replace /usr/bin/perl with a script that says "exec /path/to/perlY if it's executable, and exec /usr/bin/perlX otherwise". This is to help reduce impact in the case that perlY is unavailable due to NFS problems. (The /path/to/perlY is ahead of /usr/bin in the shell $PATH.)

The idea is that "/usr/bin/perl ABC", and also scripts with "#!/usr/bin/perl" shebang lines, should use the desired perl. First I made this be /usr/bin/perl:

#!/bin/bash if [ -x /path/to/perlY ]; then exec /path/to/perlY "$@" else exec /usr/bin/perlX "$@"
This worked for e.g. "/usr/bin/perl -V", but for perl scripts beginning with "#!/usr/bin/perl", I got complaints about the syntax of the perl scripts, because bash was trying to interpret them.

After Web searching and reading, my next try was to make /usr/bin/perl be this:

#!/usr/bin/perlX exec '/path/to/perlY', @ARGV if ( -x "/path/to/perlY" ); exec '/usr/bin/perlX', @ARGV;

This isn't right, either. I tried some code from the 'perlrun' doc, but no luck. I must be missing something small (not much of a perl coder). It seems this must have been done already, but what I find searching online is things that are similar to my case, but not the same. Any ideas? Thanks!

(We install lots of third-party code, and so do our users, and it's not feasible for us to require that all "#!/usr/bin/perl" scripts on our system either have a different shebang line, or some other line inserted as the scripts' second line.)

Replies are listed 'Best First'.
Re: wrapper script to conditionally exec different perls?
by Corion (Patriarch) on Feb 15, 2012 at 18:21 UTC

    Note that if you have NFS problems, -x "/path/to/perlY" may take a really long time, so doing this check for every invocation of a program that expects to be run by perl may be confusing to users.

    How did your second approach fail, and why? I'm not exactly sure what error conditions there are that would make it fail. Maybe you need to add the invoked script name $0 after perlX in the command line, unless the argument is -e? You might need to (re)implement a Perl option parser for that, mostly from perlrun to mirror that.

      I bet most kernels don't support having a #! line in a script that points to another script that has another #! that points to another thing. You see how that could quickly get rather convoluted.

      Unfortunately, when I try to do that myself to test it, rather than a nice clear error message telling me not to be such a clever prick, I get errors that look like my Perl script is being interpreted by something other than perl but I am, so far, at a loss as to what that other thing is.

      When I ran "strace script" to figure out what other thing is trying to interpret my Perl code, I got "exec: Exec format error" from strace.

      So, one answer for the OP is to write this simple wrapper in a compiled language so that the wrapper doesn't need a #! of its own.

      - tye        

        Tye proposed using a compiled language for the wrapper program. Indeed, a simple C program performs the desired wrapping perfectly. Thanks much!

        I suspect my problem had to do with something being run via "sh -c", even when my wrapper was a perl script. I got the same "exec: format error" that Tye did when strace'ing my script.

        most kernels don't support having a #! line in a script that points to another script

        While this is correct in general, it's maybe worth pointing out that newer versions of the Linux kernel (as of 2.6.27, IIRC) can actually handle this fine (finally!).   I.e., things like these do in fact work:

        ---  perl-wrapper  ---

        #!/usr/bin/perl my $choice = splice @ARGV,1,1; exec '/usr/local/perl/5.14.1/bin/perl', @ARGV if $choice == 14; exec '/usr/local/perl/5.12.3/bin/perl', @ARGV if $choice == 12; exec '/usr/local/perl/5.10.1/bin/perl', @ARGV if $choice == 10; exec '/usr/local/perl/5.8.8/bin/perl', @ARGV;

        ---  wrap-test.pl  ---

        #!/home/eliya/tmp/perl-wrapper print "$] -- @ARGV\n";
        $ ./wrap-test.pl 14 foo bar 5.014001 -- foo bar $ ./wrap-test.pl 12 foo bar 5.012003 -- foo bar $ ./wrap-test.pl 10 foo bar 5.010001 -- foo bar $ ./wrap-test.pl 99 foo bar 5.008008 -- foo bar

        (the technique to use the first arg as version selector is of course just for demo purposes)

        P.S.: side note for anyone playing with this: make sure you have the string "perl" in the shebang line, or else you'll get surprising effects like endless exec loops... — see perlrun for why (search for the word "bizarre").

      Thanks. I was too lazy to specify what went wrong when I replaced the bash "exec" script (meant to be /usr/bin/perl; let's call it "mywrapper") with a perl "exec" script. Here's what happened.

      "mywrapper -v" produced the desired output, showing that the correct perl binary was invoked (perlY if perlY available, else perlX).

      helloworld.pl is this:

      #!/path/to/mywrapper use strict; print "Hello World!";

      "/path/to/mywrapper helloworld.pl" caused helloworld.pl to run properly.

      But executing helloworld.pl yields

      line 3: use: command not found line 5: print: command not found

Re: wrapper script to conditionally exec different perls?
by Anonymous Monk on Feb 15, 2012 at 18:42 UTC

    I want to replace /usr/bin/perl

    FWIW, AFAIK, that could break several RHEL tools

    (We install lots of third-party code, and so do our users, and it's not feasible for us to require that all "#!/usr/bin/perl" scripts on our system either have a different shebang line, or some other line inserted as the scripts' second line.)

    If you install scripts using MakeMaker, if you use

     perl Makefile.PL 
    make
    make test
    make install

    the shebang gets rewritten to use the invoking perl

Re: wrapper script to conditionally exec different perls?
by stackspace (Initiate) on Feb 15, 2012 at 18:47 UTC

    Doh! I meant to say that /usr/bin/perl is *ahead* of /path/to/perlY (which is in /usr/local) in the shell $PATH, and that's how we want it.

      ??#!/usr/bin/env perl ...