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:
if response == None:
print(f"The java CLI does not appear to be in $PATH")
else:
print(f"The java CLI is in your $PATH - good to go")
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