shebang script interpreter from shell variable - bash

I have a number of scripts which need to specify the python binary which runs them:
#! /home/nargle/python/bin/python2.6
I need to adapt these scripts to work at two different sites. Lots of tools are installed in different places, so at new site 2 the script needs to start with:
#! /user/nargle/python/bin/python2.6
..
I want to replace directly-quoted paths with environment variables which are set differently for each site. What I would like is for this to work:
#! $MY_PYTHON_PATH
but it doesn't! I am slightly hazy on where to research this. Is it the executing shell (be it bash, csh or whatever) which detects the '#!' at the start of a script (be it bash, python or whatever) and fires up the interpreter/shell to run it?
I feel that there must be some way to do this. Please advise!
Oh yes, there is one more constraint: we cannot use the path for this. This may seem like a stupid restriction but this is for a large environment with many users
The environment is RHEL 5.7.
EDIT It has been suggested to use a shell script and that is the current plan: it works fine:
$MY_PYTHON_PATH some_script file.py $#
The problem is really that we have lots of people using the python files, and lots of automated tests which need to changed. If it has to be done it has to be done but I if possible I want to minimise the impact of a change of working practice for scores of people.
EDIT It would also be possible to have a link in a location which is the same on both systems, and which links to the real binary in a different target on each system. This is quite feasible but seems kind of messy: we use the linux 'modules' package to setup environment variables for many tools and it would be nice if we could take the python path from our modulefiles.
EDIT It isn't the answer but this feels like the kind of evil hack I was looking for:
http://docs.nscl.msu.edu/daq/bluebook/html/x3237.html
.. see "Example 4-2. #! lines for bash and for tclsh"
EDIT I hoped this might work but it didn't:
!# /usr/bin/env PATH=$PATH:$MY_PYTHON_PATH python2.6

The common solution is to change the shebang to
#!/usr/bin/env python2.6
Then, just arrange your $PATH to point to the right python2.6 on each machine.

Write a wrapper shell script. If you have script.py, write a script.py.sh with the following content:
#!/bin/bash
PYTHON_SCRIPT=$( echo "$0" | sed -e 's/\.sh$//' )
exec $MY_PYTHON_PATH $PYTHON_SCRIPT "$#"
Disclaimer: This isn't tested, just wrote it off the top of my head.
Now just set up your MY_PYTHON_PATH on each machine, and call script.py.sh instead of script.py.
Summary This solution is only second-best, since it requires a lot of script calls to be changed from script.py to script.py.sh, something that should be avoided if at all possible.
Alternative
Use env to call a python-finder script, which just calls the python binary contained in $MY_PYTHON_PATH. The python-finder script has to be in the same location on both machines, use symlinks if necessary.
#!/usr/bin/env /usr/local/bin/python-finder.sh
The contents of python-finder.sh:
#!/bin/bash
exec $MY_PYTHON_PATH "$#"
This works because for interpreter scripts (those starting with a shebang) execve calls the interpreter and passes the filename to env, which in turn passes it on to the command it calls.

I was being silly: using variable expansion with env does work.
#! /usr/bin/env PATH="$PATH:$MY_PYTHON_PATH" python2.6

We can do:
#!/bin/bash
"exec" "python" "$0"
print "Hello World"
from http://rosettacode.org/wiki/Multiline_shebang#Python

Related

How to run csh script in bash shell

