864

How can I determine the current shell I am working on?

Would the output of the ps command alone be sufficient?

How can this be done in different flavors of Unix?

4
  • 9
    Testing for particular capabilities (e.g. does it do ! substitution?) is probably more portable than finding the name of the shell. Local custom might have you running something named /bin/sh which could actually be ash, dash, bash, etc. Commented Jul 24, 2010 at 21:38
  • 2
    @msw: Seems like a good comment except it leaves me wondering "how?". Commented Oct 24, 2012 at 17:41
  • It appears that there is no simple answer to this question. If we can't query the shell, maybe the better approach is to always specify the shell. I'm not sure that this is always possible, but maybe it is more easily accomplished than people generally assume. Commented Oct 24, 2012 at 17:49
  • 3
    @BrentBradburn, ...re: "how", read the autoconf codebase :) -- it does exactly this kind of thing, including distinguishing between 70s-era Bourne and 90s-era POSIX (by looking at whether ^ is treated as a pipe symbol, behavior present in original Bourne but dropped before POSIX was standardized). Commented Mar 13 at 16:37

29 Answers 29

1024

There are three approaches to finding the name of the current shell's executable:

Please note that all three approaches can be fooled if the executable of the shell is /bin/sh, but it's really a renamed bash, for example (which frequently happens).

Thus your second question of whether ps output will do is answered with "not always".

  1. echo $0 - will print the program name, which in the case of an interactive shell is often the shell's name. But this can be changed by the user, e.g. sh -c 'echo $0' not-a-shell. And in a script, it's usually the filename of the script, not the shell.

  2. ps -p$$ - this show the command associated with the current process (the shell). This seems less susceptible to user change (but see next section on heuristics).

  3. echo $SHELL - The path to the user's preferred shell is stored as the SHELL variable by the login program. But we don't know that the running shell is the user's preferred shell, so this one doesn't work at all.


If the executable doesn't match your actual shell (e.g. /bin/sh is actually bash or ksh), you need heuristics. Here are some environmental variables specific to various shells:

  • $version is set on tcsh

  • $BASH is set on bash

  • $shell (lowercase) is set to actual shell name in csh or tcsh

  • $ZSH_NAME is set on zsh

  • ksh has $PS3 and $PS4 set, whereas the normal Bourne shell (sh) only has $PS1 and $PS2 set. This generally seems like the hardest to distinguish - the only difference in the entire set of environment variables between sh and ksh we have installed on Solaris boxen is $ERRNO, $FCEDIT, $LINENO, $PPID, $PS3, $PS4, $RANDOM, $SECONDS, and $TMOUT.


Someone brought up "ash" (Almquist Shell) in comments. There seem to be 2001 variants of it including dash; so in the interest of not blowing up the answer unnecessarily, here's a very useful page listing a ton of various flavours of ash and their differences from each other and often from standard Bourne sh.

Sign up to request clarification or add additional context in comments.

14 Comments

${.sh.version} is set on ksh93
ps -p $$ as Matthew Slattery points out. For ksh: echo $KSH_VERSION or echo ${.sh.version}.
@Dennish - my ksh right now doesn't have KSH_VERSION set. and echo ${.sh.version} returns "Bad Substitution". See my solution above
None of the above works in busybox. There, you can get busybox shell by something like: / # busybox ash ash: applet not found / # busybox hush / #
This answer is great, but didn't help me when playing with Chimera Linux and trying to determine what their /bin/sh actually is. Since they say their userland is from FreeBSD I guess that means it's Almquist shell (ash, though not busybox ash) - if anyone knows how to differentiate this shell, you could improve this answer.
|
143

ps -p $$

should work anywhere that the solutions involving ps -ef and grep do (on any Unix variant which supports POSIX options for ps) and will not suffer from the false positives introduced by grepping for a sequence of digits which may appear elsewhere.

8 Comments

Some shells have their own builtin version of ps which may not understand -p so you may need to use /bin/ps -p $$.
All the shells I'm familiar with understand $$ except for fish with which you would have to use ps -p %self.
Actually, you shouldn't rely on a hard paths such as /bin/ps. ps could easily (actually it is quite normal nowadays) be installed in /usr/bin. $(which ps) -p $$ is a better way. Of course, this will not work in fish, and possibly some other shells. I think it is (which ps) -p %self in fish.
In some minimal systems like a debian-slim docker container, ps might not be there. In that case this approachs still works: readlink /proc/$$/exe
if your sh is emulated by bash, ps -p give you /usr/bin/bash even you run it as sh
|
63

