Native shebang: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added Wren)
 
(10 intermediate revisions by 8 users not shown)
Line 55: Line 55:
Also note that this ".so" will only be generated if the ".a68" source file
Also note that this ".so" will only be generated if the ".a68" source file
has been touched.
has been touched.
'''File: echo.a68'''<lang algol68>#!/usr/bin/a68g --script #
'''File: echo.a68'''<syntaxhighlight lang="algol68">#!/usr/bin/a68g --script #
# -*- coding: utf-8 -*- #
# -*- coding: utf-8 -*- #


STRING ofs := "";
STRING ofs := "";
FOR i FROM 4 TO argc DO print((ofs, argv(i))); ofs:=" " OD</lang>
FOR i FROM 4 TO argc DO print((ofs, argv(i))); ofs:=" " OD</syntaxhighlight>
'''Test Execution:'''
'''Test Execution:'''
<pre>
<pre>
Line 68: Line 68:
Hello, world!
Hello, world!
</pre>
</pre>

=={{header|Arturo}}==
Arturo is a scripting language and does not compile to a binary.
<syntaxhighlight lang="rebol">#!/usr/bin/env arturo
print "Hello from Arturo!"</syntaxhighlight>

{{out}}

<pre>$> ./native_shebang.art
Hello from Arturo!</pre>


=={{header|C}}==
=={{header|C}}==
Line 76: Line 86:


'''File: script_gcc.c'''
'''File: script_gcc.c'''
<lang c>#!/usr/local/bin/script_gcc.sh
<syntaxhighlight lang="c">#!/usr/local/bin/script_gcc.sh
/* Optional: this C code initially is-being/can-be boot strapped (compiled) using bash script_gcc.sh */
/* Optional: this C code initially is-being/can-be boot strapped (compiled) using bash script_gcc.sh */
#include <errno.h>
#include <errno.h>
Line 200: Line 210:
perror(STRCAT(binpath, ": executable not available", ENDCAT));
perror(STRCAT(binpath, ": executable not available", ENDCAT));
exit(errno);
exit(errno);
}</lang>
}</syntaxhighlight>


'''Test Source File: echo.c'''
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc.c
<syntaxhighlight lang="c">#!/usr/local/bin/script_gcc.c
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
Line 216: Line 226:
putchar('\n');
putchar('\n');
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}</lang>
}</syntaxhighlight>


