18

In my .profile, I use the following code to ensure that Bash-related aliases and functions are only sourced if the login shell actually is Bash:

# If the current (login) shell is Bash, then
if [ "${BASH_VERSION:-}" ]; then
  # source ~/.bashrc if it exists.
  if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
  fi
fi

I’m currently in the process of putting my shell configuration files, scripts and functions under version control. I’ve also recently started the process of removing casual Bashisms from shell scripts that don’t benefit from Bash-specific features, e.g., replacing function funcname() with funcname().

For my shell files repository, I’ve configured a pre-commit hook that runs the checkbashisms utility from Debian’s devscripts package on each sh file in the repository to ensure that I don’t inadvertently introduce Bash-specific syntax. However, this raises an error for my .profile:

possible bashism in .profile line 51 ($BASH_SOMETHING):
if [ "${BASH_VERSION:-}" ]; then

I was wondering if there was a way to check which shell is running that wouldn’t trigger a warning in checkbashisms.

I checked the list of shell-related variables listed by POSIX in the hope that one of them could used to show the current shell. I’ve also looked at the variables set in an interactive Dash shell but, again, failed to find a suitable candidate.

At the moment, I’ve excluded .profile from being processed by checkbashisms; it’s a small file so it’s not hard to check it manually. However, having researched the issue, I’d still like to know if there is a POSIX compliant method to determine which shell is running (or at least a way that doesn’t cause checkbashisms to fail).


Further background/clarification

One of the reasons I’m putting my shell configuration files under version control is to configure my environment on all the systems I currently log in to on a regular basis: Cygwin, Ubuntu and CentOS (both 5 and 7, using Active Directory for user authentication). I most often log on via X Windows / desktop environments and SSH for remote hosts. However, I’d like this to be future proof and have the least reliance on system dependencies and other tools as possible.

I’ve been using checkbashisms as a simple, automated sanity check for the syntax of my shell-related files. It’s not a perfect tool, e.g., I’ve already applied a patch to it so that it doesn’t complain about the use of command -v in my scripts. While researching, I’ve learned that the program’s actual purpose is to ensure compliance with Debian policy which, as I understand it, is based on POSIX 2004 rather than 2008 (or its 2013 revision).

4 Answers4

17

Usually one uses $0 for this purpose. On the site which you linked it stands:

0
   (Zero.) Expands to the name of the shell or shell script.
jimmij
  • 47,140
  • So simple! I've become so used to using $0 for the name of a shell script, that I clean forgot that it can also refer to the current shell. Thanks! – Anthony Geoghegan Mar 02 '16 at 15:08
  • 1
    As usual, this convention is respected by all well behaved programs but a malicious program can mess with you by using various system calls from the exec family because they allow the calling program to identify the binary to be run separately from the string to be passed as argument 0. – dmckee --- ex-moderator kitten Mar 02 '16 at 21:15
  • That does work for the .profile :) cute – Rob Mar 02 '16 at 21:41
16

Your

# If the current (login) shell is Bash, then
if [ "${BASH_VERSION:-}" ]; then
  # source ~/.bashrc if it exists.
  if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
  fi
fi

code is completely POSIX conformant and the best way to check that you're currently running bash. Of course the $BASH_VERSION variable is bash-specific, but that's specifically why you're using it! To check that you're running bash!

Note that $BASH_VERSION will be set whether bash is invoked as bash or sh. Once you've asserted that you're running bash, you can use [ -o posix ] as an indicator that the shell was invoked as sh (though that option is also set when POSIXLY_CORRECT is in the environment or bash is called with -o posix, or with SHELLOPTS=posix in the environment. But in all those cases, bash will behave as if called as sh).


Another variable you could use instead of $BASH_VERSION and that checkbashism doesn't seem to complain about unless passed the -x option is $BASH. That is also specific to bash so one you should also be able to use to determine whether you're running bash or not.


I'd also argue it's not really a proper use of checkbashisms. checkbashisms is a tool to help you write portable sh scripts (as per the sh specification in the Debian policy, a superset of POSIX), it helps identify non-standard syntax introduced by people writing scripts on systems where sh is a symlink to bash.

A .profile is interpreted by different shells, many of which are not POSIX compliant. Generally, you don't use sh as your login shell, but shells like zsh, fish or bash with more advanced interactive features.

bash and zsh, when not called as sh and when their respective profile session file (.bash_profile, .zprofile) are not POSIX conformant (especially zsh) but still read .profile.

So it's not POSIX syntax you want for .profile but a syntax that is compatible with POSIX (for sh), bash and zsh if you're ever to use those shells (possibly even Bourne as the Bourne shell also reads .profile but is not commonly found on Linux-based systems).

checkbashisms would definitely help you find out bashisms but may not point out POSIX syntax that is not compatible with zsh or bash.

Here, if you want to use bash-specific code (like the work around of that bash bug whereby it doesn't read ~/.bashrc in interactive login shells), a better approach would be to have ~/.bash_profile do that (before or after sourcing ~/.profile where you put your common session initialisations).

  • That doesn't pass checkbashisms because of the variable used. – Thomas Dickey Mar 02 '16 at 22:11
  • 2
    @ThomasDickey, yes but that's a case where the checkbashisms report should be ignored. All the other solutions given so far are considerably worse. – Stéphane Chazelas Mar 02 '16 at 22:22
  • @StéphaneChazelas You say that all the other solutions are worse. What would be wrong with using $0 for this particular case? – Anthony Geoghegan Mar 02 '16 at 23:04
  • 2
    @AnthonyGeoghegan That doesn't distinguish between bash called as sh and some other shell called as sh. – Gilles 'SO- stop being evil' Mar 02 '16 at 23:16
  • It also gives a false positive if dash or another non-bash shell was incorrectly called with argv[0] == "bash", which is entirely legal, if unlikely. – Kevin Mar 03 '16 at 05:35
  • I agree that using checkbashisms on .profile is not a proper usage and I’d already excluded it from the list of files being checked. However, the additional information has provided more solid reasons why not to do this. For my shell files repository, I’ve already implemented Gilles’ suggestion of having .bash_profile source .profile. Finally, I’ve accepted this answer as it not only explains how to determine the current shell in a POSIX compliant way and how to use checkbashisms, it provides useful context on writing portable shell (including non-POSIX shells) start-up files. – Anthony Geoghegan Mar 03 '16 at 16:43
5

Usually the SHELL environment variable tells you the default shell. You should not need to manually source the .bashrc file (not true see update bellow), bash should do this automatically just make sure it is in the $HOME directory.

The other option is to do something like:

 ps -o cmd= $$

that will tell you the command (without arguments, the = without a value will not display column headers) of the current process. Example output:

 $ps -o cmd= $$
 bash
 $sh
 $ps -o cmd= $$
 sh

UPDATE:

I stand corrected! :)

The .bashrc is not always sourced as was mentioned in the comments bellow and https://stackoverflow.com/questions/415403/whats-the-difference-between-bashrc-bash-profile-and-environment

So you could move your .bashrc to .bash_profile and see if that works without having to do the test. If not you have the test above.