Use -p $$ to get info on the current process ID ($$), and -o to control the output formatting.

ps -p $$ -o 'comm=' 

or

ps -p $$ -o 'args=' 

3 Comments

This is a nice short one. I was using myself ps -o fname --no-headers $$.
Thanks. I found this the best option to use in a script to guard bash specific commands test `ps -p $$ -ocomm=` == "bash" && do_something_that_only_works_in_bash. (The next line in my script has the equivalent for csh.)
I've found that if you do this from within a subshell then it can lead to spurious extra lines by matching the parent's PID as well as the actual shell process. For this, I use -q instead of -p: SHELL=$(ps -ocomm= -q $$)
50

If you just want to ensure the user is invoking a script with Bash:

if [ -z "$BASH" ]; then echo "Please run this script $0 with bash"; exit; fi 

or ref

if [ -z "$BASH" ]; then exec bash $0 ; exit; fi 

5 Comments

It shouldn't be closer to top, because it doesn't answer the question at all. If the question would be "How to check if script is running under bash", I vote for it.
@DawidFerenczy - This question is the top result when you search for that phrase though. In the long run, I think it's much more important that answers answer what people are looking for rather than answer what the original question was about.
In addition, the variable $BASH is defined under tcsh if the tcsh is invoked from bash
This is very useful, thank you. Just adapted it a bit, putting this as the second line after #!/bin/bash: if [ ! -n "$BASH" ] ;then exec bash $0; fi. With this line the script is run using bash even if started using ksh or sh. My use case doesn't need command line arguments, but they could be added after $0 if necessary.
I run my script using sh <script file> and $BASH is set to /bin/sh.
25

You can try:

ps | grep `echo $$` | awk '{ print $4 }' 

Or:

echo $SHELL 

6 Comments

What's the point of grep followed by awk, when /pattern/ { action } will do?
# in zshell alias shell='echo ${SHELL:t}'
$SHELL environment variable contains a shell, which is configured as default for a current user. It doesn't reflect a shell, which is currently running. Also it's better to use ps -p $$ than grepping $$ because of false positives.
The $SHELL env. variable points to the 'parent' shell, as specified in the POSIX spec: SHELL This variable shall represent a pathname of the user's preferred command language interpreter. Therefore the value of $SHELL may not be the current shell.
all within awk, ps | awk '$1=='$$' { n=split($4,a,"/"); print a[n] }'
|
24

$SHELL need not always show the current shell. It only reflects the default shell to be invoked.

To test the above, say bash is the default shell, try echo $SHELL, and then in the same terminal, get into some other shell (KornShell (ksh) for example) and try $SHELL. You will see the result as bash in both cases.

To get the name of the current shell, Use cat /proc/$$/cmdline. And the path to the shell executable by readlink /proc/$$/exe.

2 Comments

... Provided you have /proc.
provided that your shall support $$ - e.g. fish does not. stackoverflow.com/questions/30379934/…
20

There are many ways to find out the shell and its corresponding version. Here are few which worked for me.

Straightforward

  1. $> echo $0 (Gives you the program name. In my case the output was -bash.)
  2. $> $SHELL (This takes you into the shell and in the prompt you get the shell name and version. In my case bash3.2$.)
  3. $> echo $SHELL (This will give you executable path. In my case /bin/bash.)
  4. $> $SHELL --version (This will give complete info about the shell software with license type)

Hackish approach

$> ******* (Type a set of random characters and in the output you will get the shell name. In my case -bash: chapter2-a-sample-isomorphic-app: command not found)

3 Comments

csh does not have a version option.
All the information has already been available above. This answer adds nothing new.
$SHELL is not necessarily the currently running shell, it's the preferred shell, set by the user. Like $EDITOR or $VISUAL.
15

I have a simple trick to find the current shell. Just type a random string (which is not a command). It will fail and return a "not found" error, but at start of the line it will say which shell it is:

ksh: aaaaa: not found [No such file or directory] bash: aaaaa: command not found 

1 Comment

No good in a script. echo 'aaaa' > script; chmod +x script; ./script gives ./script.sh: 1: aaaa: not found
14

ps is the most reliable method. The SHELL environment variable is not guaranteed to be set and even if it is, it can be easily spoofed.

1 Comment

+1 $SHELL is the default shell for programs that need to spawn one. It doesn't necessarily reflect the shell that's currently running.
9

I have tried many different approaches and the best one for me is:

ps -p $$ 

It also works under Cygwin and cannot produce false positives as PID grepping. With some cleaning, it outputs just an executable name (under Cygwin with path):

ps -p $$ | tail -1 | awk '{print $NF}' 

You can create a function so you don't have to memorize it:

# Print currently active shell shell () { ps -p $$ | tail -1 | awk '{print $NF}' } 

...and then just execute shell.

It was tested under Debian and Cygwin.

5 Comments

As of my setup (Cygwin | Windows 7) your answer is the best one, and ps -p $$ | tail -1 | gawk '{print $NF}' works even from cmd without $bash. Please note gawk instead of awk, as awk works only from bash.
@WebComer Sorry, I'm not sure what you mean by "works even from cmd without $bash". Even if you would have Windows ports of ps, tail and gawk, cmd doesn't define $$ as it's PID so it definitely cannot work under the plain cmd.
Why not simply ps -p$$ -o comm=? POSIX says that specifying all headers empty suppresses the header completely. We're still failing (like all the ps answers) when we're sourced by a directly executed script (e.g. #!/bin/sh).
this doesn't work with busybox ps (which is used on alpine)
@bernstein, this does for me: ps -o pid,comm | awk $$'==$1 {print $2}'. There we also avoid invoking tail.
7

The following will always give the actual shell used - it gets the name of the actual executable and not the shell name (i.e. ksh93 instead of ksh, etc.). For /bin/sh, it will show the actual shell used, i.e. dash.

ls -l /proc/$$/exe | sed 's%.*/%%' 

I know that there are many who say the ls output should never be processed, but what is the probability you'll have a shell you are using that is named with special characters or placed in a directory named with special characters? If this is still the case, there are plenty of other examples of doing it differently.

As pointed out by Toby Speight, this would be a more proper and cleaner way of achieving the same:

basename $(readlink /proc/$$/exe) 

4 Comments

This just errors on all Unices that don't provide /proc. Not all the world's a Linux box.
And if you are on Linux, we'd prefer basename $(readlink /proc/$$/exe) to ls+sed+echo.
This will give the name of the actual executable, not the actual shell. When the actual shell is linked as a busybox applet, say ash -> /bin/busybox, this will give /bin/busybox.
@stepse, some users might be interested in differentiating between various implementations of sh. If you run busybox and want just the *sh name, you could use following: ps -o pid,comm | awk $$'==$1 {print $2}'
7

My variant on printing the parent process:

ps -p $$ | awk '$1 == PP {print $4}' PP=$$ 

Don't run unnecessary applications when AWK can do it for you.

1 Comment

Why run an unnecessary awk, when ps -p "$$" -o 'comm=' can do it for you?
6

My solution:

ps -o command | grep -v -e "\<ps\>" -e grep -e tail | tail -1 

This should be portable across different platforms and shells. It uses ps like other solutions, but it doesn't rely on sed or awk and filters out junk from piping and ps itself so that the shell should always be the last entry. This way we don't need to rely on non-portable PID variables or picking out the right lines and columns.

I've tested on Debian and macOS with Bash, Z shell (zsh), and fish (which doesn't work with most of these solutions without changing the expression specifically for fish, because it uses a different PID variable).

1 Comment

Not on my machine - I am running several shells and ssh
5

Provided that your /bin/sh supports the POSIX standard and your system has the lsof command installed - a possible alternative to lsof could in this case be pid2path - you can also use (or adapt) the following script that prints full paths:

#!/bin/sh # cat /usr/local/bin/cursh set -eu pid="$$" set -- sh bash zsh ksh ash dash csh tcsh pdksh mksh fish psh rc scsh bournesh wish Wish login unset echo env sed ps lsof awk getconf # getconf _POSIX_VERSION # reliable test for availability of POSIX system? PATH="`PATH=/usr/bin:/bin:/usr/sbin:/sbin getconf PATH`" [ $? -ne 0 ] && { echo "'getconf PATH' failed"; exit 1; } export PATH cmd="lsof" env -i PATH="${PATH}" type "$cmd" 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; } awkstr="`echo "$@" | sed 's/\([^ ]\{1,\}\)/|\/\1/g; s/ /$/g' | sed 's/^|//; s/$/$/'`" ppid="`env -i PATH="${PATH}" ps -p $pid -o ppid=`" [ "${ppid}"X = ""X ] && { echo "no ppid found"; exit 1; } lsofstr="`lsof -p $ppid`" || { printf "%s\n" "lsof failed" "try: sudo lsof -p \`ps -p \$\$ -o ppid=\`"; exit 1; } printf "%s\n" "${lsofstr}" | LC_ALL=C awk -v var="${awkstr}" '$NF ~ var {print $NF}' 

3 Comments

this works in fish! but for me, fails in bash, because of the -i (-> ignore environment) option to env in the line where you check for lsof being available. it fails with: env -i PATH="${PATH}" type lsof -> env: ‘type’: No such file or directory
@hoijui, @carlo: yes, type fails for me too, as it is a shell built-in. To continue, I just deleted this string from the file. Replacing it with which also works for me. But with the BusyBox userland the script fails in other place too. If I append the set -- <...> line with busybox, and convert the ppid=<...> line into ppid="`env -i PATH="${PATH}" ps -o pid=,ppid= | awk $$'==\$1 { print \$2 }'`", the script starts working on Busybox based Linuces too.
PS. This is more portable IMO: ps -o pid,comm | awk $([ -n "$fish_pid" ] && echo "$fish_pid" || echo "$$$empty")'==$1 {print $2}' | xargs which | xargs realpath.
4

None of the answers worked with fish shell (it doesn't have the variables $$ or $0).

This works for me (tested on sh, bash, fish, ksh, csh, true, tcsh, and zsh; openSUSE 13.2):

ps | tail -n 4 | sed -E '2,$d;s/.* (.*)/\1/' 

This command outputs a string like bash. Here I'm only using ps, tail, and sed (without GNU extesions; try to add --posix to check it). They are all standard POSIX commands. I'm sure tail can be removed, but my sed fu is not strong enough to do this.

1 Comment

I get sed: invalid option -- 'E' on bash 3.2.51 and tcsh 6.15.00
3

If you just want to check that you are running (a particular version of) Bash, the best way to do so is to use the $BASH_VERSINFO array variable. As a (read-only) array variable it cannot be set in the environment, so you can be sure it is coming (if at all) from the current shell.

However, since Bash has a different behavior when invoked as sh, you do also need to check the $BASH environment variable ends with /bash.

In a script I wrote that uses function names with - (not underscore), and depends on associative arrays (added in Bash 4), I have the following sanity check (with helpful user error message):

case `eval 'echo $BASH@${BASH_VERSINFO[0]}' 2>/dev/null` in */bash@[456789]) # Claims bash version 4+, check for func-names and associative arrays if ! eval "declare -A _ARRAY && func-name() { :; }" 2>/dev/null; then echo >&2 "bash $BASH_VERSION is not supported (not really bash?)" exit 1 fi ;; */bash@[123]) echo >&2 "bash $BASH_VERSION is not supported (version 4+ required)" exit 1 ;; *) echo >&2 "This script requires BASH (version 4+) - not regular sh" echo >&2 "Re-run as \"bash $CMD\" for proper operation" exit 1 ;; esac 

You could omit the somewhat paranoid functional check for features in the first case, and just assume that future Bash versions would be compatible.

Comments

2

Please try this helpful command.

echo $SHELL 

1 Comment

$SHELL might not be the same as the executing shell.
1
echo $$ # Gives the Parent Process ID ps -ef | grep $$ | awk '{print $8}' # Use the PID to see what the process is. 

From How do you know what your current shell is?.

2 Comments

It's not the parent process - it's the current process.
Using grep $$ is unreliable. ps -ef | awk -v pid=$$ '$2==pid { print $8 }' is better, but why not just use ps -p $$?
1

This is not a very clean solution, but it does what you want.

# MUST BE SOURCED.. getshell() { local shell="`ps -p $$ | tail -1 | awk '{print $4}'`" shells_array=( # It is important that the shells are listed in descending order of their name length. pdksh bash dash mksh zsh ksh sh ) local suited=false for i in ${shells_array[*]}; do if ! [ -z `printf $shell | grep $i` ] && ! $suited; then shell=$i suited=true fi done echo $shell } getshell 

Now you can use $(getshell) --version.

This works, though, only on KornShell-like shells (ksh).

4 Comments

