Solve /bin/sh: 0: Illegal option --
The simplest solution to avoid the error is to have only one argument to the sh
call (on some systems) and also seems to be to add the option -x
to the perl call[d]. And I say seems because it depends on the system, kernel, shell and perl used to call the script.
So, better use something like:
#!/bin/sh --
eval 'exec perl -x -S $0 ${1+"$@"}'
#!/usr/bin/perl
print("Perl $^V\n");
In the script above the only perl executed command is to print the version.
Short Description
The only reason to use something like this is explained in the book (page 21):
Finally, if you are unfortunate enough to be on an ancient Unix system that doesn’t support the magic #! line, or if the path to your interpreter is longer than 32 characters (a built-in limit on many systems), you may be able to work around it ......
It is an ugly and convoluted way to replace the #!/usr/bin/perl
call.
In the same page 21 is the script you posted. The next script about this issue, is in page 577 (keep reading)
Note: The inclusion of the perl
name (word) in the first line is only needed if the script is at any time going to be called as perl ./script
. In that case, that word will stop perl from looping. In fact, the second example from the book (page 577) is:
#!/bin/sh -- # -*- perl -*- -p
eval 'exec perl -S $0 ${1+"$@"}'
if 0;
In which the comment part of (# -- perl -- -p) is explained in the book as being interpreted by perl (and mostly ignored).
However, that is assuming that the shell call #!/bin/sh
will allow more than one (--
) argument. That is not true in some systems. It is better moved to the next line (if required for emacs) and/or removed:
#!/bin/sh -- #
# -*- perl -*- -p
eval 'exec perl -S $0 ${1+"$@"}'
if 0;
A portable way to call perl
To understand the basics of it, the first concept to understand is that the script *may** be processed twice, once by a shell, then by the interpreter called by the shell, in this case, perl.
Understand that the script may be called as sh script
, bash ./script
, simply as ./script
, or, if the path include the present working directory, as script
and even as /usr/bin/perl script
or as perl script
. In DOS, Unix, Windows, Linux, etc.
How to make a call to perl that work in all systems?
Shell side
This code (cat ./script):
#!/bin/sh --
eval 'exec perl -S $0 ${1+"$@"}'
if 0;
Would be interpreted by almost any shell (either called as ./script
or shell ./script
as follows:
The kernel would read the first line, identify it as a shebang and start the shell executable that is placed in the /bin/sh
file (or link). Also, the kernel will accept and process --
as an argument meaning "end of options"[a][b]. That is the line:
#!/bin/sh --
The shell (assuming there is a shell in /bin/sh
) will read and execute the first (non-commented) line:
eval 'exec perl -S $0 ${1+"$@"}'
The first word (eval) will convert the list of arguments into a command line to execute removing one layer of quoting. That is, it will be converted to this command line:
exec perl -S $0 ${1+"$@"}
The first word of this (new) command line (that is: exec
) will tell the kernel to replace the running process with the executable perl
(searched in the PATH) and the arguments that result from expanding the rest of the command line.
The first argument (-S
) will tell perl to also search for the executable script that is named in the next argument ($0
). The $0
argument is (usually) the name of the previously loaded shell script (containing the code you posted aka ./script
).
The next argument: ${1+"$@"}
means: if arg $1
exists and is not null (${1+...}
) replace this whole argument with the result of the expansion of "$@"
. The expansion of "$@"
is the list of all the arguments to the loaded script.
You can see what arguments are given at this command line by testing:
#!/bin/sh -
eval 'exec /usr/bin/printf "<%s> " -S $0 ${1+"$@"}'
Calling this script:
$ ./script one two t33 f44 "f55 s66"
<-S> <./script> <one> <two> <t33> <f44> <f55 s66>
So, that line in the original script will call perl (equivalently) as:
$ perl -S ./script one two t33 f44 "f55 s66"
That call to perl replaces the shell script being executed (exec).
Perl side
It is then perl that loads the script and re-interprets its content.
Comments in Perl also start with a #
Thus, the first line (shebang) is a comment to Perl.
The second line: eval 'exec perl -S $0 ${1+"$@"}'
would be interpreted by perl (as eval
is a valid Perl command) if the next line if 0;
where not there. This third line makes Perl not execute the second line. A very long way of writing a nop
.
The rest of the script is then interpreted and executed by Perl.
Note that all that construct is a very complex way of writing:
#!/usr/bin/perl
Why is it really needed?
To Set PATH
A "hello world" example similar to and using the -x
perl option as usually recommended in the perldoc site.
Adding the -x call to perl will ensure that perl will itself search for a #!/usr/bin/perl
shebang (which could be many lines below the first line) to start executing the script. This -x
will also make the if 0
superfluous.
#!/bin/sh -
eval 'PATH="/usr/bin:/bin:/usr/local/bin:$PATH";: \
;exec perl -x -S -- "$0" ${1+"$@"};#'if 0;
#!/bin/perl -w
# Above is magic header ... real Perl code begins here
use strict;
use warnings;
print "hello world!\n";
That will set a quite more portable PATH to search for the perl executable. More variables could be set to make the call to perl more reliable (if needed).
[d]The -x
perl option will search for the shebang line with a perl name: #!/bin/perl -w
to start interpreting the script from perl. That makes the separation between shell and perl quite more clear.
This script will work if it is called either as:
$ ./script
hello world!
$ sh ./script
hello world!
$ perl ./script
hello world!
but will fail if called from a csh
shell. That is solved by adding eval '(exit $?0)'
as a test and a new exec as this:
#!/bin/sh --
eval '(exit $?0)' && eval 'PATH="/usr/bin:/bin:/usr/local/bin:$PATH";: \
;exec perl -x -S -- "$0" ${1+"$@"};#'if 0;
exec 'exec perl -x -S -- "$0" $argv:q;#'.q
A Magic Header for Starting Perl Scripts
A related long description.
[a]The whole string after the #!/bin/sh
may be interpreted as only one argument. In such case, the whole string -- # perl, to stop looping
could be reported as an error. As it is if bash directly loads the script when executing ./script
(while the running shell is bash). Read more detail in search for "Splitting arguments".
[b]In the very rare circumstances that it is useful to use a --
It is more portable to use a -
instead as explained in this question.