'''Test Execution:'''
'''Test Execution:'''
Line 229: Line 239:
===2nd version. Pure C, no extra bash script===
===2nd version. Pure C, no extra bash script===
'''File: script_gcc.c'''
'''File: script_gcc.c'''
<lang c>/*
<syntaxhighlight lang="c">/*
* rosettacode.org: Native shebang
* rosettacode.org: Native shebang
*
*
Line 400: Line 410:
return !fprintf(stderr, "%s : executable not available\n", exec_path) | ENOENT;
return !fprintf(stderr, "%s : executable not available\n", exec_path) | ENOENT;
}
}
</syntaxhighlight>
</lang>


'''Test Source File: echo.c'''
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc
<syntaxhighlight lang="c">#!/usr/local/bin/script_gcc
/*
/*
* note, any additional libs or include paths would have params added after
* note, any additional libs or include paths would have params added after
Line 421: Line 431:
putchar('\n');
putchar('\n');
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}</lang>
}</syntaxhighlight>


'''Test Execution:'''
'''Test Execution:'''
Line 431: Line 441:
Hello, world!
Hello, world!
</pre>
</pre>

=={{header|Forth}}==
Such functionality can be added easily by the following definition:
<syntaxhighlight lang="text">: #! [COMPILE] \ ; immediate</syntaxhighlight>
Some Forth compilers - like 4tH or Gforth - support this functionality out of the box.
This example program works as advertised:
<syntaxhighlight lang="text">#! /usr/local/bin/4th cxq
argn 1 ?do i args type space loop cr</syntaxhighlight>


=={{header|Free Pascal}}==
=={{header|Free Pascal}}==
Since FPC (FreePascal compiler) version 2.6.0 the distribution – e.g. the Debian or FreeBSD packages <tt>fpc-utils</tt> – come with the program <tt>instantfpc(1)</tt>, or <tt>ifpc(1)</tt> for short.
Since FPC (FreePascal compiler) version 2.6.0 the distribution – e.g. the Debian or FreeBSD packages <tt>fpc-utils</tt> – come with the program <tt>instantfpc(1)</tt>, or <tt>ifpc(1)</tt> for short.
The program fulfills this task’s specifications, plus other goodies.
The program fulfills this task’s specifications, plus other goodies.
The sources are available in [https://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/utils/instantfpc/instantfpc.pas?view=markup <tt>trunk/utils/instantfpc/instantfpc.pas</tt>] and are not repeated here.
The sources are available in [https://gitlab.com/freepascal.org/fpc/source/-/blob/release_3_2_0/utils/instantfpc/instantfpc.pas <tt>trunk/utils/instantfpc/instantfpc.pas</tt>] and are not repeated here.
See the [https://wiki.freepascal.org/InstantFPC FreePascal wiki for <tt>ifpc</tt> usage].
See the [https://wiki.freepascal.org/InstantFPC FreePascal wiki for <tt>ifpc</tt> usage].


Line 446: Line 464:


The following works fine on Ubuntu 16.04.
The following works fine on Ubuntu 16.04.
<lang go>///usr/bin/env go run echo.go "$@"; exit
<syntaxhighlight lang="go">///usr/bin/env go run echo.go "$@"; exit
package main
package main


Line 458: Line 476:
fmt.Println(os.Args[1])
fmt.Println(os.Args[1])
}
}
}</lang>
}</syntaxhighlight>


{{out}}
{{out}}
Line 469: Line 487:
As no compiling versions of J are currently available, the binaries are trivially empty and we shall store them in the empty path. We shall use /usr/local/bin/ijconsole (which was compiled using a C compiler) as the J interpreter, and <code>echo each ARGV</code> as our sample code:
As no compiling versions of J are currently available, the binaries are trivially empty and we shall store them in the empty path. We shall use /usr/local/bin/ijconsole (which was compiled using a C compiler) as the J interpreter, and <code>echo each ARGV</code> as our sample code:


<lang J>#!/usr/local/bin/ijconsole
<syntaxhighlight lang="j">#!/usr/local/bin/ijconsole
echo each ARGV</lang>
echo each ARGV</syntaxhighlight>


=={{header|jq}}==
=={{header|jq}}==
Line 484: Line 502:


'''Example 1:'''
'''Example 1:'''
<lang sh>$ cat echo.foo
<syntaxhighlight lang="sh">$ cat echo.foo
#!/usr/bin/env/jq -M -n -r -f
#!/usr/bin/env/jq -M -n -r -f
"Klaatu barada nikto!"</lang>
"Klaatu barada nikto!"</syntaxhighlight>




Line 500: Line 518:


'''Example 2:'''
'''Example 2:'''
<lang sh>$ cat echo.foo
<syntaxhighlight lang="sh">$ cat echo.foo
#!/usr/bin/env/jq -M -n -r -f
#!/usr/bin/env/jq -M -n -r -f
$x</lang>
$x</syntaxhighlight>
{{out}}
{{out}}
<lang sh>$ ./echo.foo --arg x "Hello, world!"
<syntaxhighlight lang="sh">$ ./echo.foo --arg x "Hello, world!"
Hello, world!</lang>
Hello, world!</syntaxhighlight>


=={{header|Julia}}==
=={{header|Julia}}==
usage: ./thisfile.jl "hello"
usage: ./thisfile.jl "hello"
<lang julia>#!/usr/local/bin/julia
<syntaxhighlight lang="julia">#!/usr/local/bin/julia


# Put the Julia code below this line. It will be compiled and run.
# Put the Julia code below this line. It will be compiled and run.
Line 516: Line 534:
println(ARGS)
println(ARGS)


</lang>{{out}}
</syntaxhighlight>{{out}}
<pre>
<pre>
_
_
Line 533: Line 551:


'''File: echo.langur'''
'''File: echo.langur'''
<lang langur>#!/usr/bin/langur
<syntaxhighlight lang="langur">#!/usr/bin/langur
writeln join " ", _args</lang>
writeln join " ", _args</syntaxhighlight>


'''Usage:'''
'''Usage:'''
<pre>./echo.langur hello, peeps!</pre>
<pre>./echo.langur hello, people</pre>


{{out}}
{{out}}
<pre>hello, peeps!</pre>
<pre>hello, people</pre>


=={{header|Nim}}==
=={{header|Nim}}==
Line 550: Line 568:


'''File: nativeshebang.nim'''
'''File: nativeshebang.nim'''
<lang nim>#!/usr/bin/env -S nim c -r --hints:off
<syntaxhighlight lang="nim">#!/usr/bin/env -S nim c -r --hints:off
import os,strutils
import os,strutils
echo commandLineParams().join(" ")</lang>
echo commandLineParams().join(" ")</syntaxhighlight>
'''Usage:'''
'''Usage:'''
<pre>./nativeshebang.nim hello, world</pre>
<pre>./nativeshebang.nim hello, world</pre>
Line 561: Line 579:


'''File: nim.cfg'''
'''File: nim.cfg'''
<lang nim>--hints:off</lang>
<syntaxhighlight lang="nim">--hints:off</syntaxhighlight>
'''File: nativeshebang2.nims'''
'''File: nativeshebang2.nims'''
<lang nim>#!nim r
<syntaxhighlight lang="nim">#!nim r
import os,strutils
import os,strutils
echo commandLineParams().join(" ")</lang>
echo commandLineParams().join(" ")</syntaxhighlight>
'''Usage:'''
'''Usage:'''
<pre>./nativeshebang2.nim hello, world</pre>
<pre>./nativeshebang2.nim hello, world</pre>
Line 575: Line 593:


'''File: echo.ml'''
'''File: echo.ml'''
<lang ocaml>#! /usr/bin/env ocaml
<syntaxhighlight lang="ocaml">#! /usr/bin/env ocaml


let () =
let () =
let argl = Array.to_list Sys.argv in
let argl = Array.to_list Sys.argv in
print_endline (String.concat " " (List.tl argl))</lang>
print_endline (String.concat " " (List.tl argl))</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 597: Line 615:


'''File: echo.pl'''
'''File: echo.pl'''
<lang perl>#!/usr/bin/perl
<syntaxhighlight lang="perl">#!/usr/bin/perl
print "@ARGV\n";
print "@ARGV\n";
</syntaxhighlight>
</lang>


'''Usage:'''
'''Usage:'''
Line 614: Line 632:
line of the main file start with #! it is ignored/skipped. The only difference between interpretation
line of the main file start with #! it is ignored/skipped. The only difference between interpretation
and compilation, apart from the executable file, is a -c flag on the command line, which I recommend
and compilation, apart from the executable file, is a -c flag on the command line, which I recommend
omitting unless it proves helpful or necessary. Example:
omitting unless it proves helpful or necessary. Example (quietly ignored by pwa/p2js, and Phix in general):
<lang Phix>#!/path/to/phix</lang>
<syntaxhighlight lang="phix">#!/path/to/phix</syntaxhighlight>
You can also invoke the compiler directly as follows
You can also invoke the compiler directly as follows
<!--<syntaxhighlight lang="phix">(phixonline)-->
<lang Phix>string sourcefile = "test.exw",
<span style="color: #008080;">without</span> <span style="color: #008080;">js</span> <span style="color: #000080;font-style:italic;">-- (system_exec)</span>
interpreter = get_interpreter(true),
<span style="color: #004080;">string</span> <span style="color: #000000;">sourcefile</span> <span style="color: #0000FF;">=</span> <span style="color: #008000;">"test.exw"</span><span style="color: #0000FF;">,</span>
cmd = sprintf("%s %s",{interpreter,sourcefile})
<span style="color: #000000;">interpreter</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">get_interpreter</span><span style="color: #0000FF;">(</span><span style="color: #004600;">true</span><span style="color: #0000FF;">),</span>
integer res = system_exec(cmd)</lang>
<span style="color: #000000;">cmd</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"%s %s"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">interpreter</span><span style="color: #0000FF;">,</span><span style="color: #000000;">sourcefile</span><span style="color: #0000FF;">})</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">res</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">system_exec</span><span style="color: #0000FF;">(</span><span style="color: #000000;">cmd</span><span style="color: #0000FF;">)</span>
<!--</syntaxhighlight>-->
See also demo/capture_console.exw which redirects stdin/out/err while interpreting a child process.
See also demo/capture_console.exw which redirects stdin/out/err while interpreting a child process.


Line 631: Line 652:


'''File: echo.py'''
'''File: echo.py'''
<lang python>#!/path/to/python
<syntaxhighlight lang="python">#!/path/to/python
# Although `#!/usr/bin/env python` may be better if the path to python can change
# Although `#!/usr/bin/env python` may be better if the path to python can change


import sys
import sys
print " ".join(sys.argv[1:])</lang>
print " ".join(sys.argv[1:])</syntaxhighlight>


'''Usage:'''
'''Usage:'''
Line 661: Line 682:


File <tt>native-shebang.rkt</tt> contains the following:
File <tt>native-shebang.rkt</tt> contains the following:
<lang racket>#! /usr/local/racket-6.1/bin/racket
<syntaxhighlight lang="racket">#! /usr/local/racket-6.1/bin/racket
#lang racket
#lang racket
(displayln "hello")</lang>
(displayln "hello")</syntaxhighlight>


My directory contains only this:
My directory contains only this:
Line 697: Line 718:


'''File: echo.p6'''
'''File: echo.p6'''
<lang perl6>#!/path/to/raku
<syntaxhighlight lang="raku" line>#!/path/to/raku
put @*ARGS;</lang>
put @*ARGS;</syntaxhighlight>


'''Usage:'''
'''Usage:'''
Line 712: Line 733:
===Unix shebang===
===Unix shebang===
Using e.g. Regina open source REXX interpreter
Using e.g. Regina open source REXX interpreter
<lang rexx>
<syntaxhighlight lang="rexx">
#!/usr/local/bin/regina
#!/usr/local/bin/regina
/* Echo the command line argument */
/* Echo the command line argument */
say arg(1)
say arg(1)
</syntaxhighlight>
</lang>


