Native shebang: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|C}}: fix vim typo)
(→‎{{header|C}}: add {{header|UNIX_shell}})
Line 21: Line 21:
=={{header|C}}==
=={{header|C}}==
'''File: script_gcc.c'''
'''File: script_gcc.c'''
<lang c>#include <errno.h>
<lang c>#!/usr/local/bin/script_gcc.sh
#include <errno.h>
#include <libgen.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdarg.h>
Line 30: Line 31:
#include <unistd.h>
#include <unistd.h>


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


Line 148: Line 149:


'''Test Source File: echo.c'''
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc
<lang 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);
}</lang>

'''Test Execution:'''
<pre>
$ ./echo.c Hello, world!
</pre>
'''Test Output:'''
<pre>
Hello, world!
</pre>

=={{header|UNIX Shell}}==
{{works with|Bourne Again SHell}}
Note: this '''Native shebang''' task does not exactly apply to [[bash]] because 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

# Alternative:
#!/bin/bash /usr/local/bin/gcc_script.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</lang>
'''Test Source File: echo.c'''
<lang c>#!/usr/local/bin/script_gcc.sh
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>

Revision as of 01:56, 6 September 2013

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. "#!/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 the 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!"

C

File: script_gcc.c <lang c>#!/usr/local/bin/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 targets 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;

/* gcc_script.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 srcpath;

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;

}

/* gcc_script 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 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. Alternative:
  2. !/bin/bash /usr/local/bin/gcc_script.sh
  3. 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!