Bootstrap FreeKB - Python (Scripting) - Locate CLIs using shutil.which
Python (Scripting) - Locate CLIs using shutil.which

Updated:   |  Python (Scripting) articles

There are a few commands that can be used to find files on a Linux system.

shutil.which is similar to the which command to determine if a Command Line Interface (CLI) exists in $PATH. For example, which can be used to determine if the java CLI exists in $PATH. Here is the minimal boilterplate code without error handling.

#!/usr/bin/python3
import shutil
response = shutil.which('java')
print(response)

 

Here is a more practical example, with try/except/else error handling.

#!/usr/bin/python3
import shutil

try:
  response = shutil.which('java')
except Exception as exception:
  print(exception)
else:
  print(response)

 

If the java CLI does exist in $PATH, something like this should be returned, meaning that the java CLI is located at /usr/bin/java.

~]$ python3 example.py
/usr/bin/java

 

On the other hand, if the CLI does not exist in $PATH, None should be returned.

~]$ python3 example.py
None

 

Be aware that often, the directories in $PATH are loaded from your users bash profile file, such as /home/john.doe/.bash_profile.

~]$ cat .bash_profile
PATH=$PATH:$HOME/.local/bin:$HOME/bin
export PATH

 

For example, let's say echo returns the following.

~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/john.doe/.local/bin:/home/john.doe/bin

 

Then if you were to run the following Python script using subprocess and logger to run echo $PATH.

~]$ cat testing.py
#!/usr/bin/python3
import shutil
import logging
import getpass
import sys
import subprocess

log_file="/home/john.doe/testing.log"

logger = logging.getLogger()
logger.setLevel(logging.INFO)
format = logging.Formatter(fmt="[%(asctime)s %(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
fileHandler = logging.FileHandler(log_file)
fileHandler.setFormatter(format)
logger.addHandler(fileHandler)
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(format)
logger.addHandler(consoleHandler)

command="echo $PATH"

stdout, stderr = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

logging.info(f"PATH = {stdout.decode('utf-8').strip()}")

 

The output should be exactly the same as if you ran the echo $PATH command on the command line.

~]$ python3 testing.py
[2024-04-08 04:44:56 INFO] PATH = /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/john.doe/.local/bin:/home/john.doe/bin

 

But if you use crontab to run the script.

* * * * * python3 /home/john.doe/testing.py

 

$PATH may only contain /usr/bin and /bin.

[2024-04-08 04:37:01 INFO] stdout = /usr/bin:/bin

 

Which may cause shutil.which to return None if a CLI exists in a $PATH other than /usr/bin and /bin. This occurs because cron jobs run in a minimal environment, and since they're executed directly by crond without a shell (unless you force one to be created), the regular shell setup never happens. One option is to include PATH in crontab, something like this. Be aware that this will apply to every entry in your users crontab.

PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/john.doe/.local/bin:/home/john.doe/bin

 

Or you could specify the full path to the CLI in shutil.which.

#!/usr/bin/python3
import shutil
response = shutil.which('/usr/local/bin/java')
print(response)

 




Did you find this article helpful?

If so, consider buying me a coffee over at Buy Me A Coffee



Comments


Add a Comment


Please enter 11aa20 in the box below so that we can be sure you are a human.