===ARexx===
===ARexx===
Under AmigaOS, the obligatory REXX starting comment /* is recognised as a shebang of its own, automatically causing the file to be parsed by ARexx as long as the file's script flag is set.
Under AmigaOS, the obligatory REXX starting comment /* is recognised as a shebang of its own, automatically causing the file to be parsed by ARexx as long as the file's script flag is set.
<lang rexx>
<syntaxhighlight lang="rexx">
/* Echo the command line argument */
/* Echo the command line argument */
say arg(1)
say arg(1)
</syntaxhighlight>
</lang>


=={{header|Ruby}}==
=={{header|Ruby}}==
Line 730: Line 751:
=={{header|Sidef}}==
=={{header|Sidef}}==
Sidef is a scripting language and does not compile to a binary.
Sidef is a scripting language and does not compile to a binary.
<lang ruby>#!/usr/bin/sidef
<syntaxhighlight lang="ruby">#!/usr/bin/sidef
say ARGV.join(" ")</lang>
say ARGV.join(" ")</syntaxhighlight>


{{out}}
{{out}}
Line 743: Line 764:


'''File: echo.swift'''
'''File: echo.swift'''
<lang swift>#!/usr/bin/swift
<syntaxhighlight lang="swift">#!/usr/bin/swift


import Foundation
import Foundation


print(Process.arguments[1..<Process.arguments.count].joinWithSeparator(" "))
print(Process.arguments[1..<Process.arguments.count].joinWithSeparator(" "))
</syntaxhighlight>
</lang>


'''Usage:'''
'''Usage:'''


<lang bash>
<syntaxhighlight lang="bash">
./echo.swift Hello, world!
./echo.swift Hello, world!
</syntaxhighlight>
</lang>


{{Out}}
{{Out}}
Line 767: Line 788:


'''File: echo.sh'''
'''File: echo.sh'''
<lang sh>#!/bin/sh
<syntaxhighlight lang="sh">#!/bin/sh
echo "$@"</lang>
echo "$@"</syntaxhighlight>


'''Usage:'''
'''Usage:'''
Line 785: Line 806:


'''File: script_gcc.sh'''
'''File: script_gcc.sh'''
<lang bash>#!/bin/bash
<syntaxhighlight lang="bash">#!/bin/bash


# Actual shebang when using bash:
# Actual shebang when using bash:
Line 848: Line 869:
echo "$binpath: executable not available" 1>&2
echo "$binpath: executable not available" 1>&2
exit $ENOENT
exit $ENOENT
fi</lang>
fi</syntaxhighlight>
'''Test Source File: echo.c'''
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc.sh
<syntaxhighlight lang="c">#!/usr/local/bin/script_gcc.sh
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
Line 863: Line 884:
putchar('\n');
putchar('\n');
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}</lang>
}</syntaxhighlight>


'''Test Execution:'''
'''Test Execution:'''
Line 873: Line 894:
Hello, world!
Hello, world!
</pre>
</pre>

=={{header|V (Vlang)}}==
<syntaxhighlight lang="Vlang">
$ cat file.v
#!/usr/local/bin/v run
println('hello')

$ chmod 755 file.v
$ ./file.v
============ running ./file ============
hello
</syntaxhighlight>
V also knows to compile & run .vsh files immediately, so you do not need a separate step to compile them.

An example deploy.vsh:
<syntaxhighlight lang="Vlang">
#!/usr/bin/env -S v

// Note: The shebang line above, associates the .vsh file to V on Unix-like systems,
// so it can be run just by specifying the path to the .vsh file...
</syntaxhighlight>


=={{header|Wren}}==
=={{header|Wren}}==
Normally, Process.arguments[0] would return the (first) command line argument but here we need to use Process.arguments[1] because the first argument passed to Wren's command line interpreter is ''./native_shebang.wren''.
Normally, Process.arguments[0] would return the (first) command line argument but here we need to use Process.arguments[1] because the first argument passed to Wren's command line interpreter is ''./native_shebang.wren''.
<lang ecmascript>#!/bin/wren native_shebang.wren
<syntaxhighlight lang="wren">#!/bin/wren native_shebang.wren
import "os" for Process
import "os" for Process
System.print(Process.arguments[1])</lang>
System.print(Process.arguments[1])</syntaxhighlight>


{{out}}
{{out}}
Line 891: Line 933:


Since the #! parsing is done by a compiler front end and was designed to be used from the command line, we'll do that by forking zkl to compile the source if it is newer than the binary.
Since the #! parsing is done by a compiler front end and was designed to be used from the command line, we'll do that by forking zkl to compile the source if it is newer than the binary.
<lang zkl>#!/home/craigd/Bin/zkl
<syntaxhighlight lang="zkl">#!/home/craigd/Bin/zkl
// This file: nativeShebang.zkl, compiles to nativeShebang.zsc
// This file: nativeShebang.zkl, compiles to nativeShebang.zsc
// zkl --#! . -c nativeShebang -o.
// zkl --#! . -c nativeShebang -o.
Line 911: Line 953:


