11

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.

18
  • 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”. Aug 7, 2015 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. Aug 7, 2015 at 6:02
  • 3
    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. Aug 7, 2015 at 6:40
  • Basile: in any source-able function, it will be much more maintainable if you can keep the source in one file, rather than repeating code across 5 files, or whatever. And if the differences between the shells can as much as possible be handled by other functions that your function depends on, then you can keep a sane code base. Simply re-writing everything several times is a recipe for maintenance nightmares. No one writes 5 versions of a web app just because there are browser differences.
    – iconoclast
    Aug 7, 2015 at 15:55
  • 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? Aug 8, 2015 at 7:42

8 Answers 8

6

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

2
  • 7
    Doesn't work in scripts (will return the name of the script instead)
    – Qw3ry
    Aug 6, 2018 at 12:31
  • On some systems, this may return a string in parentheses, such as (zsh).
    – Kusalananda
    Apr 16, 2022 at 6:12
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.

5
  • 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, 2015 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. Aug 17, 2015 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, 2015 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... Aug 17, 2015 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, 2015 at 15:08
1

try somethings like this

shell_bin=$(ps h -p $$ -o args='' | cut -f1 -d' ')
echo $shell_bin
4
  • This fails in csh and tcsh.
    – iconoclast
    Aug 7, 2015 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, 2015 at 1:55
  • @iconoclast- set shell_bin=`ps -p $$ -o args='' | cut -f1 -d' '` - will work with CSH but not the others
    – user14755
    Aug 7, 2015 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, 2015 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) ....

0
1

How about this, sorry but I do not have many platforms to try it on

local __shell=`ps $$ -o comm=""`
3
  • This seems very similar to Dany Flores' answer. By looking at the comments there, this solution may have some problems with csh ad tcsh.
    – marco
    Jan 17, 2021 at 13:14
  • 1
    yes, just one less fork really (to cut). Might help some, I'm always looking to reduce the forks :-)
    – colin
    Jan 18, 2021 at 15:55
  • Nice, but I would stick to POSIX: ps -o comm='' -p "$$". And for shells that are so different that they don't understand the standard $$, I wouldn't even try to imagine a solution compatible with them, there's not a single one.
    – Fravadona
    Apr 17, 2022 at 12:09
1

It is rather long, but it only relies on the shell to support pipes.

cat /proc/thread-self/status | grep PPid | awk '{ print $2 }' | xargs -iPID readlink /proc/PID/exe

Explanation:

  • cat /proc/thread-self/status: Really big list of the current proccess' properties. Unfortunately, this refers to the cat process, so a bit more is needed.
  • grep PPid | awk '{ print $2 }': Finds the line PPid <shell's pid>, and AWKs out the PPid, leaving the shell's PID.
  • xargs -iPID readlink /proc/PID/exe: Follows and prints the link /proc/PID/exe, replacing PID with the shell's PID from stdin.
5
  • Doesn't seem to work with GNU xargs unless you remove the = after -i (so leaving -iPID). With that alteration it seems to work for bash, zsh, tcsh, and dash Apr 16, 2022 at 0:13
  • @NickMatteo Whoops! That's a typo on my part. Thanks!
    – Hoo Yoo
    Apr 17, 2022 at 1:12
  • What OSs provide /proc/thread-self/status? Solaris doesn't have it
    – Fravadona
    Apr 17, 2022 at 11:35
  • @Fravadona Any Linux-based OS. I'm not sure what the equivalent would be for Solaris/UNIX though. Does /proc/thread-self exist in Solaris?
    – Hoo Yoo
    Apr 21, 2022 at 0:28
  • If you're going for a Linux-specific solution shell independent solution, you might as well use sh -c 'readlink "/proc/$PPID/exe"' Jan 16 at 16:10
1

This is based on Hoo Yoo's answer here (I don't have enough reputation to comment), but merged into a single call to awk that calls readlink, instead of a cat/grep/awk/xargs/readlink pipe. This should work on any Linux based shell, as far as I'm aware, as long as awk and readlink are present.

awk '/PPid/ { cmd="readlink /proc/"$2"/exe"; cmd | getline path; print path }' /proc/thread-self/status
-2

use below at own risk disclaimer

tmp=`head -n 1 $0`
SHELL_BIN=`readlink -f ${tmp#*!}`
SHELL=${SHELL_BIN##*/}
10
  • it's giving me a blank line... but if I understand what you're attempting to do it's very clever
    – iconoclast
    Aug 6, 2015 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, 2015 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, 2015 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, 2015 at 0:20
  • readlink not working on my puredarwin vm so I no idea what's going on there.
    – gwillie
    Aug 7, 2015 at 0:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .