The Perl manual describes a totally devious construct that will work under any of csh, sh, or Perl, such as the following:
eval '(exit $?0)' && eval 'exec perl -wS $0 ${1+"$@"}'
& eval 'exec /usr/bin/perl -wS $0 $argv:q'
if $running_under_some_shell;
Devious indeed... can someone please explain in detail how this works?
The idea is that those three lines do 3 different things if they're evaluated in a standard Bourne shell (sh), a C shell (csh), or Perl. This hack is only needed on systems that don't support specifying an interpreter name using a #!
line at the start of a script. If you execute a Perl script beginning with those 3 lines as a shell script, the shell will launch the Perl interpreter, passing it the script's filename and the command line arguments.
In Perl, the three lines form one statement, terminated by the ;
, of the form
eval '...' && eval '...' & eval '...' if $running_under_some_shell;
Since the script just started, $running_under_some_shell
is undef
, which is false, and the evals are never executed. It's a no-op.
The devious part is that $?0
is parsed differently in sh versus csh. In sh, that means $?
(the exit status of the last command) followed by 0. Since there is no previous command, $?
will be 0, so $?0
evaluates to 00
. In csh, $?0
is a special variable that is 1 if the current input filename is known, or 0 if it isn't. Since the shell is reading these lines from a script, $?0
will be 1.
Therefore, in sh, eval '(exit $?0)'
means eval '(exit 00)'
, and in csh it means eval '(exit 1)'
. The parens indicate that the exit command should be evaluated in a subshell.
Both sh and csh understand &&
to mean "execute the previous command, then execute the following command only if the previous command exited 0". So only sh will execute eval 'exec perl -wS $0 ${1+"$@"}'
. csh will proceed to the next line.
csh will ignore "& " at the beginning of a line. (I'm not sure exactly what that means to csh. Its purpose is to make this a single expression from Perl's point of view.) csh then proceeds to evaluate eval 'exec /usr/bin/perl -wS $0 $argv:q'
.
These two command lines are quite similar. exec perl
means to replace the current process by launching a copy of perl
. -wS
means the same as -w
(enable warnings) and -S
(look for the specified script in $PATH
). $0
is the filename of the script. Finally both ${1+"$@"}
and $argv:q
produce a copy of the current command line arguments (in sh and csh, respectively).
It uses ${1+"$@"}
instead of the more usual "$@"
to work around a bug in some ancient version of the Bourne shell. They mean the same thing. You can read the details in Bennett Todd's explanation (copied in gbacon's answer).
From Tom Christiansen's collection Far More Than Everything You've Ever Wanted to Know About …:
Why we use eval 'exec perl $0 -S ${1+"$@"}'
Newsgroups: comp.lang.tcl,comp.unix.shell
From: bet@ritz.mordor.com (Bennett Todd)
Subject: Re: "$@"
versus ${1+"$@"}
Followup-To: comp.unix.shell
Date: Tue, 26 Sep 1995 14:35:45 GMT
Message-ID: <DFIoJL.934@ritz.mordor.com>
(This isn't really a TCL question; it's a Bourne Shell question; so I've
cross-posted, and set followups, to comp.unix.shell).
Once upon a time (or so the story goes) there was a Bourne Shell somewhere
which offered two choices for interpolating the whole command-line. The
simplest was $*
, which just borfed in all the args, losing any quoting that
had protected internal whitespace. It also offered "$@"
, to protect
whitespace. Now the icko bit is how "$@"
was implemented. In this early
shell, the two-character sequence $@
would interpolate as
$1" "$2" "$3" "$4" ... $n
so that when you added the surrounding quotes, it finished quoting the whole
schmeer. Cute, cute, too cute.... Now consider what the correct usage
"$@"
will expand to if there are no args:
""
That's the empty string — a single argument of length zero. That's not
the same as no args at all. So, someone came up with a clever application of
another Bourne Shell feature, conditional interpolation. The idiom
${varname+value}
expands to value
if varname
is set, and nothing otherwise. Thus the
idiom under discussion
${1+"$@"}
means exactly, precisely the same as a simple
"$@"
without that ancient, extremely weird bug.
So now the question: what shells had that bug? Are there any shells
shipped with any even vaguely recent OS that included it?
--
-Bennett
bet@mordor.com
http://www.mordor.com/bet/