In Deb package maintainer script, e.g. postinstall, prerm, etc. I think we often use /bin/sh as shell script interpreter. However, /bin/sh might be different on different distribution or user preferences. For example, Ubuntu link /bin/sh to /bin/dash, or some use might link to /bin/tcsh.
There are two type of syntax I encountered that might failed if user change shell to tcsh.
1) set -e # tcsh can't understand this.
2) > /dev/null 2>&1 # ambiguous redirection.
The first thought is to remove (1), and change (2) to >& /dev/null. However, I found in most maintainer script, the 'set -e' line are presented.
Now, I'm not sure should
a) I just fix all the problem and use /bin/sh
b) change to use /bin/bash
c) ignore tcsh case
Can anyone provide some suggestions?
Thank you.
Jack
The Debian policy doesn't force you to use /bin/sh as shell, you can use any other one provided you name it correctly (with the Shebang) and it is available on the system you try to install to (which limits you to default provided shells if you use it in preinst scripts).
But the Debian policy tells you that /bin/sh, whatever that really is, will always implement the SUSv3 Shell Command Language (i.e.: a shell that is POSIX compliant) and some additional features. That is what you can rely on for your maintainer scripts.
On Debian, /bin/sh was /bin/bash until Lenny. From Squeeze, it is now /bin/dash. See the Debian wiki for more information.
On Debian or any derivatives, if /bin/sh is actually linked to /bin/tcsh it will be a local setting changed by the local administrator. That will probably not only break your scripts but many others. The Debian policy document actually tell maintainers to avoid csh and tcsh as scripting languages.
In conclusion, I think you shouldn't bother about your scripts being compatible with tcsh and the best practice would be to only use /bin/sh for all your packaging needs.
Always refer to the full Debian policy document when in doubt.
Related
I typically put a shebang for bash at the top of my shell scripts, e.g.:
#!/usr/bin/bash
However I see many other variants of this, like #!/bin/bash or #!/usr/local/bin/bash etc.
It seems to me these different conventions would result in compatibility or portability issues. If my bash is at another location than someone else's, my script won't work on their machine and vice versa.
If a shell interpreter like bash is apparently not always at the same location, isn't it plain WRONG to explicitly use a hardcoded path in a script?
I understood you can use a somewhat more flexible or less system-dependent approach like this:
#!/usr/bin/env bash
Which results in the (or a?) local version of bash, wherever that may be installed.
Does the latter variant always work? Or is there a better approach that has the highest chance of referring to any system's bash regardless of where it's installed?
I would recommend either "#!/bin/bash" or "#!/usr/bin/bash". On a modern Linux distro, bash should be installed in both places.
Apparently, that isn't true for OpenBSD ... which uses ksh as the default shell. But on an OpenBSD system, you are liable to find that bash isn't installed at all. It is apparently an optional package, and the admin may have not installed it.
So, if you want to maximize portability, use "/bin/sh" and restrict yourself to standard POSIX shell syntax and commands. "/bin/sh" is typically a link to bash or ksh, and runs in POSIX compliant mode.
Other variations:
"#!/usr/local/bin/bash" typically won't work on Linux. If it does, it may give you a locally built / modified version of bash.
"#!/usr/bin/env bash" should work, with a couple of caveats:
This will give you whatever version of bash is first on the user's command search path (i.e. $PATH).
It is conceivable that the path to env may be different, or that it may not exist. (The env command wasn't in the first version of the POSIX specs.)
The default user prompt for FreeBSD 10.1 on the console and SSH is always $, no matter what directory the shell is currently in. How do I change this to user#machine /full/path/to/current/directory $ or similar with the full path?
The default user shell in FreeBSD is the Bourne shell /bin/sh. You change the prompt by setting the PS1 variable. Do this on the command line:
export PS1="`whoami`#\H:\w\$ "
To have it done automatically at every login you should change the configuration file in your home directory .shrc.
The .shrc file already have an alternative prompt you could use - you just need to uncomment these lines:
# set prompt: ``username#hostname$ ''
PS1="`whoami`#`hostname | sed 's/\..*//'`"
case `id -u` in
0) PS1="${PS1}# ";;
*) PS1="${PS1}$ ";;
esac
If you want to have the directory as well you can do the same as me. Replace all of the lines above with:
PS1="`whoami`#\H:\w\$ "
The case structure is not needed because of "\$" which sets the prompt for $ or # (user/root).
The Bourne Shell is quite powerful and command line editing is readily available in the FreeBSD variant. I would recommend you to stick with it as any script you may write will be highly portable. Be aware that the Bourne shell in FreeBSD is more capable than on Linux. This is in part why bash is predominant on Linux. The default shell on FreeBSD is more usable out of the box. If you are used to Linux you can change to bash to feel more at home. If not - then I would spend the time to learn Bourne on FreeBSD. If you outgrow that - then I would look at something like "zsh". But if your level is at figuring out "PS1" I would strongly recommend to stick with the defaults!
There are a couple of comments to your question which I feel is bad advice:
export PS1='\u#\H: \W $' is a bash-ism. That is only applicable if you use the bash shell. FreeBSD Bourne does not have "\u".
For historic reasons the shell for "root" is set for "csh". The csh shell in FreeBSD is the "tcsh" variant. It is however only set for root - and you should never log in as root! All users have the Bourne shell as default. I would recommend against using "csh". Rather than su to "root" you could do a "su - toor" which is an alternate root account without the csh shell. This is important as you should not change the root shell away from csh!
There is absolutely no reason to change shell just to get a suitable prompt.
Update:
There are several reasons you should not change the shell of the root user:
No need! The best practice is to never log in as the root user interactively. If you do - you are doing it wrong. If you find yourself logging in as a regular user and still wants to use the root user interactively - you can still do that easily in several ways using sudo -s or su root -c "/path/to/shell". Make a good habit of using root permissions rather than the root user. Most of the time you should be using sudo and not an interactive root shell.
Predictability. You might not be the only admin. Or you might suffer pain.
Single user mode. If your system is having problems you can end up having only mounted /bin. In those cases it is very important that the shell is statically linked and placed in /bin. Third-party shells are usually placed in /usr/local/bin (and dynamically linked) which might not be mounted in your emergency situation.
You have the toor user for this exact reason. It has the same uid and gid as root. You can set the shell to your hearts desire for the toor user and have a clean root account. Simply use su - toor rather than su - (or just create and alias for su).
References:
How to set the PS1 prompt in different shells: http://www.understudy.net/custom.html
Top Ten Reasons not to use the C shell: http://www.grymoire.com/unix/CshTop10.txt
Csh Programming Considered Harmful: http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/
man page with PS1 variable for the Bourne Shell https://www.freebsd.org/cgi/man.cgi?query=sh
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 have some code which works for me, but when I gave it to some colleagues, it broke. They're using tcsh whereas, as far as I can figure, I'm using csh.
I've tried:
csh -v
csh --version
csh -V
csh --help
csh -h
with no success (they all just take me straight to the interpreter prompt). I've also grepped the man page for the string "version", but I didn't come up with anything useful there either. Is there a way to determine the version of csh that I have installed?
-- Edit --
Following the symbolic links from /bin/csh, they seem to terminate at /bin/bsd-csh which seems to imply that I'm using some BSD flavor csh shell if that helps anyone. Also, I'm using ubuntu linux.
In comments, you've indicated that you're on Ubuntu, and that /bin/csh is a symlink to /etc/alternatives/csh, which is a symlink to /bin/csh.
The csh shell originated on BSD Unix, so it's not surprising that csh is an indirect symlink to /bin/bsd-csh. It goes back to 1978 or so, before it became common for Unix program to report their own version numbers.
Since you're on Ubuntu, this:
dpkg -l csh
should tell you what version you have -- though the version number of the Debian/Ubuntu package isn't likely to be more useful than the information you already have. And the relationship between the package version number and the version of the shell isn't entirely clear.
I'm assuming that's the right package name. If not, try dpkg -S /bin/bsd-csh.
You can tell whether you're running tcsh or not, like this:
if ($?tcsh) then
echo This is tcsh
else
echo This is csh, not tcsh
endif
tcsh is supposed to be backward compatible with csh, with some extra features, mostly for interactive use. A script written for tcsh could easily fail under csh if it uses tcsh-specific features, but I'd expect tcsh to be able to handle a csh script. As the tcsh(1) man page says:
tcsh is an enhanced but completely compatible version of the Berkeley UNIX C shell, csh(1).
I understand you probably can't post the entire failing script, but can you post a small representative example that works for you and fails for your colleagues?
One solution might be to ask you colleagues to install vanilla csh on their systems; they can still use /bin/tcsh as their interactive shell if they like, but #!/bin/csh would then cause the script to be executed by the old csh, not tcsh.
Finally, I can't answer a question about [t]csh scripting without adding a link to this.
Addendum: I have access to a Solaris system with a /bin/csh that isn't tcsh; I can run some simple tests there if you like. One data point: both /bin/tcsh and /bin/csh accept : as a null command, but with /bin/csh it doesn't accept arguments:
% :
% arg
:: Too many arguments
csh --version would work only if csh is actually a symlink to... /bin/tcsh(!)
Otherwise, a csh session doesn't seem to set any version, except in this MKSToolkit, where that csh is supposed to set a variable $csh_version.
Pre-defined Variables
The following variables have special meaning to the C Shell.
Of these, argv, csh_version, cwd, home, path, prompt, ROOTDIR, shell, status, COMSPEC, and TMPDIR are always set by the shell.
Except for cwd and status, this setting occurs only at initialization; these variables are then not modified unless done explicitly by the user.
See this dotfile for instance:
shell_is_csh () { return [ -n "$csh_version" ]; }
/bin/csh links to /etc/alternatives/csh which links to /bin/bsd-csh.
Apparently it's bsd-csh
... then bsd-csh doesn't seem to support any kind of version feature.
If you're using Ubuntu, then your csh is the OpenBSD version, with a few patches. It has absolutely no support for getting its version, being that it doesn't encode its version anywhere. Can't help you beyond that, though.
If you've not installed csh on ubuntu, but have installed tcsh, then it will use tcsh as an alias to csh. They are, as you've discovered, mostly compatible.
For yourself, you can probably debug the issue by using update-alternatives to redirect csh to tcsh (as long as you've installed tcsh as well).
I'm running autoconf and configure sets SHELL to '/bin/sh'.
This creates huge problems. How to force SHELL to be '/bin/bash' for autoconf?
I'm trying to get this running on osx, it's working on linux. Linux is using SHELL=/bin/bash. osx defaults to /bin/sh.
I have similar problems on Solaris with GCC - and I use the 'standard' technique:
CONFIG_SHELL=/bin/bash ./configure ...
(Or, actually, I use /bin/ksh, but setting the CONFIG_SHELL env var allows you to tell autoconf scripts which shell to use.)
I checked the configure script for git and gd (they happened to be extracted) to check that this wasn't a GCC peculiar env var.
What are the "huge problems"? autoconf works very hard to generate a configure script that works with a very large percentage of shells. If you have an example of a construct that autoconf is writing that is not portable, please report it to the autoconf mailing list. On the other hand, if the problems you are experiencing are a result of your own shell code in configure.ac not being portable (eg, you're using bashisms) then the solution is to either stop using non-portable code or require the user to explicitly set SHELL or CONFIG_SHELL at configure time.
It sounds like the problem you are experiencing is in the environment of the user running configure. On Linux, your user has SHELL set to /bin/bash, but on OS X it is set to /bin/sh. The configure script generated by autoconf does some initial tests of the shell it is running under and does attempt to re-exec itself using a different shell if the provided shell lacks certain features. However, if you are introducing non-portable shell code in configure.ac, then you are violating one of the main philosophy's of autoconf -- namely that configure scripts should be portable. If you truly want to use bashisms in your shell code, then you are requiring your user to pass SHELL=/bin/bash as an argument to the configure script. This is not a bug in autoconf, but would be considered by many to be a bug in your project's build.
Autoconf is supposed to solve portability problems by generating a script which can run "anywhere". That's why it generates bizarre code like:
if test X$foo = X ; then ... # check if foo is empty
rather than:
if [ "$x" = "" ] ; then ...
That kind of crufty code probably once allowed these scripts to run on some ancient Ultrix system or whatever.
An configure script not running because of shell differences is like coming to a Formula-1 race with 10 liters of gas, and three spare tires.
If you're developing a configure script with Autoconf, and it is sensitive to whether the shell is Bash or the OSX shell, you are doing something wrong, or the Autoconf people broke something. If it's from you, fix whatever shell pieces you are adding to the script by making them portable.
Where is SHELL being set to that? What is being run with /bin/sh when you want /bin/bash?
configure scripts are meant to run anywhere, even on the horribly broken and non-Bash shells that exist in the wild.
Edit: What exactly is the problem?
Another edit: Perhaps you'd like the script to re-execute itself, something like this. It's probably buggy:
if test "$SHELL" = "/bin/sh" && test -x /bin/bash; then
exec /bin/bash -c "$0" "$#"
fi
ln -f /bin/bash /bin/sh
:-P (No, it's not a serious answer. Please don't hose your system by doing it!)