Reliable way to require only bash shell in script - bash

I wonder if there are any reliable methods (cross-shell compatible) to require bash as shell interpreter for my script.
For example, I have shell script that can be run only with bash interpreter. Despite of #!/usr/bin/bash at the beginning of my script some smart user/hacker can run it directly with another shell: $ csh script.sh
This can lead to unwanted consequences.
I already thought about testing echo $0 output and exiting with error code but syntax for if statements (as long as for another conditional statements) is different among various shell interpreters. Testing directly for $BASH_VERSION variable is unreliable due to the same limitations.
Are there any cross-shell compatible and reliable way to determine current interpreter?
Thank you!
EDIT: as for now I have the following basic check for compatibility:
### error codes
E_NOTABASH=1
E_OLD_BASH=2
# perform some checks
if [ -z "$BASH_VERSION" ]
then
echo -e "ERROR: this script support only BASH interpreter! Exiting" >&2
exit $E_NOTABASH
fi
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]
then
echo -e "ERROR: this script needs BASH 4.0 or greater! Your current version is $BASH_VERSION. Exiting" >&2
exit $E_OLD_BASH
fi

Not entirely sure I understand the scope of the question.
A #! /usr/bin/env bash shebang will fail if there's no bash, but to keep it from being explicitly parsed by another shell, um...
How about -
case "$BASH_VERSION" in
4.*) : bash version 4+ so ok ;;
*) echo "please run only with bash v4+. Aborting."
exit 1 ;;
esac
If the syntax works, it is either right or hacked.
If it crashes, you're good. :)

you could check for the parent process id, command respectively
pstree -p $$ | grep -m 1 -oE '^[^\(]+'
or
ps $(ps -o ppid=$$)

Related

Definitively determine if currently running shell is bash or zsh

