用Python在Windows下模仿Linux的|which.exe|程序。
这个程序会从Windows环境变量|PATH|里列出的所有目录中查找"可执行文件"。
在Linux下|which.exe|通过文件的"可执行"属性来判断文件是否可执行不同。
在Windows下我们通过扩展名来判断。
如果一个文件的扩展名是|.exe|, |.bat|,或者任何在Windows环境变量|PATHEXT|中列出的扩展名(比如我们自己添加的|.py|),则认为这个文件为"可执行文件"。这个定义也符合Windows的CMD对"可执行文件"的定义。
源码闲话少说,先上源码。实现的具体分析见源码中注释及源码后的分析。
__doc__ = """
Author: kuiyuyou@gmail.com
File: aoiwhich.py
"""
import sys
import os
def find_executable(program):
""" Given |program| which is the name or path of a program, find a list of
executable paths of the program.
#// Arguments
program: the name or path of a program. No need to include extension.
return: a list of executable paths of the program.
#// What an "executable" means?
An executable means an existing file whose extension is |.exe|, |.bat|, or
any other extensions listed in Windows environment variable |PATHEXT|.
#// What is the algorithm used to find all the executable paths?
Use each of dir paths listed in Windows environment variable |PATH| as parent dir.
Use each of extensions listed in Windows environment variable |PATHEXT| as extension.
Synthesize all the possible paths by combining each parent dir, the |program| given,
and each extension. (Empty parent dir and empty extension are two special cases.)
Check if each path synthesized is an existing executable path.
"""
#//
if not isinstance(program, str): raise TypeError(program)
#// create a list to store all the executable paths found
the_path_s = []
#// get environment variable |PATHEXT|'s value
the_PATHEXT = os.environ.get('PATHEXT')
## |os.environ.get| returns either a str or None
#// split |the_PATHEXT| into a list of extensions
if the_PATHEXT is None:
the_ext_s = []
else:
the_ext_s = the_PATHEXT.split(os.pathsep)
## |os.pathsep| on Windows is |;|
#// change each item in [the_ext_s] into lowercase
the_ext_s = [x.lower() for x in the_ext_s]
#// add '.exe' to |the_ext_s| if it is NOT in
if '.exe' not in the_ext_s:
the_ext_s.append('.exe')
#// add '.bat' to |the_ext_s| if it is NOT in
if '.bat' not in the_ext_s:
the_ext_s.append('.bat')
## The reason to add '.exe' and '.bat':
##
## On Windows, files with an extension of '.exe' or '.bat' are considered
## executable, even if '.exe' and '.bat' are not listed in environment
## variable |PATHEXT|.
##
## However, the algorithm below requires that, an "executable" msut have one
## of the extensions in |the_ext_s|. So to ensure that the algorithm can
## always recognize '.exe' and '.bat' files as executable, we add '.exe' and
## '.bat' to |the_ext_s| manually if they are not listed in |PATHEXT|.
#// get a tuple version of |the_ext_s|
## becasue |string.endswith| below takes tuple but not list as argument
the_ext_s_tuple = tuple(the_ext_s)
#// check if |program| given is an executable full path
if os.path.isfile(program) and program.endswith(the_ext_s_tuple):
the_path_s.append(program)
## do not return immediately here because if |program| is a relative path,
## algorithm below may find its absolute path as well, e.g.
## if |aoiwhich.py| file's parent dir has been added to PATH, then
## in the same dir, run command |aoiwhich.py aoiwhich.py| would find both
## the relative path |aoiwhich.py| as well as an absolute path.
#// get the environment variable |PATH|'s value
the_PATH = os.environ.get('PATH')
## |os.environ.get| returns either a str or None
#// split |the_PATH| into a list of dir paths
if the_PATH is None:
the_dir_path_s = []
else:
the_dir_path_s = the_PATH.split(os.pathsep)
## |os.pathsep| on Windows is |;|
#// insert empty dir path to the beginning of the list of dir paths
if '' not in the_dir_path_s:
the_dir_path_s.insert(0, '')
#// get unique dir paths
the_dir_path_s_unique = []
for the_dir_path in the_dir_path_s:
if the_dir_path not in the_dir_path_s_unique:
the_dir_path_s_unique.append(the_dir_path)
#// synthesize possible executable paths.
#// check whether each of the synthesized executable paths is existing executable path.
for the_dir_path in the_dir_path_s_unique:
# synthesize a path
# use |the_dir_path| as the parent dir
# use |program| as the file name (assuming it has included extension)
# use no extension
the_path_synthesized = os.path.join(the_dir_path, program)
# check if the path synthesized is an existing executable path
if os.path.isfile(the_path_synthesized) and the_path_synthesized.endswith(the_ext_s_tuple):
the_path_s.append(the_path_synthesized)
#
for the_ext in the_ext_s:
# synthesize a path
# use |the_dir_path| as the parent dir
# use |program| as the file name (assuming it has NOT included extension)
# use |the_ext| as the extension
the_path_synthesized_plus_ext = the_path_synthesized + the_ext
# check if the path synthesized is an existing executable path
if os.path.isfile(the_path_synthesized_plus_ext):
the_path_s.append(the_path_synthesized_plus_ext)
#// make the list items unique
the_path_s_unique = []
for the_path in the_path_s:
if the_path not in the_path_s_unique:
the_path_s_unique.append(the_path)
#//
return the_path_s_unique
if __name__ == '__main__':
#//
if len(sys.argv) != 2:
print('Usage: {} PROGRAM'.format(sys.argv[0]))
print('Example: {} {}'.format(sys.argv[0], 'notepad'))
sys.exit(0)
## I was using |argparse| module to parse the arguments, which was very
## convenient. However, |argparse| module is available only since python
## version 3.2. So here I simply check the number of arguments given instead.
#//
the_program = sys.argv[1]
#// find paths according to program name given
the_path_s = find_executable(the_program)
#// has found NO paths, exit
if len(the_path_s) == 0:
sys.exit(0)
#// has found some paths, print
print('\n'.join(the_path_s))
分析
通过|os.environ.get('PATHEXT')|和|os.environ.get('PATH')|分别得到Window环境变量|PATHEXT|和|PATH|的值。 ## 注意,当这些变量在注册表中没有定义时,|os.environ.get|返回的是None而不是空的list。
将这两个值用|split|拆分为多项。|PATHEXT|的每一个项是一个扩展名(包括点)。|PATH|的每一个项是一个目录路径。
通过将每一个目录路径,与函数参数|program|,与每一个扩展名相组合,得到所有可能的可执行文件的路径。 ## 有两个特殊情况应考虑在内,一个是目录为当前目录,一个是函数参数|program|已经包含了扩展名。
再用|os.path.isfile|判断该路径是否是一个存在的文件。
最后将找到的所有路径打印出来。
用法先将存有源码的文件命名为|aoiwhich.py|。
在命令行运行:
python aoiwhich.py notepad
在俺的机器上得到:
C:\Windows\system32\notepad.exe
C:\Windows\notepad.exe
在命令行运行:
python aoiwhich.py regedit
在俺的机器上得到:
C:\Windows\system32\regedit.exe
C:\Windows\regedit.exe
跋
此软件已开源到 http://sourceforge.net/p/aoiwhich/ 。