将多个路径作为命令行参数传递的最佳实践,用于解释带空格的路径

Best practice to pass multiple paths as command line argument accounting for paths with spaces

背景

我正在编写的 Python 模块具有一些功能,可通过命令行界面通过 python -m fun_module -p .... 参数 -p--path 获取多个路径。目前我用以下方式定义这个论点:

parser = argparse.ArgumentParser(prog="FunProg", add_help=True)
parser.add_argument(
    "-p",
    "--path",
    type=utils.dir_path,
    nargs="+",
    help="Start path(s) used to conduct the search. Multiple paths should be separated with spaces.",
)
args = parser.parse_args()

dir_path 函数可通过 utils.py 文件获得

import os

def dir_path(str_path):
    if os.path.isdir(str_path):
        return str_path
    else:
        raise NotADirectoryError(str_path)

main方法

def main(args):
    for iarg in args.path:
        print("Searching path: " + iarg)

问题

处理以空格分隔并以引号包围的多个路径不会return 获得预期的结果。

期望的输出

python -m fun_module -p ~/Documents ~/Downloads/
Searching path: /Users/thisuser/Documents
Searching path: /Users/thisuser/Downloads/

python -m fun_module -p '~/Documents' '~/Downloads/'
Searching path: /Users/thisuser/Documents
Searching path: /Users/thisuser/Downloads/

python -m fun_module -p "~/Documents" "~/Downloads/"
Searching path: /Users/thisuser/Documents
Searching path: /Users/thisuser/Downloads/

错误:

python -m fun_module -p '~/Documents' '~/Downloads/'                          
Traceback (most recent call last):
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/thisuser/Dev/Python/FileLister/filelister/__main__.py", line 25, in <module>
    args = parser.parse_args()
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 1818, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 1851, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 2060, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 2000, in consume_optional
    take_action(action, args, option_string)
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 1912, in take_action
    argument_values = self._get_values(action, argument_strings)
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 2461, in _get_values
    value = [self._get_value(action, v) for v in arg_strings]
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 2461, in <listcomp>
    value = [self._get_value(action, v) for v in arg_strings]
  File "/Users/thisuser/.pyenv/versions/3.9.0/lib/python3.9/argparse.py", line 2476, in _get_value
    result = type_func(arg_string)
  File "/Users/thisuser/Dev/Python/FileLister/filelister/utils.py", line 7, in dir_path
    raise NotADirectoryError(str_path)
NotADirectoryError: ~/Documents

问题有两个方面:

  1. thispost所示,使用~时需要使用os.path.expanduser
  2. 当您的输入包含引号('")时,os.path.expanduser 将无法正确处理输入。

要解决问题,请先将引号替换为空字符串。使用 python 3.9 可以通过以下方式完成:

for quote in ['"', "'"]:
    str_path = str_path.removeprefix(quote).removesuffix(quote)

否则您必须手动检查并删除:

for quote in ['"', "'"]:
   if str_path.startswith(quote):
      str_path = str_path[1:-1]

然后您必须检查波浪号开头:

if str_path.startswith('~'):
    str_path = os.path.expanduser(str_path)

完整代码:

def dir_path(str_path: str):
    for quote in ['"', "'"]:
        if str_path.startswith(quote):
            str_path = str_path[1:-1]
    if str_path.startswith('~'):
        str_path = os.path.expanduser(str_path)
    if os.path.isdir(str_path):
        return str_path
    raise NotADirectoryError(str_path)

使用以下任何输入进行测试都将生成完整路径:

python -m fun_module -p '~/Documents' '~/Downloads'
python -m fun_module -p "~/Documents" "~/Downloads"
python -m fun_module -p ~/Documents ~/Downloads