Tuesday, October 04, 2005

Sneaky bat files

The ActiveState Perl implementation contains some examples of imbedding Perl inside a Windows .bat file. Of course if we wanted to do that sort of thing in a Unix shell we would probably use a 'here' document, but bat files do not have that level of sophistication.

The structure of these bat files is as follows:

@rem = '--*-Perl-*--

perl -x -S "%0" %*

goto endofperl
@rem ';

#!/usr/local/bin/perl -w
#line 9
... Perl code ...
__END__
:endofperl

The "%0" is the name of the current bat file, and %* is a list of the arguments passed into the bat file (like $0 and $* in the Korn shell and Bash).

So we are executing the bat file as if it was a perl script! This is where the –x option to perl comes in. Check perlrun, -x means that the perl code starts at the #! line, and preceding lines are to be ignored. The –S option means that %path% is used to find the perl script. There is a problem with this.

The quotes around "%0" would appear to protect us from imbedded spaces in the filename, but they do not. Paths with imbedded spaces do not work with this method. Strangely, this works if the quotes are omitted. The implication is that most of the ActivePerl bat files do not work with imbedded spaces in path names.

(The pugs Windows installation has a similar problem)

The –S option is not really needed. This is because to run the bat file we need to specify a hierarchic or full pathname anyway (Explorer makes the script's directory its current directory).

The sneaky part is the use of @rem.

The bat file starts with a non-echo'ed remark:

@rem = '--*-Perl-*--
… bat file stuff …
@rem ';

The idea here is that, by a happy coincidence, @rem comment prefix in a bat file is a valid perl command, to assign to an array called @rem. The assignment ends on the final comment, with a close quote (notice that the first @rem has no close quote).
I can only guess that this is in the bat files for historical reasons, since the –x option to perl makes this trickery unnecessary. The goto (whatever that is) is required to leap over the perl code.

The #line directive sets the line number when error reporting, or for the die statement. This means that we can set the line numbers relative to the start of our bat file, rather than the start of the perl code.

Finally, the __END__ label tells perl that this is the end of the script, and ignores any junk that follows.

No comments: