7

I'm looking for a simple and reliable way to get the name of the current shell from inside a script or a sourced file (not from the command line). I would have expected to just do $(basename "$SHELL") but if my login shell is zsh and I have the following code in some_script.sh

this_shell=$( basename "$SHELL" )
echo "The Shell is $this_shell"
echo "The Shell is $0"

and I run it with bash some_script.sh, it still lists zsh instead of bash even though the interpreter used is /bin/bash. I'm not sure why shell designers chose to show the default shell instead of the current shell, but that seems to be what we're stuck with.

There are some other slightly similar questions (here and here), but their answers fall short in many ways.

  1. They often assume the user is trying to figure out what interactive shell they're currently using, but that's crazy. I know what shell I'm typing commands into--I need code that could be run anywhere to be able to determine what shell it is using.
  2. They often give several different things to try--again as though you're on the command line and can just fiddle around until you learn you're using bash--but I need one single thing that is reliable in all contexts.
  3. They often give things that are utterly useless from scripts, like echo $0. That fails as shown in the script above. (Yes it works in an interactive shell command line, but why would you ever not know what shell you're using?)
  4. They sometimes give commands that (in my limited tests) include the correct information, like ps -p $$, but lack cross-platform, cross-shell compatible sed/awk commands to pipe it through to get just the shell name and ignore the other information that comes along for the ride.
  5. They include things that will only work on a couple of shells, like $BASH_VERSION and $ZSH_VERSION. I want to support as many shells as possible, such as fish, csh, tcsh.

How do I reliably and accurately detect any current shell? I'm looking for something that will work across platforms, in scripts, and for as many shells as possible and reasonable1.

UPDATE: When I posted this question I expected that there was some facility built into the shells to give us this info, which appears to not be the case. Since it appears inevitable now to rely on something outside of the shells, I should make more explicit that I'm asking for a cross platform solution (which, though implied by my objections to other answers above, might be easy to miss if you don't read the question carefully).

Update 2 If anyone still believes this is a duplicate of the Linux-only question because Stéphane’s answer is not Linux-only, here are the differences between what I'm asking for and what he has provided. (Note that what he has written is ingenious, and I'm not knocking it, but it doesn't solve my problem.)

  1. I'm looking for something simple and reliable, that
  2. can be added to any script or function definition (that would be sourced by a .zshrc or .bash_profile or whatever) to branch.
  3. You cannot use his script as an outside utility that passes the interpreter name to the calling script/function, since it will always be interpreted by the default interpreter and return that. This makes it either difficult or impossible to use for my purposes. If it is possible, it is still very very difficult, and the solution to making it work is not given in the answer. Therefore he did not answer my question, therefore it is not a duplicate.

If you want to see something that will work, take a look at ShellDetective on GitHub. That may make it easier to see the differences between what is already present on SE, and what this question is looking for (and was in fact written in order to satisfy the needs of this question, which were unmet anywhere else).


(P.S. if you can't believe there's a use case for this, imagine functions that get sourced into .zshrc, .bash_profile, or .profile depending on what server is being used and what shells it has available. They're sourced so they have no shebang line. They are most useful if they can work in any shell, but sometimes they have to know which shell they're in to know how to behave.)


1 I'm not concerned with "shells" that are not shells in any traditional sense of the word. I'm not concerned with some shell that some guy wrote for himself. I'm only concerned with real shells that actually occur in the wild, and which someone might conceivably have to use when logging into some server on which they cannot just install whatever shells they want.

  • To read the entirely off-topic discussion on whether Python is a shell: see here – iconoclast Aug 7 '15 at 2:51
  • 2
    Quoting Gilles’s answer to the question What is the exact difference between a ‘terminal’, a ‘shell’, a ‘tty’ and a ‘console’?  —  “A shell is the primary interface that users see when they log in, whose primary purpose is to start other programs.”  IMO, that’s enough to eliminate Perl and Python from the category of “shell”. – G-Man Aug 7 '15 at 5:39
  • Vaguely related: Compatibility scripting: Save $? for use later.  A suggested approach: do an end-around of the differences in the syntaxes of the different shells by saying sh -c "…", and then do the bulk of the work in POSIX shell syntax. – G-Man Aug 7 '15 at 6:02
  • 2
    Why do you want to get that? Some shells, usable as login shells, have very incompatible syntax (some shells are Lisp-like languages!), so I don't understand why you want to do that. If you are coding sourceable files, you'll need to code several variants and tell your user to choose the appropriate one. – Basile Starynkevitch Aug 7 '15 at 6:40
  • 1
    My second question is an example of disguising a shell. What do you do if a script is executed by an interpreter executable whose name doesn't match the language that the interpreter interprets? If I take a copy of csh, rename it to fish and use it to run your script, do you want fish or csh? – Gilles Aug 8 '15 at 7:42
4

I've had great results with this combination:

ps -p $$ | awk '$1 != "PID" {print $(NF)}'

On Tru64 (OSF/1), the output has parentheses around the shell. Tack on tr -d '()' to remove them.

ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()'  

Appears to works on all shells, on Solaris 10 and RHEL 5.7 / 6.4. Didn't test other distros such as Debian, Ubuntu, or Mint, but I would think they should all work the same; I also have no idea if FreeBSD would work.

enter image description here

  • Doesn't work in scripts (will return the name of the script instead) – Qw3ry Aug 6 '18 at 12:31
2

So I am still trying to flesh this out, but I think I have an idea that will work. As you have noticed, what you are trying to do is if not impossible, exceedingly difficult to do in the all shells (each variant you add to the polyglot increases the complexity at a greater than linear rate). you could probably do it if you split into Bourne (sh, ash, dash, bash, ksh, pdksh, zsh, etc.) and c style shells (csh, tcsh, fish, etc.) but the variation between csh variants presents various interesting challenges.

So let's cheat and write our detection routine in a known language (bash with a shebang line, C, perl, python, whatever) and determine the name of the executable of the parent. we can then use two tricks to get the information back to the parent, namely return value(s) and a single word written to standard out. On sh descended shells we would return 0 and write the name of the shell as they all have good backtick handling. on the C family of shells we can start incrementing the return value for each set of incompatibilities we need to work around. Return values over two hundred would indicate errors starting with everything seems fine, but I just can't tell who my parent is at two hundred.

The inner program seems easy on linux (/proc/ppid/exe is half the battle). I am pretty sure this can be done on bsd with ps options, but I don't have a running bsd system at the moment to test on, and my mac needs a new hard drive (and is only 10.4 anyway). Although it reduces the shell syntax issues significantly, it does introduce a different set of compatibility issues. I still think It has possibilities.

  • This is the insight that I eventually settled on—that you need an outside program to do the work—although I got it when reading G-Man's comment about sh -c "...", which he took from another question. (Sorry: I skipped your answer at first because I didn't see any code in it, and only came back and read it later.) – iconoclast Aug 8 '15 at 15:41
  • There's a paradox in @iconoclast's question. He wants to call that from a script (note that some shells don't support functions) that can be sourced from any shell, so that script presumably is already polyglot, so must already have some support for figuring out what is interpreting it. In my experience, writing polyglot code (my which_intepreter is an extreme example, but see also there for a shell-only one is extremely difficult, and generally not worth the effort. – Stéphane Chazelas Aug 17 '15 at 16:01
  • @StéphaneChazelas: I think you are right that it's generally not worth the effort (certainly generally not, and maybe always not) but I'm not ready to give up... I think the problem breaks down into 2 problems really: 1st getting the shell name, and 2nd using it. Both (at least practically) require some outside help to avoid the limitations of the shells that you could be using. I've solved the first problem with a small utility written in Crystal, and when I find time I want to solve the 2nd one as well. Btw, your solution is brilliant, but obviously very complex. – iconoclast Aug 17 '15 at 19:58
  • @iconoclas, assuming you can retrieve the shell name reliably (note that command substitution and variable assignment syntax varies between shells), you can't do things like case $shell in sh)... (that's Bourne-only syntax), or switch ($shell) (csh-only). test "$shell" = "sh" && do-something works in csh/sh/rc/es, but not fish... – Stéphane Chazelas Aug 17 '15 at 20:05
  • yes, I've run into this already. The worst thing is fish won't even load a script that has syntax it doesn't like, so you can't just send STDERR to /dev/null. But I have a solution, which I will post once I've got it all working. – iconoclast Aug 18 '15 at 15:08
1

try somethings like this

shell_bin=$(ps h -p $$ -o args='' | cut -f1 -d' ')
echo $shell_bin
  • This fails in csh and tcsh. – iconoclast Aug 7 '15 at 1:54
  • with a small modification it will work in fish, but then only in fish, unfortunately: ps h -p %self -o args='' | cut -f1 -d' '. This creates a chicken and egg problem... you have to know you're in fish in order to execute the fish version of the code... – iconoclast Aug 7 '15 at 1:55
  • @iconoclast- set shell_bin=`ps -p $$ -o args='' | cut -f1 -d' '` - will work with CSH but not the others – DarkHeart Aug 7 '15 at 3:54
  • @DarkHeart: actually that fails to, since the variable assignment wasn't the problem on csh/tcsh, it was this part: ps -p $$ -o args='' | cut -f1 -d' ', which returns -sh on csh, and -csh on tcsh. – iconoclast Aug 7 '15 at 17:12
1

I believe you cannot always get the current shell's name, and I think you should be aware of the limitations of what is possible.

On Linux distributions, most users would have bash as their login & interactive shell (since bash is the default shell on most distros). Some users would set their shell to zsh, csh (and variants) or to fish.

(As other comments and answers explain, finding reliably the shell out of bash, zsh, tcsh, fish is already challenging)

But some weird users may set their login shell to something entirely different - a Lisp interpreter, scsh, es, some scripting language à la Python, or Ocaml, or Perl, etc...- and it is their freedom to do so. Probably, some people are coding their own shell and using it interactively. Even if you found their strange shell, you won't be able to do anything useful of it (hence I believe you shouldn't try to get the shell's name).

So I guess that you are coding some sourceable file (perhaps generating one) to configure some software. So just explain what you are doing, and code for the common case of bash (and perhaps zsh & tcsh) ....

-1

use below at own risk disclaimer

tmp=`head -n 1 $0`
SHELL_BIN=`readlink -f ${tmp#*!}`
SHELL=${SHELL_BIN##*/}
  • it's giving me a blank line... but if I understand what you're attempting to do it's very clever – iconoclast Aug 6 '15 at 23:56
  • It shouldn't give you a blank line, unless -f option in readlink, some implementations dont have that switch – gwillie Aug 6 '15 at 23:59
  • I'm on OS X, and the man page lists -f as an available option, taking the format as an argument. – iconoclast Aug 7 '15 at 0:14
  • That may work in some special cases if $0 is an script (starts by #!/...) but that is not the usual setup. Here, most $0 point to an ELF file. – user79743 Aug 7 '15 at 0:20
  • readlink not working on my puredarwin vm so I no idea what's going on there. – gwillie Aug 7 '15 at 0:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.