Native shebang: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎[[Native shebang#ALGOL 68]]: use #!/usr/bin/a68g --script #)
(Added zkl)
Line 283: Line 283:
Hello, world!
Hello, world!
</pre>
</pre>

=={{header|zkl}}==
echo.zkl:
<lang zkl>#!/home/craigd/Bin/zkl
println(vm.arglist.concat(" "));</lang>
Bin/zkl is actually a script too.
<lang zkl>./echo.zkl Hello World!</lang>
{{out}}
<pre>Hello World!</pre>

Revision as of 06:49, 18 March 2014



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.

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.

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

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<lang algol68>#!/usr/bin/a68g --script #

  1. -*- coding: utf-8 -*- #

STRING ofs := ""; FOR i FROM 4 TO argc DO print((ofs, argv(i))); ofs:=" " OD</lang>Test Execution:

$ ./echo.c Hello, world!

Output:

Hello, world!

C

File: script_gcc.c <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 */

  1. include <errno.h>
  2. include <libgen.h>
  3. include <stdarg.h>
  4. include <stdio.h>
  5. include <stdlib.h>
  6. include <string.h>
  7. include <sys/stat.h>
  8. include <unistd.h>

/* the actual shebang for C target scripts is:

  1. !/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 */

  1. 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);

}</lang>

Test Source File: echo.c <lang c>#!/usr/local/bin/script_gcc.c

  1. include <stdio.h>
  2. include <string.h>
  3. 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);

}</lang>

Test Execution:

$ ./echo.c Hello, world!

Test Output:

Hello, world!

UNIX Shell

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 <lang bash>#!/bin/bash

  1. Actual shebang when using bash:
  2. !/usr/local/bin/script_gcc.sh
  1. Alternative shebang when using bash:
  2. !/bin/bash /usr/local/bin/script_gcc.sh
  1. CACHE=No # to turn off caching...
  1. 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 # => "$@"

  1. basename="$(basename "$srcpath" ."$DIALECT")"

basename="$(basename "$srcpath")"

  1. 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"

  1. /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</lang> Test Source File: echo.c <lang c>#!/usr/local/bin/script_gcc.sh

  1. include <stdio.h>
  2. include <string.h>
  3. 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);

}</lang>

Test Execution:

$ ./echo.c Hello, world!

Test Output:

Hello, world!

zkl

echo.zkl: <lang zkl>#!/home/craigd/Bin/zkl println(vm.arglist.concat(" "));</lang> Bin/zkl is actually a script too. <lang zkl>./echo.zkl Hello World!</lang>

Output:
Hello World!