How can I definitively determine if the currently running shell is bash or zsh?
(being able to disambiguate between additional shells is a bonus, but only bash & zsh are 100% necessary)
I've seen a few ways to supposedly do this, but they all have problems (see below).
The best I can think of is to run some syntax that will work on one and not the other, and to then check the errors / outputs to see which shell is running. If this is the best solution, what command would be best for this test?
The simplest solution would be if every shell included a read-only parameter of the same name that identified the shell. If this exists, however, I haven't heard of it.
Non-definitive ways to determine the currently running shell:
# default shell, not current shell
basename "${SHELL}"
# current script rather than current shell
basename "${0}"
# BASH_VERSINFO could be defined in any shell, including zsh
if [ -z "${BASH_VERSINFO+x}" ]; then
echo 'zsh'
else
echo 'bash'
fi
# executable could have been renamed; ps isn't a builtin
shell_name="$(ps -o comm= -p $$)"
echo "${shell_name##*[[:cntrl:][:punct:][:space:]]}"
# scripts can be sourced / run by any shell regardless of shebang
# shebang parsing
On $ prompt, run:
echo $0
but you can't use $0 within a script, as $0 will become the script's name itself.
To find the current shell (let's say BASH) if shebang / magic number executable was #!/bin/bash within a script:
#!/bin/bash
echo "Script is: $0 running using $$ PID"
echo "Current shell used within the script is: `readlink /proc/$$/exe`"
script_shell="$(readlink /proc/$$/exe | sed "s/.*\///")"
echo -e "\nSHELL is = ${script_shell}\n"
if [[ "${script_shell}" == "bash" ]]
then
echo -e "\nI'm BASH\n"
fi
Outputs:
Script is: /tmp/2.sh running using 9808 PID
Current shell used within the script is: /usr/bin/bash
SHELL is = bash
I'm BASH
This will work, if shebang was: #!/bin/zsh (as well).
Then, you'll get the output for SHELL:
SHELL is = zsh
While there is no 100% foolproof way to achieve it, it might help to do a
echo $BASH_VERSION
echo $ZSH_VERSION
Both are shell variables (not environment variables), which are set by the respective shell. In the respective other shell, they are empty.
Of course, if someone on purpose creates a variable of this name, or exports such a variable and then creates a subshell of the different kind, i.e.
# We are in bash here
export BASH_VERSION
zsh # the subshell will see BASH_VERSION even though it is zsh
this approach will fail; but I think if someone is really doing such a thing, he wants to sabotage your code on purpose.
This should work for most Linux systems:
cat /proc/$$/comm
Quick and easy.
Working from comments by #ruakh & #oguzismail, I think I have a solution.
\shopt -u lastpipe 2> /dev/null
shell_name='bash'; : | shell_name='zsh'

Script to read user input not running when run with sh interpreter

This may be a duplicate of bash user input if, but the answers do not solved my problem, so I think there is something else.
I have the next script:
#!/bin/bash
echo "Do that? [Y,n]"
read input
if [[ $input == "Y" || $input == "y" ]]; then
echo "do that"
else
echo "don't do that"
fi
and when I do sh basic-if.sh
Also I have
#!/bin/bash
read -n1 -p "Do that? [y,n]" doit
case $doit in
y|Y) echo yes ;;
n|N) echo no ;;
*) echo dont know ;;
esac
and when I do sh basic-if2.sh
I think my bash has a problem because appereatly the other users didn't have these problems running those examples. Thanks
Running the script with sh scriptname overrides any default interpreter set inside your script. In your case the bourne shell (sh) runs the script instead of the bourne again shell (bash). The sh does not support [[ and the read command in its POSIX compliant form does not support -n flag.
In all likelihood, your sh in your system is not symlinked to bash and it is operating in itself as a POSIX compliant shell. Fix the problem by running
bash basic-if2.sh
or run it with a ./ before the script name there by making the system to look out for the interpreter in the first line of the file (#!/bin/bash). Instead of fixing the interpreter you could also do #!/usr/bin/env bash for the OS to look up where bash is installed and execute with that.
chmod a+x basic-if2.sh
./basic-if2.sh
You could additionally see if ls -lrth /bin/sh to see if its symlinked to dash which is a minimal POSIX compliant shell available on Debian systems.

How to check the current shell and change it to bash via script?

#!/bin/bash
if [ ! -f readexportfile ]; then
echo "readexportfile does not exist"
exit 0
fi
The above is part of my script. When the current shell is /bin/csh my script fails with the following error:
If: Expression Syntax
Then: Command not found
If I run bash and then run my script, it runs fine(as expected).
So the question is: If there is any way that myscript can change the current shell and then interpretate rest of the code.
PS: If i keep bash in my script, it changes the current shell and rest of the code in script doesn't get executed.
The other replies are correct, however, to answer your question, this should do the trick:
[[ $(basename $SHELL) = 'bash' ]] || exec /bin/bash
The exec builtin replaces the current shell with the given command (in this case, /bin/bash).
You can use SHEBANG(#!) to overcome your issue.
In your code you are already using she-bang but make sure it is first and foremost line.
$ cat test.sh
#!/bin/bash
if [ ! -f readexportfile ]; then
echo "readexportfile does not exist"
exit 0
else
echo "No File"
fi
$ ./test.sh
readexportfile does not exist
$ echo $SHELL
/bin/tcsh
In the above code even though I am using CSH that code executed as we mentioned shebang in the code. In case if there is no shebang then it will take the help of shell in which you are already logged in.
In you case you also check the location of bash interpreter using
$ which bash
or
$ cat /etc/shells |grep bash

Argument passing in a bash script

I've got following bash script to do something for each parameter of the script
#! /bin/sh
while (($#)); do
echo $1
shift
done
But somehow, if I start it with the command sudo ./test.sh foo1 foo2 it wont work. And the real strange thing is, that if I enter sudo bash test.sh foo1 foo2 it works. Does anybody know what causes this strange behaviour?
You have specified /bin/sh as your interpreter, which may not be bash. Even if it is bash, bash runs in POSIX mode when called as /bin/sh.
The (( )) command is a bash-specific feature. The following will work in any POSIX compliant shell:
while [ $# -gt 0 ]; do
echo $1
shift
done
Have you tried #!/bin/bash rather than sh?
Here's a link explaining the difference:
http://www.linuxquestions.org/questions/programming-9/difference-between-bin-bash-and-bin-sh-693231/
This will work in either sh or bash:
for arg
do
echo "$arg"
done
and it does the same thing as your script is intended to do without destroying the argument list.

Shell scripting: die on any error

Suppose a shell script (/bin/sh or /bin/bash) contained several commands. How can I cleanly make the script terminate if any of the commands has a failing exit status? Obviously, one can use if blocks and/or callbacks, but is there a cleaner, more concise way? Using && is not really an option either, because the commands can be long, or the script could have non-trivial things like loops and conditionals.
With standard sh and bash, you can
set -e
It will
$ help set
...
-e Exit immediately if a command exits with a non-zero status.
It also works (from what I could gather) with zsh. It also should work for any Bourne shell descendant.
With csh/tcsh, you have to launch your script with #!/bin/csh -e
May be you could use:
$ <any_command> || exit 1
You can check $? to see what the most recent exit code is..
e.g
#!/bin/sh
# A Tidier approach
check_errs()
{
# Function. Parameter 1 is the return code
# Para. 2 is text to display on failure.
if [ "${1}" -ne "0" ]; then
echo "ERROR # ${1} : ${2}"
# as a bonus, make our script exit with the right error code.
exit ${1}
fi
}
### main script starts here ###
grep "^${1}:" /etc/passwd > /dev/null 2>&1
check_errs $? "User ${1} not found in /etc/passwd"
USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1`
check_errs $? "Cut returned an error"
echo "USERNAME: $USERNAME"
check_errs $? "echo returned an error - very strange!"

Resources