////////////// the script:
////////////// the script:
println("Hello world!");</lang>
println("Hello world!");</syntaxhighlight>
{{out}}
{{out}}
<pre>
<pre>
Line 945: Line 987:
#yep, new binary generated
#yep, new binary generated
</pre>
</pre>

{{omit from|6502 Assembly|There's no real way to do this except maybe by loading a file containing code and then JSR to its memory location.}}
{{omit from|68000 Assembly|See above.}}
{{omit from|8080 Assembly|See above.}}
{{omit from|8086 Assembly|See above.}}
{{omit from|ARM Assembly|See above.}}
{{omit from|Z80 Assembly|See above.}}

Latest revision as of 03:08, 5 March 2024



Native 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.

In short: Use the specimen language (native) for "scripting".

Example
If your language is "foo", then the test case of "echo.foo" runs in a terminal as "./echo.foo Hello, world!".


In long: Create a program (in the specimen language) that will automatically compile a test case (of the same specimen language) to a native binary executable and then transparently load and run this test case executable.

Make it so that all that is required is a custom shebangs at the start of the test case. e.g. "#!/usr/local/bin/script_foo"

Importantly: This task must be coded strictly in the specimen language, neither using a shell script nor any other 3rd language.

Optimise this progress so that the test program binary executable is only created if the original test program source code as been touched/edited.

Note: If the lauguage (or a specific implementation) handles this automatically, then simple provide an example of "echo.foo"


Background:

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 "./script.py".

The task Multiline shebang largely demonstrates how to use "shell" code in the shebang to compile and/or run source-code from a 3rd language, typically "#!/bin/bash" or "#!/bin/sh".


This task:

However in this task Native shebang task we are go native. In the shebang, instead of running a shell, we call a binary-executable generated from the original native language, e.g. when using C with gcc "#!/usr/local/bin/script_gcc" to extract, compile and run the native "script" source code.

Other small innovations required of this Native shebang task:

  • Cache the executable in some appropriate place in a path, dependant on available write permissions.
  • Generate a new cached executable only when the source has been touched.
  • If a cached is available, then run this instead of regenerating a new executable.

Difficulties:

  • Naturally, some languages are not compiled. These languages are forced to use shebang executables from another language, eg "#!/usr/bin/env python" uses the C binaries /usr/bin/env and /usr/bin/python. If this is the case, then simply document the details of the case.
  • In a perfect world, the test file (e.g. echo.c) would still be a valid program, and would compile without error using the native compiler (e.g. gcc for text.c). The problem is that "#!" is syntactically incorrect on many languages, but in others it can be parsed as a comment.
  • The "test binary" should be exec-ed and hence retain the original Process identifier.

Test case:

  • Create a simple "script file" (in the same native language) called "echo" then use the "script" to output "Hello, world!"

ALGOL 68

Using ALGOL 68G to script ALGOL 68G

Works with: ALGOL 68 version Revision 1.
Works with: ALGOL 68G version Any - Tested with release algol68g-2.7.

Note: With Algol68G the option "-O3" will compile the script file to a ".so" file, this ".so" file is a binary executable and dynamically loaded library. Also note that this ".so" will only be generated if the ".a68" source file has been touched.

File: echo.a68

#!/usr/bin/a68g --script #
# -*- coding: utf-8 -*- #

STRING ofs := "";
FOR i FROM 4 TO argc DO print((ofs, argv(i))); ofs:=" " OD

Test Execution:

$ ./echo.a68 Hello, world!
Output:
Hello, world!

Arturo

Arturo is a scripting language and does not compile to a binary.

#!/usr/bin/env arturo
print "Hello from Arturo!"
Output:
$> ./native_shebang.art
Hello from Arturo!

C

Using gcc to script C

This example is incorrect. Please fix the code and remove this message.

Details: Talk:Native shebang#Problems: "The C example doesn't work for me (unless a segmentation fault from script_gcc.sh can be described as "working" or a bad interpreter error from echo.c can be described as "working"). --Rdm"

I was able to get this functional, by renaming the itoa() function to itoa_() NOTE, there is an itoa() function already. I will also write a 'correct' 2nd version of this example, that does not use a bash helper script (no reason for that). --JimF

File: script_gcc.c

#!/usr/local/bin/script_gcc.sh
/* Optional: this C code initially is-being/can-be boot strapped (compiled) using bash script_gcc.sh */
#include <errno.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

/* the actual shebang for C target scripts is:
#!/usr/local/bin/script_gcc.c
*/

/* general readability constants */
typedef char /* const */ *STRING;
typedef enum{FALSE=0, TRUE=1} BOOL;
const STRING ENDCAT = NULL;

/* script_gcc.c specific constants */
#define DIALECT "c" /* or cpp */
const STRING
  CC="gcc",
  COPTS="-lm -x "DIALECT,
  IEXT="."DIALECT,
  OEXT=".out";
const BOOL OPT_CACHE = TRUE;

/* general utility procedured */
char strcat_out[BUFSIZ];

STRING STRCAT(STRING argv, ... ){
  va_list ap;
  va_start(ap, argv);
  STRING arg;
  strcat_out[0]='\0';
  for(arg=argv; arg != ENDCAT; arg=va_arg(ap, STRING)){
     strncat(strcat_out, arg, sizeof strcat_out);
  }
  va_end(ap);
  return strndup(strcat_out, sizeof strcat_out);
}

char itoa_out[BUFSIZ];

STRING itoa_(int i){
  sprintf(itoa_out, "%d", i);
  return itoa_out;
}

time_t modtime(STRING filename){
  struct stat buf;
  if(stat(filename, &buf) != EXIT_SUCCESS)perror(filename);
  return buf.st_mtime;
}

/* script_gcc specific procedure */
BOOL compile(STRING srcpath, STRING binpath){
  int out;
  STRING compiler_command=STRCAT(CC, " ", COPTS, " -o ", binpath, " -", ENDCAT);
  FILE *src=fopen(srcpath, "r"),
       *compiler=popen(compiler_command, "w");
  char buf[BUFSIZ];
  BOOL shebang;

  for(shebang=TRUE; fgets(buf, sizeof buf, src); shebang=FALSE)
    if(!shebang)fwrite(buf, strlen(buf), 1, compiler);

  out=pclose(compiler);
  return out;
}

void main(int argc, STRING *argv, STRING *envp){

  STRING binpath,
         srcpath=argv[1],
         argv0_basename=STRCAT(basename((char*)srcpath /*, .DIALECT */), ENDCAT),
         *dirnamew, *dirnamex;
  argv++; /* shift */

/* Warning: current dir "." is in path, AND * /tmp directories are common/shared */
  STRING paths[] = {
    dirname(strdup(srcpath)), /* not sure why strdup is required? */
    STRCAT(getenv("HOME"), "/bin", ENDCAT),
    "/usr/local/bin",
    ".",
    STRCAT(getenv("HOME"), "/tmp", ENDCAT),
    getenv("HOME"),
    STRCAT(getenv("HOME"), "/Desktop", ENDCAT),
/*  "/tmp" ... a  bit of a security hole */
    ENDCAT
  };

  for(dirnamew = paths; *dirnamew; dirnamew++){
    if(access(*dirnamew, W_OK) == EXIT_SUCCESS) break;
  }

/* if a CACHEd copy is not to be kept, then fork a sub-process to unlink the .out file */
  if(OPT_CACHE == FALSE){
    binpath=STRCAT(*dirnamew, "/", argv0_basename, itoa_(getpid()), OEXT, ENDCAT);
    if(compile(srcpath, binpath) == EXIT_SUCCESS){
      if(fork()){
        sleep(0.1); unlink(binpath);
      } else {
        execvp(binpath, argv);
      }
    }
  } else {
/* else a CACHEd copy is kept, so find it */
    time_t modtime_srcpath = modtime(srcpath);
    for(dirnamex = paths; *dirnamex; dirnamex++){
      binpath=STRCAT(*dirnamex, "/", argv0_basename, OEXT, ENDCAT);
      if((access(binpath, X_OK) == EXIT_SUCCESS) && (modtime(binpath) >= modtime_srcpath))
        execvp(binpath, argv);
    }
  }

  binpath=STRCAT(*dirnamew, "/", argv0_basename, OEXT, ENDCAT);
  if(compile(srcpath, binpath) == EXIT_SUCCESS)
    execvp(binpath, argv);

  perror(STRCAT(binpath, ": executable not available", ENDCAT));
  exit(errno);
}

Test Source File: echo.c

#!/usr/local/bin/script_gcc.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv, char **envp){
  char ofs = '\0';
  for(argv++; *argv; argv++){
    if(ofs)putchar(ofs); else ofs=' ';
    fwrite(*argv, strlen(*argv), 1, stdout);
  }
  putchar('\n');
  exit(EXIT_SUCCESS);
}

Test Execution:

$ ./echo.c Hello, world!
Output:
Hello, world!

2nd version. Pure C, no extra bash script

File: script_gcc.c

/*
 * rosettacode.org: Native shebang
 *
 * Copyright 2015, Jim Fougeron.  Code placed in public domain. If you
 * use this, please provide attribution to author. Code originally came
 * from the code found on rosettacode.org/wiki/Native_shebang, however
 * the code was about 80% rewritten.  But the logic of the compile()
 * function still strongly is based upon original code, and is a key
 * part of the file.
 *
 * Native C language shebang scripting. Build this program to /usr/local/bin
 * using:
 *    gcc -o/usr/local/bin/script_gcc script_gcc.c
 *
 * The name of the executable: "script_gcc" IS critical. It is used in knowing
 * that we have have found the proper shebang file when parsing commandline
 *
 * the actual shebang for executable C source scripts is:

#!/usr/local/bin/script_gcc [extra compile/link options]

 * If there additional lib's needed by your source file, then add the proper
 * params to the shebang line. So for instance if gmp, openssl, and zlib
 * were needed (and an additional include path), you would use this shebang:

 #!/usr/local/bin/script_gcc -lgmp -lssl -lcrypto -lz -I/usr/local/include

 * NOTE, we leak strdup calls, but they are 1 time leaks, and this process
 * will simply exec another process, so we ignore them.
 */

#include <errno.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define shebangNAME "script_gcc"
#define CC          "gcc"
#define CC_OPTS     "-lm -x c"
#define IEXT        ".c"
#define OEXT        ".out"

/* return time of file modification. If file does not exit, return 0
 * so that if we compare, it will always be older, and will be built */
time_t modtime(const char *filename) {
  struct stat st;

  if(stat(filename, &st) != EXIT_SUCCESS)
    return 0;
  return st.st_mtime;
}
/* join a pair of strings */
char *sjoin(const char *s1, const char *s2){
  char buf[BUFSIZ];

  if (!s1) s1=""; if (!s2) s2="";
  snprintf(buf, sizeof(buf), "%s%s", s1, s2);
  return strdup(buf);
}
/* compiles the original script file. It skips the first line (the shebang)
 * and compiles using "gcc .... -x c -" which avoids having to write a temp
 * source file (minus the shebang), we instead pipe source to gcc */
int compile(const char *srcpath, const char *binpath, const char *ex_comp_opts) {
  char buf[BUFSIZ];
  FILE *fsrc, *fcmp;

  sprintf(buf, "%s %s %s -o %s -", CC, CC_OPTS, ex_comp_opts, binpath);
  fsrc=fopen(srcpath, "r");
  if (!fsrc) return -1;
  fcmp=popen(buf, "w"); /* open up our gcc pipe to send it source */
  if (!fcmp) { fclose(fsrc); return -1; }

   /* skip shebang line, then compile rest of the file. */
  fgets(buf, sizeof(buf), fsrc);
  fgets(buf, sizeof(buf), fsrc);
  while (!feof(fsrc)) {
    fputs(buf, fcmp); /* compile this line of source with gcc */
    fgets(buf, sizeof(buf), fsrc);
  }
  fclose(fsrc);
  return pclose(fcmp);
}

/* tries to open the file 'argv0'. If we can open that file we read first line
 * and make SURE it is a script_gcc file. If it is a script_gcc file, we
 * look for any extra params for the compiler (params to the shebang line)
 * and if we find them, they are returned in ex_comp_opts.
 * return 0 if this is NOT a shebang file, return 1 if it IS the shebang file.
 */
int load_shebangline(const char *argv0, char **ex_comp_opts) {
  char opt[BUFSIZ], *cp;
  FILE *in = fopen(argv0, "r");

  if (!in) return 0;
  fgets(opt, sizeof(opt), in);
  fclose(in);
  /* ok, we found a readable file, but IS it our shebang file? */
  strtok(opt, "\r\n");
  if (strncmp(opt, "#!", 2) || !strstr(opt, shebangNAME))
    return 0; /* nope, keep looking */
  cp = strstr(opt, shebangNAME)+strlen(shebangNAME);
  if (*cp) /* capture compiler extra params, if any */
    *ex_comp_opts = strdup(cp);
  return 1;
}

/* NOTE, the argv[] array is different than 'normal' C programs.  argv[0] is
 * the shebang exe file. then argv[1] ... argv[p] are the params that follow
 * the shebang script name (from line1 of the script file). NOTE, some systems
 * (Linux, cygwin), will pack all of these options into argv[1] with spaces.
 * There is NO 'standard' on exactly what the layout it of these shebang params
 * may be, we only know that there will be 0 or more params BEFORE the script
 * name (which is the argv[0] we normally 'expect). Then argv[p+1] is the name
 * of script being run. NOTE if there are no shebang args, argv[1] will be
 * the script file name.  The script file name is our expected argv[0], and
 * all the params following that are the normal argv[1]...argv[n] we expect.
 */
int main(int argc, char *const argv[]) {
  int i;
  char exec_path[BUFSIZ], *argv0_basename, *ex_comp_opts=0, **dir,
  *paths[] = {
    NULL, /* paths[0] will be filled in later, to dir of the script */
    "/usr/local/bin",
    sjoin(getenv("HOME"), "/bin"),
    sjoin(getenv("HOME"), "/tmp"),
    getenv("HOME"),
    sjoin(getenv("HOME"), "/Desktop"),
    /* . and /tmp removed due to security concerns
    ".",
    "/tmp", */
    NULL
  };

  /* parse args, looking for the one that is the script. This would have been argv[0] if not exec'd as a shebang */
  for (i = 1; i < argc; ++i) {
    if (load_shebangline(argv[1], &ex_comp_opts)) {
      argc -= i;
      i = 0;
      break;
    }
    ++argv;
  }
  if (i)
    return !fprintf(stderr, "could not locate proper %s shebang file!!\n", shebangNAME) | ENOENT;
  ++argv;
  /* found it.  Now argv[0] is the 'script' name, and rest of argv[] is params to the script */
  argv0_basename = basename(strdup(argv[0]));
  paths[0] = dirname(strdup(argv[0]));
  /* find a cached version of the script, and if we find it, run it */
  for(dir = paths; *dir; dir++) {
    snprintf(exec_path, sizeof(exec_path), "%s/%s%s", *dir, argv0_basename, OEXT);
    if(modtime(exec_path) >= modtime(argv[0]))
      execvp(exec_path, argv); /* found a newer cached compiled file. Run it */
  }
  /* no cached file, or script is newer. So find a writeable dir */
  for(dir = paths; *dir; dir++) {
    if(!access(*dir, W_OK))
      break;
  }
  if (!*dir)
    return !fprintf(stderr, "No writeable directory for compile of the script %s\n", argv[0]) | EACCES;
  /* compile and exec the result from our C script source file */
  snprintf(exec_path, sizeof(exec_path), "%s/%s%s", *dir, argv0_basename, OEXT);
  if(!compile(argv[0], exec_path, ex_comp_opts))
    execvp(exec_path, argv);
  return !fprintf(stderr, "%s : executable not available\n", exec_path) | ENOENT;
}

Test Source File: echo.c

#!/usr/local/bin/script_gcc
/*
 * note, any additional libs or include paths would have params added after
 * the script_gcc parts of the shebang line, such as:
 * #!/usr/local/bin/script_gcc -lgmp -I/usr/local/include/gmp5
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv, char **envp){
  char ofs = '\0';
  for(argv++; *argv; argv++){
    if(ofs)putchar(ofs); else ofs=' ';
    fwrite(*argv, strlen(*argv), 1, stdout);
  }
  putchar('\n');
  exit(EXIT_SUCCESS);
}

Test Execution:

$ ./echo.c Hello, world!
Output:
Hello, world!

Forth

Such functionality can be added easily by the following definition:

: #! [COMPILE] \ ; immediate

Some Forth compilers - like 4tH or Gforth - support this functionality out of the box. This example program works as advertised:

#! /usr/local/bin/4th cxq
argn 1 ?do i args type space loop cr

Free Pascal

Since FPC (FreePascal compiler) version 2.6.0 the distribution – e.g. the Debian or FreeBSD packages fpc-utils – come with the program instantfpc(1), or ifpc(1) for short. The program fulfills this task’s specifications, plus other goodies. The sources are available in trunk/utils/instantfpc/instantfpc.pas and are not repeated here. See the FreePascal wiki for ifpc usage.

Go

In Go, single line comments can only begin with // and so we have to use the former in place of a normal shebang (#!) to achieve the same effect.

The 'go run' command compiles a .go source code file to a binary executable and then runs the latter automatically. The executable is placed in a temporary directory which is then deleted when the process ends.

To cache the executable in the current directory, one would have to use 'go build' instead (replace the opening line with ///usr/bin/env go build echo.go; ./echo "$@"; exit).

The following works fine on Ubuntu 16.04.

///usr/bin/env go run echo.go "$@"; exit
package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) > 1 {
        fmt.Println(os.Args[1])
    }
}
Output:
$ ./echo.go "Hello, world!"
Hello, world!

J

As no compiling versions of J are currently available, the binaries are trivially empty and we shall store them in the empty path. We shall use /usr/local/bin/ijconsole (which was compiled using a C compiler) as the J interpreter, and echo each ARGV as our sample code:

#!/usr/local/bin/ijconsole
echo each ARGV

jq

Works with: jq version 1.4

jq can be invoked on the shebang line, e.g. as

#!/usr/local/bin/jq -M -n -f

or

#!/usr/bin/env jq -M -n -f

Example 1:

$ cat echo.foo
#!/usr/bin/env/jq -M -n -r -f
"Klaatu barada nikto!"


$ ./echo.foo 
Klaatu barada nikto!

Command-line parameters of a script created with a shebang line in this manner are processed as jq command-line parameters. Thus, instead of being able to invoke the script along the lines of

$ ./echo.foo "Hello world!"   # nope

one would have to introduce a named variable to hold the command-line parameter, as illustrated in the next example:

Example 2:

$ cat echo.foo
#!/usr/bin/env/jq -M -n -r -f
$x
Output:
$ ./echo.foo --arg x "Hello, world!"
Hello, world!

Julia

usage: ./thisfile.jl "hello"

#!/usr/local/bin/julia

# Put the Julia code below this line. It will be compiled and run.

Base.banner()
println(ARGS)
Output:
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.1.1 (2019-05-16)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/ 
hello                  |

langur

Langur uses a hash mark to start single line comments, so a shebang is not a problem, as the compiler will ignore it.

File: echo.langur

#!/usr/bin/langur
writeln join " ", _args

Usage:

./echo.langur hello, people
Output:
hello, people

Nim

Using env

Uses env -S to split up the commandline parameters,which is maybe cheating but apart from that housekeeping this ticks all the boxes:

  • it compiles the source code to an executable, if and only if it has changed
  • it then runs that executable with the supplied arguments

File: nativeshebang.nim

#!/usr/bin/env -S nim c -r --hints:off
import os,strutils
echo commandLineParams().join(" ")

Usage:

./nativeshebang.nim hello, world
Output:
hello, world

Using a nim.cfg and/or nim r

Alternatively, accept all the compiler messages, or create a nim.cfg that silences them:

File: nim.cfg

--hints:off

File: nativeshebang2.nims

#!nim r
import os,strutils
echo commandLineParams().join(" ")

Usage:

./nativeshebang2.nim hello, world
Output:
hello, world

OCaml

OCaml can run in script mode or compiled (compiled to bytecode or native binary).

File: echo.ml

#! /usr/bin/env ocaml

let () =
  let argl = Array.to_list Sys.argv in
  print_endline (String.concat " " (List.tl argl))
Output:
$ chmod u+x echo.ml
$ ./echo.ml Hello, world!
Hello, world!

But we have to remove the shebang if we want to compile the code, or we get an error:

$ ocamlc -c echo.ml
File "echo.ml", line 1, characters 0-2:
Error: Syntax error

Perl

Perl is a script language. It's natural and easy to script native Perl code.

File: echo.pl

#!/usr/bin/perl
print "@ARGV\n";

Usage:

./echo.pl Hello, world!
Output:
Hello, world!

Phix

Phix is a hybrid interpreter/compiler, so a shebang adds little value anyway, however should the first line of the main file start with #! it is ignored/skipped. The only difference between interpretation and compilation, apart from the executable file, is a -c flag on the command line, which I recommend omitting unless it proves helpful or necessary. Example (quietly ignored by pwa/p2js, and Phix in general):

#!/path/to/phix

You can also invoke the compiler directly as follows

without js -- (system_exec)
string sourcefile = "test.exw",
       interpreter = get_interpreter(true),
       cmd = sprintf("%s %s",{interpreter,sourcefile})
integer res = system_exec(cmd)

See also demo/capture_console.exw which redirects stdin/out/err while interpreting a child process.

Python

Extract: "If you need to create a .pyc file for a module that is not imported, you can use the py_compile and compileall modules. The py_compile module can manually compile any module. One way is to use the py_compile.compile function in that module interactively:[1]:"

>>> import py_compile
>>> py_compile.compile('echo.py')

File: echo.py

#!/path/to/python
# Although `#!/usr/bin/env python` may be better if the path to python can change

import sys
print " ".join(sys.argv[1:])

Usage:

./echo.py Hello, world!
Output:
Hello, world!

Racket

Racket has raco: Racket Command Line Tools which can be used to compile to bytecode or compile to standalone executables (along with a whole load of other fun stuff to do with packages, unit testing and the likes).

To properly compile a file/program, one needs to invoke raco or go through invocations of racket to see what needs to be done. Compilation is expensive. Dependency management is expensive and difficult to do. The only one who can probably be trusted to do this is raco. So (as with Python), if you need to compile the program, do so with the compiler.

Once you have done this, however, racket (in the shebang) will use the 'compiled' version, not the source.

In this example:

File native-shebang.rkt contains the following:

#! /usr/local/racket-6.1/bin/racket
#lang racket
(displayln "hello")

My directory contains only this:

-bash-3.2$ ls
native-shebang.rkt

Which runs:

-bash-3.2$ ./native-shebang.rkt 
hello

But has not self-compiled or anything like that:

-bash-3.2$ ls
native-shebang.rkt

I run raco to compile it:

-bash-3.2$ raco make native-shebang.rkt 
-bash-3.2$ ls -R
.:
compiled  native-shebang.rkt

./compiled:
native-shebang_rkt.dep  native-shebang_rkt.zo

The dependency file and byte-code -- .zo -- file are in a compiled directory.

I still run native-shebang.rkt from the script (with the racket shebang). Racket will use the compiled code instead of the source in the script:

-bash-3.2$ ./native-shebang.rkt 
hello

(although it's hard to prove)

Raku

(formerly Perl 6)

Raku is not installed by default on most systems and does not have a default install directory, so the path will vary by system.

Works with: Rakudo version 2020.02

File: echo.p6

#!/path/to/raku
put @*ARGS;

Usage:

./echo.p6 Hello, world!
Output:
Hello, world!

REXX

Unix shebang

Using e.g. Regina open source REXX interpreter

#!/usr/local/bin/regina
/* Echo the command line argument */
say arg(1)

ARexx

Under AmigaOS, the obligatory REXX starting comment /* is recognised as a shebang of its own, automatically causing the file to be parsed by ARexx as long as the file's script flag is set.

/* Echo the command line argument */
say arg(1)

Ruby

Ruby does not compile to a binary, thankfully.

Sidef

Sidef is a scripting language and does not compile to a binary.

#!/usr/bin/sidef
say ARGV.join(" ")
Output:
$ ./echo.sf Hello, World!
Hello, World!

Swift

Using Swift REPL:

File: echo.swift

#!/usr/bin/swift

import Foundation

print(Process.arguments[1..<Process.arguments.count].joinWithSeparator(" "))

Usage:

./echo.swift Hello, world!
Output:
Hello, world!

UNIX Shell

Using sh to script sh

In strictly shell this is natural, native and easy:

File: echo.sh

#!/bin/sh
echo "$@"

Usage:

./echo.sh Hello, world!
Output:
Hello, world!

Using bash to script C

Works with: Bourne Again SHell

Note: this Native shebang task does not exactly apply to bash because bash is interpretive, but as a skeleton template the following script is an example of how compiled languages can implement the shebang. Also: this bash code can be used to automatically compile the C code in /usr/local/bin/script_gcc.c above.

File: script_gcc.sh

#!/bin/bash

# Actual shebang when using bash:
#!/usr/local/bin/script_gcc.sh

# Alternative shebang when using bash:
#!/bin/bash /usr/local/bin/script_gcc.sh

# CACHE=No # to turn off caching...

# Note: this shell should be re-written in actual C! :-)

DIALECT=c # or cpp
CC="gcc"
COPTS="-lm -x $DIALECT"
IEXT=.$DIALECT
OEXT=.out

ENOENT=2

srcpath="$1"; shift # => "$@"
#basename="$(basename "$srcpath" ."$DIALECT")"
basename="$(basename "$srcpath")"

# Warning: current dir "." is in path, AND */tmp directories are common/shared
paths="$(dirname "$srcpath")
$HOME/bin
/usr/local/bin
.
$HOME/tmp
$HOME
$HOME/Desktop"
#/tmp

while read dirnamew; do
  [ -w "$dirnamew" ] && break
done << end_here_is
$paths
end_here_is

compile(){
  sed -n '2,$p' "$srcpath" | "$CC" $COPTS -o "$binpath" -
}

if [ "'$CACHE'" = "'No'" ]; then
  binpath="$dirnamew/$basename-v$$$OEXT"
  if compile; then
    ( sleep 0.1; exec rm "$binpath" ) & exec "$binpath" "$@"
  fi
else
  while read dirnamex; do
    binpath="$dirnamex/$basename$OEXT"
    if [ -x "$binpath" -a "$binpath" -nt "$srcpath" ];
      then exec "$binpath" "$@"; fi
  done << end_here_is
$paths
end_here_is

  binpath="$dirnamew/$basename$OEXT"
  if compile; then exec "$binpath" "$@"; fi

  echo "$binpath: executable not available" 1>&2
  exit $ENOENT
fi

Test Source File: echo.c

#!/usr/local/bin/script_gcc.sh
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv, char **envp){
  char ofs = '\0';
  for(argv++; *argv; argv++){
    if(ofs)putchar(ofs); else ofs=' ';
    fwrite(*argv, strlen(*argv), 1, stdout);
  }
  putchar('\n');
  exit(EXIT_SUCCESS);
}

