xonsh "which" equivalent - how to test if a (subprocess mode) command is available?

295 Views Asked by At

I would like to test (from xonsh) if a command is available or not. If I try this from the xonsh command prompt:

which bash

Then it works:

user@server ~ $ which bash                                                                                                           
/usr/bin/bash

But it does not work from xonsh script:

#!/usr/bin/env xonsh
$RAISE_SUBPROC_ERROR = True
try:
    which bash
    print("bash is available")
except:
    print("bash is not available")

Because it results in this error:

NameError: name 'which' is not defined

I understand that which is a shell builtin. E.g. it is not an executable file. But it is available at the xnosh command prompt. Then why it is not available inside an xonsh script? The ultimate question is this: how can I test (from an xonsh script) if a (subprocess mode) command is available or not?

4

There are 4 best solutions below

0
nagylzs On BEST ANSWER
import shutil
print(shutil.which('bash'))
0
Jason R. Coombs On

While nagylzs' answer led me to the right solution, I found it inadequate.

shutil.which defaults to os.environ['PATH']. On my machine, the default os.environ['PATH'] doesn't contain the active PATH recognized by xonsh.

 ~ $ os.environ['PATH']
'/usr/bin:/bin:/usr/sbin:/sbin'

I found I needed to pass $PATH to reliably resolve 'which' in the xonsh environment.

 ~ $ $PATH[:2]
['/opt/google-cloud-sdk/bin', '/Users/jaraco/.local/bin']
 ~ $ import shutil
 ~ $ shutil.which('brew', path=os.pathsep.join($PATH))
'/opt/homebrew/bin/brew'
0
Jason R. Coombs On

The latest version of xonsh includes a built-in which command. Unfortunately, the version included will emit an error on stdout if the target isn't found, a behavior that is not great for non-interactive use.

0
tmt On

As mentioned in another answer, which exists in the current version of xonsh (0.13.4 as of 15/12/2022) so your script would work. However, it outputs its own error message so it's necessary to redirect stderr to get rid of it.

Also, unless you redirect its stdout as well (using all>), it migh be a good idea to capture its output so the final version would look like this:

#!/usr/bin/env xonsh
$RAISE_SUBPROC_ERROR = True

try:
    bash = $(which bash err> /dev/null)
    print(f"bash is available: {bash}")
except:
    print("bash is not available")