Reputation: 38714
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?
Upvotes: 14
Views: 2058
Reputation: 62109
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).
Upvotes: 27
Reputation: 139631
From Tom Christiansen's collection Far More Than Everything You've Ever Wanted to Know About …:
eval 'exec perl $0 -S ${1+"$@"}'
Newsgroups: comp.lang.tcl,comp.unix.shell
From: [email protected] (Bennett Todd)
Subject: Re:"$@"
versus${1+"$@"}
Followup-To: comp.unix.shell
Date: Tue, 26 Sep 1995 14:35:45 GMT
Message-ID: <[email protected]>(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
ifvarname
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 [email protected] http://www.mordor.com/bet/
Upvotes: 10