Test Execution:

$ ./echo.c Hello, world!
Output:
Hello, world!

V (Vlang)

$ cat file.v
#!/usr/local/bin/v run
println('hello')

$ chmod 755 file.v
$ ./file.v
============ running ./file ============
hello

V also knows to compile & run .vsh files immediately, so you do not need a separate step to compile them.

An example deploy.vsh:

#!/usr/bin/env -S v

// Note: The shebang line above, associates the .vsh file to V on Unix-like systems,
// so it can be run just by specifying the path to the .vsh file...

Wren

Normally, Process.arguments[0] would return the (first) command line argument but here we need to use Process.arguments[1] because the first argument passed to Wren's command line interpreter is ./native_shebang.wren.

#!/bin/wren native_shebang.wren
import "os" for Process
System.print(Process.arguments[1])
Output:
$ chmod +x native_shebang.wren
$ ./native_shebang.wren "Hello world!"
Hello world!

zkl

This isn't something that is usually done with zkl as the compiler is fast enough (for scripts) to just run the source and let it get compiled and run. But, it can be done. Note: The binary still "links" against zkl (the VM) so the #! is required.

Since the #! parsing is done by a compiler front end and was designed to be used from the command line, we'll do that by forking zkl to compile the source if it is newer than the binary.

#!/home/craigd/Bin/zkl
// This file: nativeShebang.zkl, compiles to nativeShebang.zsc
// zkl --#! . -c nativeShebang -o.
// chmod a+x nativeShebang.z*
// ./nativeShebang.zsc

// If this [source] file is newer than the compiled version (or the
// compiled file doesn't exist), compile up a new version

runningName :=System.argv[1];  // argv==("zkl","nativeShebang.zkl|zsc")
parts,path  :=File.splitFileName(runningName),parts[0,2].concat() or ".";
srcName     :=parts[0,-1].concat() + ".zkl";
compiledName:=parts[0,-1].concat() + ".zsc";
if(not File.exists(compiledName) or 
   File.info(srcName)[2] > File.info(compiledName)[2]){ // compare modifed dates
      System.cmd("zkl --#! . -c %s -o%s --exit".fmt(srcName,path));
      System.cmd("chmod a+x %s".fmt(compiledName));
}

////////////// the script:
println("Hello world!");
Output:
#run the source in the usual manner to generate a binary
#could use ./nativeShebang.zkl if chmod'd it
$ zkl nativeShebang.zkl
Compiled Class(nativeShebang)  (0.0 seconds, ??? lines/sec)
Wrote Class(nativeShebang) to ./nativeShebang.zsc
Hello world!

#eyeball the binary
$ zkl hexDump nativeShebang.zsc
   0: 23 21 2f 68 6f 6d 65 2f | 63 72 61 69 67 64 2f 42   #!/home/craigd/B
  16: 69 6e 2f 7a 6b 6c 0a 7a | 6b 44 00 00 28 31 2e 31   in/zkl.zkD..(1.1
  32: 00 5a 4b 4c 20 53 65 72 | 69 61 6c 69 7a 65 64 20   .ZKL Serialized 
  48: 43 6c 61 73 73 00 6e 61 | 74 69 76 65 53 68 65 62   Class.nativeSheb
  64: 61 6e 67 00 00 02 00 00 | 34 2b 61 74 74 72 69 62   ang.....4+attrib
  80: 75 74 65 73 3a 73 74 61 | 74 69 63 20 63 72 65 61   utes:static crea
  96: 74 65 52 65 74 75 72 6e | 73 53 65 6c 66 00 6e 61   teReturnsSelf.na
 112: 74 69 76 65 53 68 65 62 | 61 6e 67 00 00 00 01 00   tiveShebang.....
...

#run the binary
$ ./nativeShebang.zsc
Hello world!

#see if update works
$ touch ./nativeShebang.zkl
$ ./nativeShebang.zsc
Compiled Class(nativeShebang)  (0.0 seconds, ??? lines/sec)
Wrote Class(nativeShebang) to ./nativeShebang.zsc
Hello world!
#yep, new binary generated