Friday, March 27, 2015

Which which is which?

By Vasudev Ram

Recently I had blogged about which.py, a simple Python program that I wrote, here:

A simple UNIX-like 'which' command in Python

I also posted the same program on ActiveState Code:

A UNIX-like "which" command for Python (Python recipe)

A reader there, Hongxu Chen, pointed out that my which.py actually implemented the variant "which -a", that is, the UNIX which with the -a (or -all) option included. This variant displays not just the first full pathname of an occurrence of the searched-for name in the PATH (environment variable), but all such occurrences, in any directory in the PATH. That was not what I had intended. It was a bug. I had intended it to only show the first occurrence.

So I rewrote the program to fix that bug, and also implemented the -a option properly - i.e. when -a (or its long form, --all, is given, find all occurrences, otherwise only find the first. Here is the new version:
from __future__ import print_function

# which.py
# A minimal version of the UNIX which utility, in Python.
# Also implements the -a or --all option.
# Author: Vasudev Ram - www.dancingbison.com
# Copyright 2015 Vasudev Ram - http://www.dancingbison.com

import sys
import os
import os.path
import stat

def usage():
    sys.stderr.write("Usage: python which.py [ -a | --all ] name ...\n") 
    sys.stderr.write("or: which.py [ -a | --all ] name ...\n") 

def which(name, all):
    for path in os.getenv("PATH").split(os.path.pathsep):
        full_path = path + os.sep + name
        if os.path.exists(full_path):
            print(full_path)
            if not all:
                break

def main():
    if len(sys.argv) < 2:
        usage()
        sys.exit(1)
    if sys.argv[1] in ('-a', '--all'):
        # Print all matches in PATH.
        for name in sys.argv[2:]:
            which(name, True)
    else:
        # Stop after printing first match in PATH.
        for name in sys.argv[1:]:
            which(name, False)

if "__main__" == __name__:
        main()
I tested it some and it seems to be working okay both with and without the -a option now. After more testing, I'll upload it to my Bitbucket account.

- Vasudev Ram - Online Python training and programming

Dancing Bison Enterprises

Signup to hear about new software or info products that I create.

Posts about Python  Posts about xtopdf

Contact Page

2 comments:

Anonymous said...

Your which.py does not check if the file it has found is really a file (as opposed to a dir) and has executable permissions.
So run this: mkdir /usr/bin/spamm; touch /usr/local/bin/spamm
Then "which.py spamm" should give nothing, but gives:
/usr/bin/spamm
/usr/local/bin/spamm

Vasudev Ram said...

Thanks for your comment. You are right about those checks (for a dir or an exe file) not being done. But did you read the previous post that I linked to:

A simple UNIX-like "which" command in Python

in which (pun intended:) I said:

"I'll discuss my interpretation of these variations in an upcoming post - including a variation that uses os.stat(full_path).st_mode:"?

If you look at that commented code fragment, you'll see that it includes a check for if a file is executable, i.e.:

if os.stat(full_path).st_mode & stat.S_IXUSR:

And I have not yet written that "upcoming post". However, I had not thought of checking for the file being a directory as well (good point), which I will now do in that next post, including trying to take care of the case where a file can be a dir (with execute bit set), and excluding that case (at least on UNIX, need to see if Python supports checking for that on Windows).