Related
I have read that if you want to use Bash in a portable way you should use the shebang:
#!/usr/bin/env bash
But now I am wondering: When I want to state explicitly that I do not rely on Bash, but instead, wrote a POSIX compliant script, should I use:
#!/bin/sh
Or is #!/usr/bin/env sh preferable here too?
Formal perspective
The informative section of the POSIX specification for
sh: Application Usage states
that you cannot rely on the sh executable being installed at /bin/sh.
Applications should note that the standard PATH to the shell cannot be
assumed to be either /bin/sh or /usr/bin/sh, and should be
determined by interrogation of the PATH returned by
getconf
PATH, ensuring that the returned pathname is an absolute pathname and not
a shell built-in.
For example, to determine the location of the standard sh utility:
command -v sh
However, instead of suggesting the use of env to use the appropriate PATH,
it suggests that shell scripts should be modified at installation time to use
the full path to sh:
Furthermore, on systems that support executable scripts (the "#!"
construct), it is recommended that applications using executable scripts
install them using
getconf
PATH to determine the shell pathname and update the "#!" script
appropriately as it is being installed (for example, with
sed).
In practice
I mostly write POSIX shell scripts and, in practice, every GNU/Linux system
(Red Hat and Debian-based) – and others such as Cygwin and OS X – has a
POSIX-compliant sh either installed to /bin/sh or available as a soft or
hard link at this path. I’ve never needed to use env to cater for systems
where sh does not use this path.
There may be some Unix systems where a POSIX-compliant sh is not available
as /bin/sh. The POSIX specification suggests that it might be installed on
some systems as /usr/xpg4/bin/sh. As I understand it, this is (was?) true
for Solaris systems where /bin/sh is an earlier version of the Bourne shell
which predates POSIX. In this case, using env sh would not be guaranteed to help as it could still find the Bourne shell (at /bin/sh) before the POSIX shell at /usr/xpg4/bin/sh.
Summary
If you’re writing POSIX shell scripts for common Unix and Linux operating
systems, simply use #!/bin/sh as the shebang.
In rare cases where /bin/sh is a Bourne shell instead of a POSIX-compliant
shell, you would have to modify the shebang to use the appropriate full path
to the POSIX shell.
In either case, there’s no benefit to using #!/usr/bin/env sh – and would be
more likely to fail than simply using #!/bin/sh.
I would say #!/usr/bin/env sh is preferable.
Some portable constructs, e.g. the loop over the results of find, require sh to be called explicitly. Consider this script:
#!/bin/sh
# ... some commands here ...
find . -exec sh -c '
for file do
# ... commands processing "$file" ...
done' find-sh {} +
The commands at the beginning will be run by /bin/sh, and the commands processing "$file" will be run by whatever sh that comes first in the PATH, which may behave differently than /bin/sh. This is a potential source of unexpected bugs. The #!/usr/bin/env sh shebang solves this problem, as all the commands will be run by the sh that is first in your PATH.
The only potential disadvantage of the #!/usr/bin/env sh shebang is the fact that /usr might not be mounted at the time of invoking the script. However, this shouldn't occur often in practice. External programs frequently used in portable scripts, such as awk, are also often found in /usr/bin, so it might be difficult to make sure the script runs correctly with /usr unmounted anyway.
If you really want to be portable and not depend on /usr being mounted, you can begin your script as follows, to make sure it is always executed by sh from the PATH, wherever it is:
#!/bin/sh
if test X"$SUBSHELL" != X"1"; then
SUBSHELL=1
export SUBSHELL
exec sh "$0" "$#"
exit 1
fi
# ... your actual script comes here ...
But it does seem to be a bit of an overkill, so I'd say the #!/usr/bin/env sh shebang is a reasonable compromise.
This is a bash weirdness that's been bugging me.
I'm on OSX.
I've installed the android SDK, and as a result the adb tool is located in a folder in my home directory. That folder appears in my path as reported by env as ~/Development/android_sdk_latest/platform-tools.
adb itself runs just fine, but when I do which adb the result is empty. If I do command -v adb, the result is the full path, as expected: /Users/me/Development/android_sdk_latest/platform-tools/adb
adb does not appear in my aliases.
What subtlety of bash paths or which am I in the dark on?
You've run into a subtle incompatibility between /bin/bash and the which command.
On my system (Linux Mint), the which command is actually a shell script, not a built-in command, and its first line is #! /bin/sh. That means that it uses /bin/sh's handling of the $PATH variable.
This can vary depending on how /bin/sh is set up (it's sometimes a symlink to /bin/bash), but a little experimentation shows that bash handles a literal ~ character in $PATH as if it were the full path to your home directory, but /bin/sh does not. Since you have
~/Development/android_sdk_latest/platform-tools
as one of the elements of your $PATH, bash (your interactive shell) can find the adb command, but sh (the shell used by which) cannot.
On some systems, apparently including your OSX system, which is a binary executable. Again, since it's not a bash script, it's not going to match bash's treatment of $PATH.
I recommend making two changes.
First, don't put a literal ~ in your $PATH. For example, to append the platform-tools directory to your $PATH, rather than this:
export PATH="$PATH:~/Development/android_sdk_latest/platform-tools" # BAD!
do this:
export PATH="$PATH:$HOME/Development/android_sdk_latest/platform-tools"
The $HOME will expand to the path to your home directory (when you run the export command, not when you use $PATH later). ~ is not expanded within double-quoted strings.
Second, rather than using the which command, use the type command that's built into bash. type will follow the rules of your current shell rather than those of /bin/sh, and it will be able to report shell functions and aliases that which is unable to see. It has several useful command-line options; type help type at a bash prompt for details.
The bash shell's treatement of ~ characters in $PATH is documented in the bash manual's section on Tilde Expansion.
I've seen in a number of places, including recommendations on this site (What is the preferred Bash shebang?), to use #!/usr/bin/env bash in preference to #!/bin/bash. I've even seen one enterprising individual suggest using #!/bin/bash was wrong and bash functionality would be lost by doing so.
All that said, I use bash in a tightly controlled test environment where every drive in circulation is essentially a clone of a single master drive. I understand the portability argument, though it is not necessarily applicable in my case. Is there any other reason to prefer #!/usr/bin/env bashover the alternatives and, assuming portability was a concern, is there any reason using it could break functionality?
#!/usr/bin/env searches PATH for bash, and bash is not always in /bin, particularly on non-Linux systems. For example, on my OpenBSD system, it's in /usr/local/bin, since it was installed as an optional package.
If you are absolutely sure bash is in /bin and will always be, there's no harm in putting it directly in your shebang—but I'd recommend against it because scripts and programs all have lives beyond what we initially believe they will have.
The standard location of bash is /bin, and I suspect that's true on all systems. However, what if you don't like that version of bash? For example, I want to use bash 4.2, but the bash on my Mac is at 3.2.5.
I could try reinstalling bash in /bin but that may be a bad idea. If I update my OS, it will be overwritten.
However, I could install bash in /usr/local/bin/bash, and setup my PATH to:
PATH="/usr/local/bin:/bin:/usr/bin:$HOME/bin"
Now, if I specify bash, I don't get the old cruddy one at /bin/bash, but the newer, shinier one at /usr/local/bin. Nice!
Except my shell scripts have that !# /bin/bash shebang. Thus, when I run my shell scripts, I get that old and lousy version of bash that doesn't even have associative arrays.
Using /usr/bin/env bash will use the version of bash found in my PATH. If I setup my PATH, so that /usr/local/bin/bash is executed, that's the bash that my scripts will use.
It's rare to see this with bash, but it is a lot more common with Perl and Python:
Certain Unix/Linux releases which focus on stability are sometimes way behind with the release of these two scripting languages. Not long ago, RHEL's Perl was at 5.8.8 -- an eight year old version of Perl! If someone wanted to use more modern features, you had to install your own version.
Programs like Perlbrew and Pythonbrew allow you to install multiple versions of these languages. They depend upon scripts that manipulate your PATH to get the version you want. Hard coding the path means I can't run my script under brew.
It wasn't that long ago (okay, it was long ago) that Perl and Python were not standard packages included in most Unix systems. That meant you didn't know where these two programs were installed. Was it under /bin? /usr/bin? /opt/bin? Who knows? Using #! /usr/bin/env perl meant I didn't have to know.
And Now Why You Shouldn't Use #! /usr/bin/env bash
When the path is hardcoded in the shebang, I have to run with that interpreter. Thus, #! /bin/bash forces me to use the default installed version of bash. Since bash features are very stable (try running a 2.x version of a Python script under Python 3.x) it's very unlikely that my particular BASH script will not work, and since my bash script is probably used by this system and other systems, using a non-standard version of bash may have undesired effects. It is very likely I want to make sure that the stable standard version of bash is used with my shell script. Thus, I probably want to hard code the path in my shebang.
There are a lot of systems that don't have Bash in /bin, FreeBSD and OpenBSD just to name a few. If your script is meant to be portable to many different Unices, you may want to use #!/usr/bin/env bash instead of #!/bin/bash.
Note that this does not hold true for sh; for Bourne-compliant scripts I exclusively use #!/bin/sh, since I think pretty much every Unix in existence has sh in /bin.
For invoking bash it is a little bit of overkill. Unless you have multiple bash binaries like your own in ~/bin but that also means your code depends on $PATH having the right things in it.
It is handy for things like python though. There are wrapper scripts and environments which lead to alternative python binaries being used.
But nothing is lost by using the exact path to the binary as long as you are sure it is the binary you really want.
#!/usr/bin/env bash
is definitely better because it finds the bash executable path from your system environment variable.
Go to your Linux shell and type
env
It will print all your environment variables.
Go to your shell script and type
echo $BASH
It will print your bash path (according to the environment variable list) that you should use to build your correct shebang path in your script.
I would prefer wrapping the main program in a script like below to check all bash available on system. Better to have more control on the version it uses.
#! /usr/bin/env bash
# This script just chooses the appropriate bash
# installed in system and executes testcode.main
readonly DESIRED_VERSION="5"
declare all_bash_installed_on_this_system
declare bash
if [ "${BASH_VERSINFO}" -ne "${DESIRED_VERSION}" ]
then
found=0
all_bash_installed_on_this_system="$(\
awk -F'/' '$NF == "bash"{print}' "/etc/shells"\
)"
for bash in $all_bash_installed_on_this_system
do
versinfo="$( $bash -c 'echo ${BASH_VERSINFO}' )"
[ "${versinfo}" -eq "${DESIRED_VERSION}" ] && { found=1 ; break;}
done
if [ "${found}" -ne 1 ]
then
echo "${DESIRED_VERSION} not available"
exit 1
fi
fi
$bash main_program "$#"
Normally #!path/to/command will trigger bash to prepend the command path to the invoking script when executed. Example,
# file.sh
#!/usr/bin/bash
echo hi
./file.sh will start a new process and the script will get executed like /bin/bash ./file.sh
Now
# file.sh
#!/usr/bin/env bash
echo hi
will get executed as /usr/bin/env bash ./file.sh which quoting from the man page of env describes it as:
env - run a program in a modified environment
So env will look for the command bash in its PATH environment variable and execute in a separate environment where the environment values can be passed to env like NAME=VALUE pair.
You can test this with other scripts using different interpreters like python, etc.
#!/usr/bin/env python
# python commands
Your question is biased because it assumes that #!/usr/bin/env bash is superior to #!/bin/bash. This assumption is not true, and here's why:
env is useful in two cases:
when there are multiple versions of the interpreter that are incompatible.
For example python 2/3, perl 4/5, or php 5/7
when the location depends on the PATH, for instance with a python virtual environment.
But bash doesn't fall under any of these two cases because:
bash is quite stable, especially on modern systems like Linux and BSD which form the vast majority of bash installations.
there's typically only one version of bash installed under /bin.
This has been the case for the past 20+ years, only very old unices (that nobody uses any longer) had a different location.
Consequently going through the PATH variable via /usr/bin/env is not useful for bash.
Add to these three good resons to use #!/bin/bash:
for system scripts (when not using sh) for which the PATH variable may not contain /bin.
For example cron defaults to a very strict PATH of /usr/bin:/bin which is fine, sure, but other context/environments may not include /bin for some peculiar reason.
when the user screwed-up his PATH, which is very common with beginners.
for security when for example you're calling a suid program that invokes a bash script. You don't want the interpreter to be found via the PATH variable which is entirely under the user's control!
Finally, one could argue that there is one legitimate use case of env to spawn bash: when one needs to pass extra environment variables to the interpreter using #!/usr/bin/env -S VAR=value bash.
But this is not a thing with bash because when you're in control of the shebang, you're also in control of the whole script, so just add VAR=value inside the script instead and avoid the aforementioned problems introduced by env with bash scripts.
I rarely touch shell scripts, we have another department who write them, so I have an understanding of writing them but no experience. However they all appear rather useless with my issue.
I am trying to execute some KornShell (ksh) scripts on a windows based machine using Cygwin- we use these to launch our Oracle WebLogic servers, now it simply will not execute. I used to be able to execute these exact same scripts fine on my old machine.
Now I have narrowed this down to the fact the 'magic number' or whatever it is at the start of the script where it specifies the script interpreter path:
i.e.:
#!/bin/ksh
if I change it to execute as a simple bash it works i.e:
#!/bin/sh
I went through checking the packages installed for cygwin - now the shells I installed are:
mksh MirdBSD KornShell
bash the bourne again shell
zsh z shell
Should I expect to see a ksh.exe in my cygwin/bin directory? there is a system file 'ksh' which I was making an assume somehow associates it with one of the other shell exes, like mksh.exe
I understand my explanation may well be naff. But that being said, any help would be very much appreciated.
Thanks.
I believe the MirBSD korn shell is called mksh. You can verify this and look for the correct path by typing
% which mksh
% which ksh
or if you have no which,
% type -p mksh
% type -p ksh
or if that fails too, check /etc/shells which should list all valid shells on a system:
% grep ksh /etc/shells
You need to put the full path after the #! line. It will probably be /bin/mksh, so your line needs to look like:
#!/bin/mksh
You've probably fixed it by now, but the answer was no, your Cygwin does not (yet) know about ksh.
I solved this problem by launching the cygwin setup in command-line mode with the -P ksh attribute (as described in http://www.ehow.com/how_8611406_install-ksh-cygwin.html).
You can run a ksh using a bat file
C:\cygwin\bin\dos2unix kshfilename.ksh
C:\cygwin\bin\bash kshfilename.ksh
Running a shell script through Cygwin on Windows
Install KornShell (ksh) into Cygwin by the following process:
Download: ksh.2012-08-06.cygwin.i386.gz
Install ksh via Cygwin setup.
Execture Cygwin setup.exe
Choose: Install from Local Directory
Select the ksh.2012-08-06.cygwin.i386.gz as the Local Package Directory.
Complete Cygwin setup.
Restart Cygwin.
How can I tell what type my shell is? ie, whether it's traditional sh, bash, ksh, csh, zsh etc.
Note that checking $SHELL or $0 won't work because $SHELL isn't set by all shells, so if you start in one shell and then start a different one you may still have the old $SHELL.
$0 only tells you where the shell binary is, but doesn't tell you whether /bin/sh is a real Bourne shell or bash.
I presume that the answer will be "try some features and see what breaks", so if anyone can point me at a script that does that, that'd be great.
This is what I use in my .profile:
# .profile is sourced at login by sh and ksh. The zsh sources .zshrc and
# bash sources .bashrc. To get the same behaviour from zsh and bash as well
# I suggest "cd; ln -s .profile .zshrc; ln -s .profile .bashrc".
# Determine what (Bourne compatible) shell we are running under. Put the result
# in $PROFILE_SHELL (not $SHELL) so further code can depend on the shell type.
if test -n "$ZSH_VERSION"; then
PROFILE_SHELL=zsh
elif test -n "$BASH_VERSION"; then
PROFILE_SHELL=bash
elif test -n "$KSH_VERSION"; then
PROFILE_SHELL=ksh
elif test -n "$FCEDIT"; then
PROFILE_SHELL=ksh
elif test -n "$PS3"; then
PROFILE_SHELL=unknown
else
PROFILE_SHELL=sh
fi
It does not make fine distinctions between ksh88, ksh95, pdksh or mksh etc., but in more than ten years it has proven to work for me as designed on all the systems I were at home on (BSD, SunOS, Solaris, Linux, Unicos, HP-UX, AIX, IRIX, MicroStation, Cygwin.)
I don't see the need to check for csh in .profile, as csh sources other files at startup.
Any script you write does not need to check for csh vs Bourne-heritage because you explicitly name the interpreter in the shebang line.
Try to locate the shell path using the current shell PID:
ps -p $$
It should work at least with sh, bash and ksh.
If the reason you're asking is to try to write portable shell code, then spotting the shell type, and switching based on it, is an unreliable strategy. There's just too much variation possible.
Depending on what you're doing here, you might want to look at the relevant part of the autoconf documentation. That includes an interesting (and in some respects quite dismal) zoology of different shell aberrations.
For the goal of portable code, this section should be very helpful. If you do need to spot shell variants, then there might be some code buried in autoconf (or at least in one of the ./configure scripts it generates) which will help with the sniffing.
You can use something like this:
shell=`cat /proc/$$/cmdline`
Oh, I had this problem. :D
There is a quick hack, use ps -p $$ command to list the process with PID of the current running process -- which is your SHELL. This returns a string table structure, if you want, you can AWK, or SED the shell out...
The system shell is the thing you see when you open up a fresh terminal window which is not set to something other than bash (assuming this is your default SHELL).
echo $SHELL
Generally, you can find out all the constants defined by running
set
If the output is a lot of stuff then run
set | less
so you can scroll it from the top of the command line or
set > set.txt
To save the output to a file.
Invoking a different interactive shell to bash in your terminal does not mean that your system shell gets changed to something else i.e. your system shell is set to bash although you invoke a csh shell from a bash shell just that one session.
The above means that typing /bin/csh or /bin/python in bash or whatever does not set the system shell to the shell you invoked, at all.
If you really want to see the SHELL constant change then you need to set it to something else. If successful you should see the new shell whenever you open a fresh terminal...
It's old thread but...
In GNU environment You can sh --help and get something like
BusyBox v1.23.2 (2015-04-24 15:46:01 GMT) multi-call binary.
Usage: sh [-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]
Unix shell interpreter
So, the first line is shell type =)