在编写Python工具脚本时,我们常常需要从命令行接收用户输入的文件路径或配置选项,随后在程序内部进行文件读写。如果只依靠手工拆分sys.argv,不仅容易遗漏边界情况,还会让路径拼接变得脆弱。合理组合参数解析模块与路径处理模块,可以让脚本在Linux、Windows和macOS上都能稳定运行。

一、为什么不能用字符串直接拼路径
不少初学者习惯用加号把目录和文件名连起来,例如base + '/' + name。这种做法在换了操作系统后就可能出错:Windows默认使用反斜杠,而Linux使用正斜杠;当用户传入带有盘符或相对跳转的参数时,字符串拼接完全不会做规范化,最终传给open()的就是一个不存在的地址。
另一个隐患是路径遍历。如果脚本把用户输入的../../etc/passwd直接拼到工作目录后面,就可能读到系统敏感文件。用专门的路径处理工具可以在解析阶段就把路径绝对化并限制在允许范围内,从根源减少安全风险。
二、使用argparse解析命令行参数
argparse是Python标准库自带的参数解析器,它能够自动生成帮助信息、做类型转换和必填校验。下面这段示例代码定义了一个接收输入文件与输出目录的命令行工具:
import argparse
def build_parser():
parser = argparse.ArgumentParser(description='处理文件的小工具')
parser.add_argument('-i', '--input', required=True, help='输入文件路径')
parser.add_argument('-o', '--output', default='./result', help='输出目录')
return parser
if __name__ == '__main__':
args = build_parser().parse_args()
print('输入:', args.input)
print('输出:', args.output)
上面的代码把--input标记为必填项,若用户忘记传参,argparse会直接报错并退出。相比手写sys.argv[1]判断,这种方式可读性更高,也方便后期扩展子命令。
需要注意的是,argparse只会把参数值作为字符串或指定类型返回,它本身并不验证这个路径是否真实存在。因此拿到参数后,还应交给路径模块做进一步检查,而不是立刻调用文件操作函数。
三、用pathlib做跨平台路径处理
pathlib提供了面向对象的Path类,能自动选用当前系统的路径分隔符,并支持链式调用。以下示例展示如何把用户传入的路径绝对化并判断是否存在:
from pathlib import Path
def resolve_input(user_path):
p = Path(user_path).expanduser().resolve()
if not p.exists():
raise FileNotFoundError('路径不存在: ' + str(p))
return p
def safe_output_dir(user_dir):
out = Path(user_dir).expanduser().resolve()
out.mkdir(parents=True, exist_ok=True)
return out
expanduser()会把类似~/data的写法展开成当前用户主目录,resolve()则消除中间所有的.和..并得到绝对路径。这样无论用户在什么目录下启动脚本,得到的都是明确位置。
如果项目需要兼容非常老的Python版本,也可以使用os.path模块。不过os.path返回的是字符串,每次处理都要重新调用函数,而pathlib的Path对象可以复用,并且能和open()直接配合,例如open(resolve_input(args.input), 'r')。
四、把参数解析与路径校验串起来
实际工程中,建议把两步合并:在argparse拿到原始字符串后,立刻转成Path对象并校验。下面给出一个完整可运行的骨架:
import argparse
from pathlib import Path
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', required=True)
parser.add_argument('-o', '--output', default='./out')
args = parser.parse_args()
in_path = Path(args.input).expanduser().resolve()
if not in_path.is_file():
parser.error('输入必须是一个存在的文件')
out_path = Path(args.output).expanduser().resolve()
out_path.mkdir(parents=True, exist_ok=True)
print('即将处理', in_path, '结果写入', out_path)
if __name__ == '__main__':
main()
这里用parser.error()在参数阶段就终止程序,比在业务逻辑里抛异常更友好,因为用户能看到清晰的参数错误提示而不是堆栈信息。
当脚本部署到不同机器时,这种写法能显著降低“在我电脑上能跑”的问题。即便用户传入相对路径或带波浪线的缩写,程序也会统一转换成绝对路径再使用。
五、常见误区与建议
一个典型误区是认为argparse的type=Path能自动校验文件存在。实际上即使写了type=Path,argparse也只是把字符串包成Path对象,不会检查磁盘上的实际情况,存在性判断仍需自己写。
另一个建议是不要在帮助文本里写死路径分隔符。用pathlib后,代码里基本不再出现/或,帮助信息也应描述为“目录或文件路径”而非“形如/opt/data的目录”,这样对Windows用户更友好。
| 处理方式 | 跨平台 | 代码可读性 | 存在校验 |
|---|---|---|---|
| 字符串拼接 | 差 | 低 | 无 |
| os.path | 好 | 中 | 需手动 |
| pathlib | 好 | 高 | 需手动 |
综合来看,用argparse接收参数、用pathlib规范化并校验路径,是Python命令行工具最稳妥的组合。它让脚本在接收外部输入时既灵活又安全,也减少了因环境差异导致的隐性Bug。