Could someone explain this shebang line which uses sh and then does exec perl?

The idea is that the eval command is valid in both shell and in Perl, with the difference that the newline terminates the command in shell, but not in Perl. Instead, Perl reads the following line, which adds the condition if 0, effectively negating the whole command.

(Perl supports a few of such "backwards" structures for shorthand, e.g. you can write next if $_ == 0 instead of if ($_ == 0) { next }, or print for @a instead of for (@a) { print }.)

If the script is started by a shell, the shell processes the eval, and replaces it with the Perl interpreter, giving it the script name ($0) and it's arguments ($@) as parameters.

Then Perl runs, reads the eval, skips it (because of the if 0), and then goes on to execute the rest of the script.


That's how it should work. In practice, you get the error because of two things: 1) Perl reads the hashbang line itself, and 2) the way Linux processes the hashbang lines.

When Perl runs a script with a hashbang, it doesn't really take it just as a comment. Instead, it interprets any options to Perl given in the hashbang (you can have #!/usr/bin/perl -Wln etc.) but it also checks the interpreter and executes it if the script isn't supposed to be run by Perl!

Try e.g. this:

$ cat > hello.sh
#!/bin/bash
echo $BASH_VERSION 
$ perl hello.sh
4.4.12(1)-release

That actually runs Bash.

So, the comment #perl is there to tell Perl that yes, this is actually supposed to be run by Perl, so that it doesn't start a shell again.

However, Linux gives everything after the interpreter name as a single argument, so when you run the script, it runs /bin/sh, with the two arguments -- # perl, to stop looping, and scriptname.pl. The first one starts with a dash, but isn't exactly --, so both Dash and Bash try to interpret it as options. It's not a valid one, so you get an error, similarly as if you tried to run bash ---, or bash "-- #perl".

On other systems that split the arguments in the hashbang line, -- #perl would give the shell --, #perl, and it would try to look for a file called #perl. This again wouldn't work. But apparently there are/have been some systems that take # signs as comment markers in the #! line, and/or only pass on the first argument. (See Sven Mascheck's page on the matter.) On those systems, it might work.


A somewhat better working one is given in perlrun (adapted):

#!/usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
    if $running_under_some_shell;
print("Perl $^V\n");

Running that with bash ./script.pl actually runs Perl and prints (e.g.) Perl v5.24.1. ($running_under_some_shell is just an undefined variable, which defaults to falsy. if 0 would be cleaner but not as descriptive.)


Like it says on your quote, all of this is only required on ancient systems where #! doesn't work properly. In some, it's not supported at all, and asking a shell to run a non-binary always runs it in the shell. In others, there's a limit on the path length in the hashbang line, so #!/really/veeeery/long/path/to/perl wouldn't work.

Just put the #!/usr/bin/perl hashbang there on any modern system.