Rob
  • 818
  • 1
    .bashrc is not actually sourced by a login shell but .profile (if it exists) or .bash_profile are. SHELL is not specified by POSIX and unfortunately, neither is the cmd format option for ps. – Anthony Geoghegan Mar 02 '16 at 15:06
  • 1
    This is what I would use, but it is also subject to malicious manipulation. – dmckee --- ex-moderator kitten Mar 02 '16 at 21:17
  • Note that while the ps -o cmd= $$ should work pretty much everywhere, the $SHELL variable is completely irrelevant. That is the user's default shell and has no bearing on what shell is currently being run or what shell is running a shell script. – terdon Mar 03 '16 at 11:20
  • @terdon, I did mention that.. but you if you are running from your .profile the odds are good your default shell will be running, or is that not a good assumption? – Rob Mar 03 '16 at 12:27
  • 2
    Not really, no. many shells will read ~/.profile when started as login shells. So, for example, your $SHELL could be csh and you run bash -l. That will start bash as a login shell, so it will read ~/.profile, but your $SHELL will still point to csh. Plus, .profile might be sourced explicitly by another script. Since the OP is going for maximum robustness, all sorts of cases should be considered. In any case, $SHELL doesn't provide any useful information about the currently running shell. – terdon Mar 03 '16 at 12:59
  • 2
    FWIW, I too stand corrected – about SHELL not being specified by POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 – Anthony Geoghegan Mar 03 '16 at 14:12
  • Thank you @terdon looks like both learned something new today :). – Rob Mar 03 '16 at 14:16
  • @Rob Here's the best resource I've come across for explaining why environmental configuration should be in .profile (or .bash_profile) and why shell-specific configuration should be in a file such as .bashrc: http://mywiki.wooledge.org/DotFiles Gilles and others have also posted excellent answers to related questions on this site. – Anthony Geoghegan Mar 03 '16 at 14:26
  • SHELL is defined in the 2004 Edition aswell: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html – kelvin Aug 03 '19 at 21:23
  • Also, note that SHELL is not guaranteed to be set. POSIX only defines what happens when it is set: https://unix.stackexchange.com/a/329458/301259 https://unix.stackexchange.com/questions/263773/is-the-shell-environment-variable-only-for-interactive-shells#comment456258_263773 – kelvin Aug 03 '19 at 22:26
4

The question asks for the user's login shell as well as the current shell in a manner that appeases checkbashisms. If that is meant to be the shell on which the user logged in, I would use the one from /etc/passwd, e.g.,

MY_UID=$(id -u)
MYSHELL=$(awk -F: '$3 == '$MY_UID'{ print $7; }' </etc/passwd )

Users can of course start a new shell after logging in. Of course, if one is bash and the other is not, tests of bash's environment variables may not help.

Some may want to use getent rather than just the passwd file (but that wouldn't be in the scope of the question).

Based on the comment about LDAP, and the suggestion for logname, this alternative form could be used:

MY_NAME=$(logname)
MYSHELL=$(getent passwd | awk -F: '$1 ~ /^'$MY_NAME'$/ {print $7;}' )

While testing it, I noticed that logname does not like its input redirected (so I left the expression split up). A quick check shows getent should work on the platforms mentioned (though that should have been provided in the original question):

Thomas Dickey
  • 76,765
  • 1
    Assuming the user database is in /etc/passwd (not LDAP/NIS/mysql...) and that there's only one user name per userid. (would be better to check the first column against $(logname)). – Stéphane Chazelas Mar 02 '16 at 22:24
  • iirc, POSIX doesn't define the utility needed (or I would have used it in my answer). Whether to use id or a user-modifiable variable is a different choice. – Thomas Dickey Mar 02 '16 at 22:26
  • 1
    Note that I said $(logname), not $LOGNAME. The user id and the login shell are both derived from the username used at login. You cannot get back from the user id to the login shell except on systems where there's only one user name per user id. Or IOW, the primary key in the user database is the username, not the uid. – Stéphane Chazelas Mar 02 '16 at 22:31
  • Thanks @ThomasDickey for an answer coming from a different perspective. However, it’s a little more complex than what I was thinking of (more like jimmij’s and Stéphane’s answers). One of the reasons I’m putting my shell configuration files under version control is to configure my environment on all the systems I currently log in to on a regular basis: Cygwin, Ubuntu and CentOS (both 5 and 7, using Active Directory for user authentication). For that reason, I prefer to keep the logic as simple as possible. – Anthony Geoghegan Mar 02 '16 at 23:18