-2

I was trying to craft a script that produces the location of the local bash binary from a variety of (Linux) environments, assuming existence of standard shell /bin/sh and that /usr/bin/env bash is capable of finding it. Once I obtain the bash shell, the next step is naturally to attempt to find the shell's own path. Disappointingly, I wasn't able to get $SHELL to produce the binary path, so I decided to attempt to get the bash pid path from proc as so:

#!/bin/sh
/usr/bin/env -i /usr/bin/env bash -c '/usr/bin/env readlink /proc/$$/exe'

Strangely, this returns /usr/bin/readlink.

Just as I was about to give up, I tried fiddling like so:

#!/bin/sh
/usr/bin/env -i /usr/bin/env bash -c '/usr/bin/env readlink /proc/$$/exe; true'

/usr/bin/bash Somehow, this works!

Why does this work? What is the difference between the two invocations?

EDIT:

Many of you are confused by my /usr/bin/env invocations. As I explain, restricted PATHs prevent a regular env bash invocation from finding bash, and I don't need a current bash path, just a bash path. My script is tested and works, whereas your "fixed" versions don't work for my PATHs. Anyways, that is not the crux of the question.

Let me put a copy of my comment here:

Just to explain about the script, I'm not trying to find the bash executable previously being used; I'm not assuming I'm running in bash, rather I'm finding a valid bash executable path. It relies on the /usr/bin/env bash trick being able to find bash in basically all systems. The reason for the first /usr/bin/env -i being, this needs to run in locked-off PATHs which actually prevent env from finding bash, which removing the environment does allow finding, as I tested. The last env is due to having no PATH, readlink cannot be found... except with env.

You may notice that I run in /bin/sh, careful to not assume the bash path or that I'm running in bash already.

EDIT:

I'm going with

#!/bin/sh
/usr/bin/env -i bash -c 'echo $BASH'
19
  • 3
    I'm very confused by what you do here. By starting with env -i, you explicitly say "well, don't use the $PATH as set for this process, use something else, as used by nothing started in this environment", so this is a non-starter to find the actual bash executable used in the actual environment. Commented Jan 18 at 14:43
  • 1
    But it gets stranger. You run /usr/bin/env from /usr/bin/env -i? What sense does that make? And then you start bash, just to again start /usr/bin/env? So now we're three env deep, and the only one of these that actually does anything does the opposite of what you want. Commented Jan 18 at 14:44
  • 1
    To top that off, /proc/$$ is totally unnecessary, Linux (and you say only care about Linux, but as far as I'm aware, all other Unixoid OSes with /proc probably do the same) offers /proc/self. You could simply ask bash itself instead of going literally four redirections there. Commented Jan 18 at 14:45
  • 2
    the reason why ;true changes things is explained for example here (if there's only a single command, the shell and env, can, instead of forking and replacing the fork with the execution of the launched command, just replace itself with the launched command. Commented Jan 18 at 14:51
  • 6
    OK, whatever. I can tell you three times that this is a surefire way to not find the bash that the system has intentionally installed, and you seem to be intent on doing exactly that – then I must believe you that this is your intention! I will, however, say a fourth time, that this is really really not what you should be doing. Commented Jan 18 at 16:57

2 Answers 2

5

I was trying to craft a script that produces the location of the local bash binary […]

The simplest solution for this is to use $BASH, i.e.

#!/bin/sh
bash -c 'echo "$BASH"'

It's documented in (at least) the GNU page for bash,

The full pathname used to execute the current instance of Bash.

Some examples:

# Debian GNU/Linux 12 (bookworm)
# Debian GNU/Linux 13 (trixie)
/usr/bin/bash

# Cygwin
/bin/bash

# Termux
/data/data/com.termux/files/usr/bin/bash
8
  • 1
    @ChrisDavies - called mine bt because lazy, but invoked as above it prints ./bt for me on Ubuntu 22.04, bash 5.1.16(1) ... Commented Jan 18 at 19:12
  • 1
    @tink thankyou. I'll try to reproduce your effect. It works as expected for me on Debian (various recent releases) Commented Jan 18 at 20:20
  • 2
    @tink interesting. Works with bash 5.2.15. Fails with 5.3.9 Commented Jan 18 at 20:35
  • 1
    @ChrisDavies , tink: works with GNU bash, version 5.2.37(1)-release (x86_64-redhat-linux-gnu) (Fedora 42) and 5.3.0 (Fedora 43), and, and that's interesting, 5.3.9 (Fedora rawhide) Commented Jan 18 at 21:25
  • Interesting indeed ... who would have thought that something that basic has a regression over late versions and different distros. Commented Jan 18 at 21:31
3

bash executes the last command directly in its process without forking. It is actually documented in the NEWS file in the bash sources:

This is a terse description of the new features added to bash-5.1 since
the release of bash-5.0.
...
b. Bash attempts to optimize the number of times it forks when executing
   commands in subshells and from `bash -c'.

Except it does not mention "last".

1
  • It doesn't necessarily execute the last command directly, it really depends on the implementation. Sometimes it only works if there's a single command. See unix.stackexchange.com/questions/766570/… Commented Jan 18 at 17:07

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.