Multiline shebang

From Rosetta Code
Multiline shebang is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Simple shebangs can help with scripting, e.g., #!/usr/bin/env python at the top of a Python script will allow it to be run in a terminal as "./".

Occasionally, a more complex shebang line is needed. For example, some languages do not include the program name in ARGV; a multiline shebang can reorder the arguments so that the program name is included in ARGV.

The syntax for a multiline shebang is complicated. The shebang lines must be simultaneously commented away from the main language and revealed to some shell (perhaps Bash) so that they can be executed. In other words, Polyglots.

Warning: Using a multiline shebang of the form #!/bin/sh ... exec ... !# will set the code's mimetype to text/x-shellscript, which creates problems such as Emacs treating the file as a shell script, no matter which language and file extension it really uses.

See also
  • Native shebang - where the "program loaded" is of the actual native task language.


Translation of: C
sed -n -e '7,$p' < "$0" > mulshbang.adb
gnatmake -q mulshbang
./mulshbang $*
rm mulshbang*
with Ada.Text_IO, Ada.Command_Line; -- first line of Ada program
procedure Mulshbang is
use Ada.Text_IO;
Put_Line("Name: " & Ada.Command_Line.Command_Name);
for I in 1 .. Ada.Command_Line.Argument_Count loop
Put_Line(" Arg" & Integer'Image(I) & ": " &
end loop;
end Mulshbang;
Name: ./mulshbang
>./adamulshbang one two three
Name: ./mulshbang
  Arg 1: one
  Arg 2: two
  Arg 3: three


sed -n -e '7,$p' < "$0" | /usr/bin/gcc -x c -o "$0.$$.out" -
$0.$$.out "$0" "[email protected]"
rm $0.$$.out
exit $STATUS
#include <stdio.h>

int main(int argc, char **argv)
  int i;
  for (i = 0; i < argc; i++)
    printf("argv[%d] -> %s\n", i, argv[i]);
  return 0;

Test runs:

$ ./cmulshbang.c
argv[0] -> ./cmulshbang.c.4062.out
argv[1] -> ./cmulshbang.c
$ ./cmulshbang.c 1
argv[0] -> ./cmulshbang.c.4071.out
argv[1] -> ./cmulshbang.c
argv[2] -> 1
$ ./cmulshbang.c 1 2
argv[0] -> ./cmulshbang.c.4080.out
argv[1] -> ./cmulshbang.c
argv[2] -> 1
argv[3] -> 2

Student exercise: use a stable filename for the executable, e.g. "$0.out". Do not remove it, and only recompile it if the script's timestamp is newer than that of the executable.


The namespace = basename = filename minus the extension must be passed as a value to Clojure's -m flag.

":";exec clj -m `basename $0 .clj` $0 ${1+"[email protected]"}

Alternate shebang, using the Leiningen 'exec' plugin:

":";exec lein exec $0 ${1+"[email protected]"}

Common Lisp[edit]

Works with: CLISP

Here, the script name is passed once to CLISP and once to ext:*args*, which normally omits it.

exec clisp -q -q $0 $0 ${1+"[email protected]"}


E uses only “#” for line comments, like the shell, so there is no straightforward answer. We can abuse the fact that “>” is also a line comment to achieve this effect. Note that a “>” line comment should ordinarily only occur as part of Updoc (test/documentation) text, so this is not good practice.

In this example, we are including the command name itself in the argument list, which would ordinarily not include it.

>/dev/null; exec rune $0 $0 ${1+"[email protected]"}
println(`I was called as ${interp.getArgs()[0]}.`)

Emacs Lisp[edit]

:;exec emacs -batch -l $0 -f main $*



#!/usr/bin/env escript
main(_) -> io:format("Hello World!~n", []).

This works fine when the module is run by itself with dot slash:

$ ./hello.erl 
Hello World!

But when another Erlang module tries to import the code, or you try to compile manually in erl, you get a syntax error.

$ erl
Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8.4 (abort with ^G)
1> c(hello).
./hello.erl:1: syntax error before: '#'
./hello.erl:4: no module definition


F# scripts may be run with dot-slash notation using the following multiline shebang:

#light (*
exec fsharpi --exec "$0" --quiet
let main = printfn "Hello World"

However, if a script has any dependencies that need to be compiled in, the fsharpi interpreter will not understand how to import them. This means dot-slashing is no longer viable, and the script must be compiled in order to run properly. The shebang can stay, but it is best to remove it, to make clear to users that the script should not be dot-slashed.


Factor no longer requires a space after #! as of v0.95.

#!/usr/bin/env factor -script


We can use Gforth's (non-ANS standard) support for shebangs and the '#' number prefix to make Gforth skip over the shebang without interfering with shell script interpretation.

#! /bin/sh
#0 [IF] \ lines below read by shell but ignored by Gforth
exec gforth \
-m 256M \
-d 16M \
"$0" "[email protected]"
.( hello world) CR BYE


script_dir="$(cd $(dirname $0) >/dev/null; pwd -P)"
if [ -z "${GROOVY_HOME}" ]
echo 'GROOVY_HOME must be defined.' >&2
exit 1
CLASSPATH="${script_dir}" "${GROOVY_HOME}/bin/groovy" -e "$(sed -e '1,/^!#$/d' $0)" "${@:1}"
println 'aoeu'


sed -n -e '12,$p' < "$0" > ttmmpp.go
go build ttmmpp.go
rm ttmmpp.go
mv ttmmpp $binfile
$binfile "[email protected]"
rm $binfile
exit $STATUS
######## Go Code start on line 12
package main
import (
func main() {
for i, x := range os.Args {
if i == 0 {
fmt.Printf("This program is named %s.\n", x)
} else {
fmt.Printf("the argument #%d is %s\n", i, x)


Translation of: C
sed -n -e '7,$p' < "$0" > $0.$$.hs
ghc $0.$$.hs > /dev/null
./$0.$$ "$0" "[email protected]"
rm $0.$$*
import Text.Printf
import System.Environment
main :: IO ()
main = getArgs >>= mapM_ (uncurry $ printf "argv[%d] -> %s\n") . zip ([0..] :: [Int])
$ ./multibang.hs
argv[0] -> ./multibang.hs
$ ./multibang.hs Goodbye, World!
argv[0] -> ./multibang.hs
argv[1] -> Goodbye,
argv[2] -> World!

Or you can 'cheat' by ignoring Bash's complaints about Haskell comments (gives the exact same output as above):

{- 2> /dev/null
exec runghc $0 $0 [email protected]

import Text.Printf
import System.Environment
main :: IO ()
main = getArgs >>= mapM_ (uncurry $ printf "argv[%d] -> %s\n") . zip ([0..] :: [Int])


Assuming this task is asking for a mix of unix shell commands and J, and also that the J binary directory is listed in $PATH

# 0 :0
echo unix shell commands go here
echo presumably this will condition the environment
echo for example:
cd working-directory
echo or maybe you want to modify $PATH, ... whatever...
echo then start up J:
exec jconsole "$0" "[email protected]"
NB. exit on error
onfail_z_=:3 :0
1!:2&2 ARGV
1!:2&2]13!:12'' NB. display error message
2!:55>:13!:11'' NB. exit with 1 origin error number
9!:27 'onfail 1'
NB. and then the rest of the file is J
echo 'hi!'
echo 'your command line arguments were:'
echo ARGV
echo p:i. 3 4
exit 0


The #!/bin/sh line is interpreted by J as a verb train with no arguments - in other words, it is ignored.

The # 0 :0 line is interpreted by shell as a comment and by J as the beginning of a multiline "hereis" script which basically ignores everything up to the lone right parenthesis.

So then it's just regular shell script up until the line where we turn control over to J. On that line, we use exec (so that the shell process does not hang around, waiting for J to finish - J takes over the current process). And we pass any shell script command line arguments on to J.

On the J side of the fence, we presumably want this code to behave like a normal unix module, so we need to override J's default behavior (which is to provide the J command line). 9!:29]1[9!:27'2!:55]1 is a bit of magic that accomplishes that: it stacks a command to exit with exit code 1 to be executed when we reach the command line. So any errors will terminate the program.

Next, we run the system J profile so that we have all of the standard stuff that that provides. (Or leave this out if that's what you want.)

Finally we do some J stuff and then exit. If everything goes right, the command line exit we stacked earlier just gets ignored.

Here's a variant where the shell script tests J's exit code and does something different based on success or failure.

# 0 :0
echo unix shell commands go here
echo presumably this will condition the environment
echo for example:
cd working-directory
echo or maybe you want to modify $PATH, ... whatever...
echo then start up J:
if jconsole -jprofile "$0" "[email protected]"; then
echo success
echo failure
exit $?
9!:29]1[9!:27'2!:55]1' NB. exit on error
(3 :'0!:0 y')<BINPATH,'/profile.ijs'
NB. and then the rest of the file is J
echo 'hi!'
echo 'your command line arguments were:'
echo ARGV
echo p:i. 3 4
exit 0

The exit $? line tells the shell interpreter to ignore the J part of the file, and the $? reuses J's exit code as the exit code from the shell instance.

Note that we've left off the onfail handler within J, and just used a minimal definition to give us a non-zero exit code for the error case. Mostly, the assumption here would be that the error message would not be interesting, and that any failure should be handled by a retry. But you could replace the exit on error line here with the full definition and 9!: preparatory bit from the previous example and you could also of course change the 1!:2&2 lines (1!:2&2 is the "low-level" write to stdout mechanism for J - and, yes, those numbers are part of the definition of the language - or at least the "Foreigns" part of the language - note that ultimately all computer languages resolve to things which can be thought of as numbers or sequences of numbers, though some people will vigorously assert other things).


Unlike Octave, MATLAB has no built-in support for shebangs. In fact, several tricks are required to even approximate a shebang, due to the byzantine way that MATLAB structures scripts and function files.


matlab -nojvm -nodisplay -nosplash -r "varargin = regexp('${1+"[email protected]"}', ' ', 'split'); nvarargin = length(varargin); run('$1'); exit" | tail -n +16


'shmatlab'% $0 ${1+"[email protected]"}
for i = 1:nvarargin


$ ./args.m a b c


ocamlc hates shebangs, so much trickery is needed. The number of underscores in the dummy kkkk identifier corresponds to the number of bash strings in the shebang. Thus, core library .cma files can be specified this way in interpreted mode, though accessing other OCaml scripts requires compiling them first, and referencing the .cmo's here.

if true then ignore begin let kkkk _ _ _ _ = 0 in kkkk
"exec" "ocaml" "$0" "[email protected]" + let fi = 0 and exit _ _ = 0 in if false
then exit
true else 0
let main = print_endline "Hello World!"


$ head -n 2
if true then ignore begin let kkkk _ _ _ _ _ _ = 0 in kkkk
"exec" "ocaml" "$0" "unix.cma" "graphics.cma" "[email protected]" + let fi = 0 and exit _ _ = 0 in if false
$ ocaml
Hello World!
$ /bin/bash
Hello World!
$ ocamlc -o she.byte
$ ./she.byte
Hello World!
$ ocamlopt -o she.opt
$ ./she.opt
Hello World!


The PARI equivalent to a multiline shebang is a collection of GP; lines:

GP;addhelp(GP_name, "GP_name(n): Computes the foo of bar(n).");

These commands are passed to GP when invoked by gp2c.


From perldoc perlrun, the following is supposed to find perl one way or another under sh, csh or perl.

eval '(exit $?0)' && eval 'exec perl -wS $0 ${1+"[email protected]"}'
& eval 'exec /usr/bin/perl -wS $0 $argv:q'
if $running_under_some_shell;

Perl 6[edit]

use MONKEY; EVAL '(exit $?0)' && EVAL 'exec perl6 $0 ${1+"[email protected]"}'
& EVAL 'exec perl6 $0 $argv:q'
if 0;


We can use a multi-line comment #{ ... }# to hide the shell commands from Lisp. The opening #{ in turn is a coment for the shell.

exec pil $0 foo bar
# }#
# Lisp code
(println (cadr (file)) (opt) (opt))
$ ./myScript
"myScript" "foo" "bar"


we use a multiline comment to hide the shell command from pike, and we can use a preprocessor directive to hide the comment begin from the shell.

#define foo foo /*
exec pike $0 hello world

int main(int argc, array argv)
write("%O\n", argv);
({ /* 3 elements */


This style of shebang would also work with other languages that use double dashes for comments, though most of them (Lua, Haskell) already support traditional #!... shebangs.

--() { :; }; exec psql -f "$0"
SELECT 'Hello World!';


We can use multiple strings to make the shell commands do nothing from Python (actually they become the module docstring.).

"exec" "python" "$0"
print "Hello World"
$ ./myScript
Hello World

Control structures (if/for/etc.) can't be quoted, but one can use the following to embed any script:

"true" '''\'
if [ -L $0 ]; then
exec "$interpreter" "[email protected]"
exit 127

__doc__ = """module docstring"""
print "Hello World"

Here we use a) the code '''\' translates to \ in shell, but opens a multi-line string in Python; b) the true command ignores its argument, c) we always exit before the ending ''' so that the shell interpreter never reads it. Also, remember to set any docstrings by assigning to __doc__ since the docstring is already used for the shell script.


#| -*- scheme -*-
# this is sh code
echo running "$0", passing it into itself as an argument
exec racket -tm "$0" "$0"
#lang racket
(provide main)
(define (main arg)
(printf "argument: ~a\nexecuted as: ~a\n"
arg (find-system-path 'exec-file)))


One can use a single-line shebang, like #!/usr/bin/env ruby, and use Kernel#system or `backquotes` to run any extra shell commands. A multi-line shebang is possible, but not necessary.

This script works both ways: either /bin/sh script.rb or ruby script.rb would run multiple lines of shell commands, and then start Ruby.

# Insert shell code here!
printf '%s\n' "Shell running $0"
for arg do
printf '  %s\n' "\${$i}: $arg"
i=`expr $i + 1`
# Switch from shell to Ruby.
exec ${RUBY-ruby} -x "$0" --coming-from-sh "[email protected]"
ARGV[0] == "--coming-from-sh" or exec "/bin/sh", $0, *ARGV
# Insert Ruby code here!
puts "Ruby running #$0"
ARGV.each_with_index do |arg, i|
puts " ARGV[#{i}]: #{arg}"

When running /bin/sh scratch.rb, the shell:

  1. ignores #!/bin/sh, because it is a comment.
  2. runs multiple lines of shell code.
  3. executes ruby -x; user can set RUBY environment variable to pick different Ruby, like RUBY=ruby19 or RUBY=jruby.

ruby -x skips every line until the first Ruby shebang. This line must start with "#!" and must contain "ruby". (So "#!ruby" is the shortest shebang to work.)

When running ruby scratch.rb (without -x option), Ruby notices that the first line "#!/bin/sh" is a foreign shebang.

  • Ruby 1.8 then interprets this shebang and executes /bin/sh.
  • Ruby 1.9 then assumes -x option and skips to the first Ruby shebang. The script is not --coming-from-sh, so it executes /bin/sh.


The scala(1) interpreter parses a header section. The scalac compiler does not.

scala $0 $@
def fact(n : Int) : Int = {
var i = n ;
var a = 1 ;
while (i > 0) {
a = a*i ;
i -= 1 ;
return a ;
println("fact(5) = " + fact(5));


Works with: Chicken Scheme
#!/usr/bin/env csi -ss


#`(if running under some shell) {
eval 'exec /usr/bin/sidef $0 ${1+"[email protected]"} "world"'
say "Hello, #{ARGV[0]}!"
$ ./script.sf
Hello, world!

$ ./script.sf Sidef
Hello, Sidef!

$ sidef script.sf RosettaCode
Hello, RosettaCode!


"exec" "gst" "-f" "$0" "$0" "[email protected]"

SQL PL[edit]

Works with: Db2 LUW

Based on the Postgres shebang, it works the same with IBM Db2.

--() { :; }; exec db2 -txf "$0"
GET instance;
CONNECT TO sample;
SELECT 'Hello' FROM sysibm.sysdummy1;


$ ./myScript 

 The current database manager instance is:  db2inst1

   Database Connection Information

 Database server        = DB2/LINUXX8664 11.1.1
 SQL authorization ID   = DB2INST1
 Local database alias   = SAMPLE



The db2profile should be loaded before executing the 'db2' command (. ~db2inst1/sqllib/db2profile).

The options used in the example are: t - delimited by semi colon, x - Suppress printing of column headings, f - Read from input file. For other options, you can execute 'db2 ? options', and change the shebang.


It is normal to use a line like this:

#!/usr/bin/env tclsh

But in cases where that is not enough perhaps because it needs some logic to locate the Tcl interpreter to use the differences in the way Tcl and the Bourne shell interpret end-of-line backslashes in comments can be used:

# Next line is comment in Tcl, but not in sh... \
exec tclsh "$0" ${1+"[email protected]"}

Additional complexity can be added so long as the lines for the shell are commented in a Tcl sense.


sed -n -e '4,$p' < "$0" | /usr/bin/txr -B - "$0" "[email protected]"
exit $?
@(next :args)

Test run:

$ ./multilineshebang.txr
$ ./multilineshebang.txr 1
$ ./multilineshebang.txr 1 2 3


zkl has a variant of the here doc that means ignore the doc, as in "#if 0" but more so. But that doesn't mean a shell has to ignore it.

File foo.zkl (the .zkl extension is needed):

echo "A shell script in a zkl program ($0)"
echo "Now run zkl <this file> with Hello World as args"
zkl $0 Hello World!
println("The shell script says ",vm.arglist.concat(" "));
$ ./foo.zkl 
A shell script in a zkl program (./foo.zkl)
Now run zkl <this file> with Hello World as args
The shell script says Hello World!