"This works, though, only on ksh-like shells." Are you saying I have to verify the shell before running this? Hmm...
@jpaugh, the lists must be supported by the shell sourcing this code, which is not the case for dash, yash etc. Normally, if you are using bash, zsh, ksh, whatever - you shouldn't care about such things.
So you're counting bash as "ksh-like"? Got it. That makes more sense
@jpaugh, well, that's what I meant because ksh's set of features is mostly a subset of bash's features (haven't checked this thoroughly though).
1

Do the following to know whether your shell is using Dash/Bash.

ls –la /bin/sh:

  • if the result is /bin/sh -> /bin/bash ==> Then your shell is using Bash.

  • if the result is /bin/sh ->/bin/dash ==> Then your shell is using Dash.

If you want to change from Bash to Dash or vice-versa, use the below code:

ln -s /bin/bash /bin/sh (change shell to Bash)

Note: If the above command results in a error saying, /bin/sh already exists, remove the /bin/sh and try again.

Comments

1

I like Nahuel Fouilleul's solution particularly, but I had to run the following variant of it on Ubuntu 18.04 (Bionic Beaver) with the built-in Bash shell:

bash -c 'shellPID=$$; ps -ocomm= -q $shellPID' 

Without the temporary variable shellPID, e.g. the following:

bash -c 'ps -ocomm= -q $$' 

Would just output ps for me. Maybe you aren't all using non-interactive mode, and that makes a difference.

1 Comment

ps on macOS (and *BSD?) dies not understand -q
0

On Mac OS X (and FreeBSD):

ps -p $$ -axco command | sed -n '$p' 

3 Comments

Tried this while using zsh, and it gave me -bash.
On my system (now), tested with bash and dash, this return mutt... :-b
On MacOS terminal (zsh) it gave me sed
0

Grepping PID from the output of "ps" is not needed, because you can read the respective command line for any PID from the /proc directory structure:

echo $(cat /proc/$$/cmdline) 

However, that might not be any better than just simply:

echo $0 

About running an actually different shell than the name indicates, one idea is to request the version from the shell using the name you got previously:

<some_shell> --version 

sh seems to fail with exit code 2 while others give something useful (but I am not able to verify all since I don't have them):

$ sh --version sh: 0: Illegal option -- echo $? 2 

Comments

0

One way is:

ps -p $$ -o exe= 

which is IMO better than using -o args or -o comm as suggested in another answer (these may use, e.g., some symbolic link like when /bin/sh points to some specific shell as Dash or Bash).

The above returns the path of the executable, but beware that due to /usr-merge, one might need to check for multiple paths (e.g., /bin/bash and /usr/bin/bash).

Also note that the above is not fully POSIX-compatible (POSIX ps doesn't have exe).

Comments

0

Get it with the $SHELL environment variable. A simple sed could remove the path:

echo $SHELL | sed -E 's/^.*\/([aA-zZ]+$)/\1/g' 

Output:

bash 

It was tested on macOS, Ubuntu, and CentOS.

1 Comment

It specifies which shell to run for programs started by shell, but not necessarily equals shell that is running. I.e. GNU screen won't set itself as $SHELL even it is IS user's login "shell".
-1

Kindly use the below command:

ps -p $$ | tail -1 | awk '{print $4}' 

3 Comments

First one is nonsense, echo $SHELL does what you're trying to do and do it well. Second one also isn't good, because $SHELL environment variable contains default shell for a current user, not a currently running shell. If I have for example bash set as default shell, execute zsh and echo $SHELL, it'll print bash.
You are correct Dawid Ferenczy, We can use ps command to determine current shell. [ # ps -p $$ | tail -1 | awk '{print $4}' ].
echo $SHELL may be rubbish: ~ $ echo $SHELL /bin/zsh ~ $ bash bash-4.3$ echo $SHELL /bin/zsh bash-4.3$
-1

This one works well on Red Hat Linux (RHEL), macOS, BSD and some AIXes:

ps -T $$ | awk 'NR==2{print $NF}' 

alternatively, the following one should also work if pstree is available,

pstree | egrep $$ | awk 'NR==2{print $NF}' 

1 Comment

Why the negative vote?
-1

You can use echo $SHELL|sed "s/\/bin\///g"

1 Comment

No, the SHELL environment variable just tells other commands which shell to invoke for you.
-2

And I came up with this:

sed 's/.*SHELL=//; s/[[:upper:]].*//' /proc/$$/environ 

1 Comment

This will only work on Linux! Not MacOS, nor BSD!!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.