My default shell is bash in Ubuntu 14.04. I have a csh script file named clean.sh with the following make command:
#! /bin/csh -f
make -f commande.make del
And commande.make has
CKHOME=../CHEMKIN/DATA_BASES
LIN_DATA=${CKHOME}/LIN_FILES/
LINK_CKTP=${CKHOME}/LINK_CKTP_FILES/
#-----------------------------------------------------
include schema_cinetique.make
LINKFILE=${NAME}_LINK
LINKTPFILE=${NAME}_LINKTP
LINKFILE_OLD=${NAME_OLD}_LINK
LINKFILE_NEW=${NAME_NEW}_LINK
#-----------------------------------------------------
cplink :
${COPY} ${LINK_CKTP}${LINKFILE} LINK
cplink2 :
${COPY} ${LINK_CKTP}${LINKFILE} LINKZ1
tplink :
${COPY} ${LINK_CKTP}${LINKTPFILE} LINKTPZ1
calcul :
${COPY} jobtimp1 LJOBNZ1
${COPY} unsteadyf.dat1 DATZ1
del :
${DELETE} LINKZ1 LINKTPZ1 LJOBNZ1 DATZ1 SOLASUZ1
I opened the terminal and moved to the location and tried
./clean.sh
or
csh clean.sh &
or
csh -f clean.sh
Nothing worked.
I got the following line in the terminal,
LINKZ1 LINKTPZ1 LJOBNZ1 DATZ1 SOLASUZ1
make: LINKZ1: Command not found
make: *** [del] Error 127
So, how to run clean.sh file ?
You are confused. The Csh script contains a single command which actually runs identically in Bash.
#!/bin/bash
make -f commande.make del
Or, for that matter, the same with #!/bin/sh. Or, in this individual case, even sh clean.sh, since the shebang is then just a comment, and the commands in the file are available in sh just as well as in csh.
Once make runs, that is what parses and executes the commands in commande.make. make is not a "Fortran command", it is a utility for building projects (but the makefile named commande.make probably contains some instructions for how to compile Fortran code).
In the general case, Csh and Bash are incompatible, but the differences are in the shell's syntax itself (so, the syntax of loops and conditionals, etc, as well as variable assignments and various other shell builtins).
As an aside, Csh command files should probably not have a .sh extension, as that vaguely implies Bourne shell (sh) syntax. File extensions on Unix are just a hint to human readers, so not technically important; but please don't confuse them/us.
(As a further aside, nobody should be using Csh in 2022. There was a time when the C shell was attractive compared to its competition, but that was on the order of 40 years ago.)
The subsequent errors you are reporting seem to indicate that the makefile depends on some utilities which you have not installed. Figuring that out is a significant enough and separate enough question that you should probably ask a new question about that, probably with more debugging details. But in brief, it seems that make needs to be run with parameters to indicate what NAME and COPY (and probably some other variables) should be. Try with make -f commande.make COPY=cp DELETE=rm NAME=foobar for a start, but it's probably not yet anywhere near sufficient.
(I would actually assume that there will be a README file or similar to actually instruct you how to use commande.make since it seems to have some local conventions of its own.)
It seems the script is written having portability in mind, i.e. the name of the cp and rm binaries is kept in variables rather than hard-coding it. My best guess is that this has been done to make it possible to run the script on non UNIX systems, like Windows.
To make it work, export the respective variables before running the script. For the del action you are calling, only the DELETE variable is needed. It should be set to rm which is the command used to remove files on Linux:
export DELETE=rm
./clean.sh
Note: exporting the variable can also be done in one line when invoking the script, by prepending it to the command line:
DELETE=rm ./clean.sh
This behaviour is described in the bash manual:
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters. These assignment statements affect only the environment seen by that command.

What is the shell interpretor path on iSeries?

On linux systems, it is good practice to prefix shell scripts by a commentary giving the path to the requiered shell to execute.
example:
#!/bin/bash
#or
#!/usr/bin/env bash
This makes the shell syntax awaited explicit (and it is a final touch telling people the script has been reviewed).
But I'm currently writing scripts on iSeries (AS400) where I use qsh.
And I don't know if there is something similar to write on top of my scripts.
Do you know the path to the interpretor? What do you write in your scripts?
I use:
#!/bin/qsh
In PASE (call qp2term) you can use
#!/bin/sh

Source a script with a different shell type

Let's say a script is called with /bin/sh. Is it possible to source another script from that script and to have it be interpreted with #!/bin/bash?
It would appear that the #!/bin/bash doesn't do anything...
And by source, at this point I am meaning the functionality of manipulating the parent environment.
No. The whole point of sourcing a script is that the script is interpreted by the shell doing the sourcing. If, as is often the case, /bin/sh is bash, then you will get the desired behavior. Otherwise, you are out of luck.
Try the source command, or dot operator. You might also try the env command. Note, make sure you export if you're using source (or dot).

How to run a TCL script from shell script?

I am new to TCL scripting and shell scripting. I want to invoke a TCL script from the shell script. I have tried as below.
#!/bin/sh
for i in {1..5}
do
my_script
test_script
done
If I run the script, it is throwing error as follows,
./sample.sh: line 5: my_script: command not found
./sample.sh: line 5: test_script: command not found
Can anyone help me out with this ?
Thanks in advance.
If they cannot be found in your $PATH you have to provide a path to your scripts, e.g.:
./my_myscript # current directory
/path/to/test_script # absolute path
If you haven't made your script executable (with chmod +x) then you need to use:
tclsh my_script.tcl
Or maybe tclsh8.5 /path/to/script.tcl or many variations on that.
If you have made the script executable, check that the directory containing the script is on your PATH (if not, use the full filename of the script or adjust your PATH) and that you've got a suitable #! line. The usual recommended one is:
#!/usr/bin/env tclsh8.5
as that will search your path for the tclsh8.5 executable instead of hard-coding it.
From man tclsh. I guess the second block answers your question.
If you create a Tcl script in a file whose first line is
#!/usr/local/bin/tclsh
then you can invoke the script file directly from your shell if you mark the file as executable. [...]
An even better approach is to start your script files with the following three lines:
#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" ${1+"$#"}
This approach has three advantages over the approach in the previous paragraph [...]
You should note that it is also common practice to install tclsh with its version number as part of the name.This has the advantage of allowing multiple
versions of Tcl to exist on the same system at once, but also the disadvantage of making it harder to write scripts that start up uniformly across different versions of Tcl.

bash trickery using --init-file

I have a bash-script (let's call it /usr/bin/bosh) using the following she-bang line:
#!/bin/bash --init-file
It defines a couple of functions, and generally puts the interactive shell in an environment where the user can control a bunch of stuff that I want. This works pretty well. Now for the interesting part, I'd like to be able to let users use this in-between-layer for writing new scripts, without explicitly havnig to source this one. Is that at all possible?
I tried writing a script (let's call it /usr/bin/foo) using the she-bang line
#!/usr/bin/bosh
Which I thought, would be rewritten to execute the command
/usr/bin/bosh /usr/bin/foo
which in turn would result in
/bin/bash --init-file /usr/bin/bosh /usr/bin/foo
But it doesn't work, /usr/bin/foo gets executed, but /usr/bin/bosh is not source before that.
How can I make it source the init file even though the script is not interactive? Or would I have to write a wrapper script for that? I thought of having a script like this
#!/bin/bash
. /usr/bin/bosh
. "$1"
But that wouldn't turn into an interactive shell if I don't specify a script to run, which would be kind of a shame.
EDIT
For clarification, what I'm really asking is, how can I make bash source a file (like --init-file) regardless whether it's interactive (before starting the interactive part) or not (before executing the script)? If there's no way, is there any other way to solve my problem perhaps?
The program specified by the #! cannot be another script I'm afraid at least until linux kernel 2.6.27.9, which allows this feature. If you run strace on foo you'll see that you'd get an ENOEXEC or exec format error, because bosh cannot be executed as a standalone program.
What is happening is that instead of /bin/bosh being executed and handed foo as input, your login shell is simply silently falling back to executing foo itself in a sub-shell, which is why it seems to almost work.
A wrapper or C program that launches bash the way you want are probably your only options. Even with an upgrade to your kernel, it will not quite work the way you want I'm afraid.
Everything you ever wanted to know about #! here: http://www.in-ulm.de/~mascheck/various/shebang/
EDIT: If your kernel really does support chained scripts, then a work-around for /usr/bin/bosh might be something like:
#!/bin/bash
if [ ! $PS1 ]; then
exec /bin/bash --init-file "$0" -i "$#"
fi
... rest of bosh init file ...
An exec seems to be unavoidable to get this to work the way you want it to.
A script is not a runtime environment. That may be your problem. The shebang defnies the runtime environment. ie... /bin/java /bin/python /bin/bash /bin/dash. Your script is not an environment. Your "wrapper example" would be appropriate.

Resources