Python中递归控制台工具的最佳实践

4 投票
4 回答
1294 浏览
提问于 2025-04-16 03:44

对于一个处理目录树中选定文件的命令行工具,最好的实践是什么(接口和实现)?

我想到一个例子,但我在寻找一个“最佳实践”:

flipcase foo.txt foo2.txt

可以处理 foo.txt 并将结果保存为 foo2.txt。

flipcase -rv *.txt

可以处理当前目录中的所有文本文件。
-r--recursive 会包括所有子目录。
-v 在处理时会打印一些信息到标准输出。

我看到这个例子有一个问题,就是 *.txt 这个参数有时会被 shell 扩展(在 Unix 和 Vista 上),所以在遍历子目录时我不能使用这个模式。
我猜原因是,在 Unix 上这样的工具通常会和 find 命令结合使用,但在 Windows 上似乎不太常见。这也让最后打印总结变得困难。

需求:

  • 必须在 Unix、Windows XP、Windows 7 和 Mac 上运行
  • 应该遵循这些平台上的常见约定。(是的,我知道。但我在寻找一个合理的折中方案。例如,在 Windows 上使用 - 而不是 / 是可以的。)
  • 不应该依赖于单独的 find 命令,就像 grep 那样。
  • 必须支持单个文件、文件模式和目录层次中的模式。
  • 应该使用标准的 Python 库构建,例如 OptionParseros.walk
  • 可以处理多个模式,例如 *.txt,*.html

关于设计决策的其他问题:

  • 这个工具应该返回什么(状态码)?
  • 这个工具应该处理哪些控制键,方式是什么?
  • 应该支持标准输入而不是单个文件吗?可配置还是自动检测?
  • 应该支持输出重定向吗?可配置还是自动检测?在这种情况下,如何处理调试输出?
  • 模式应该是通配符语法,还是正则表达式?
  • 是否有支持递归的通用模式语法?也许 recursive:*.txt,在这种情况下就不需要 -r 选项了。
  • 创建修改文件的备份的最佳实践是什么?使用 -b 选项,还是默认有备份并添加 --no-backup 选项?
  • 对于单个文件,应该能够指定目标文件名。怎么做?
  • 应该打印哪些状态信息,如何配置?默认应该详细打印,还是允许使用 -q 来安静模式?或者总是打印一点点,允许使用 -v(或 -vv)来增强输出,或者使用 -q 完全静音?

我并不期待得到一个单一的正确答案,但希望能得到一些想法和指向好的示例项目的建议。

4 个回答

1

命令行工具处理目录树中选定文件的最佳实践(接口和实现)是什么?

我觉得没有一个统一的标准或者“最佳实践”来实现命令行工具。不过,通过查看和尝试一些做得很好的工具,比如GNU核心工具集,你会获得很多有用的见解。

另外,我觉得你可能也在寻找这样的内容:http://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html

了解和尝试Unix的做法,实际上可以帮助你解决很多关于设计决策的问题。

我看到这个例子有一个问题,就是*.txt这个参数有时候会被shell(Unix和Vista)自动扩展,所以在遍历子目录时我不能使用这个模式。

在Unix中,*会被自动扩展。我不太确定Windows的情况,但如果我没记错的话,*不会被扩展,所以你可以直接使用glob.glob(sys.argv[1])。对于Unix来说,一个解决办法是对通配符进行转义,但应该还有更好的方法。

1

关于你提到的“通配符匹配”部分,实际上你提到的列表中,唯一不支持的就是Windows。UNIX系统的处理方式,以及一种不错的方法,就是让命令行自己来处理通配符匹配。你只需要得到一个文件列表。我不知道有什么UNIX工具会自己处理通配符匹配(在这种基本情况下)。我建议你也不要自己去做,而是依赖命令行。

在Windows上,你可以建议大家使用Cygwin这样的命令行工具。当然,Windows用户通常不太喜欢使用命令行,所以如果你做一个图形界面(GUI),他们会更开心。

这部分没有涉及到你的-r选项。不过这就变得复杂了。你想给用户提供一个功能,让他们可以指定“所有子目录中扩展名为.txt的文件”吗?要注意,像ZSH这样的现代命令行工具可以处理递归的通配符匹配,比如:

rm **/*.tmp

而且,正如你所说的,你总是可以使用find命令来替代。所以在这里的建议确实需要考虑到你工具的具体情况。rsync实现了自己的-r选项是有好处的,但一个假设的flipcase可能就不需要了。

2

根据我的经验,最好的起步方式是创建一个遵循基本Unix原则的工具——也就是说,它应该从标准输入读取数据,然后写入标准输出。这样,大家就可以灵活地使用你的工具:

flipcase input.txt > output.txt
othercommand | flipcase > output.txt
flipcase | othercommand > ouput.txt
flipcase input1.txt  input2.txt > output.txt

下一个功能可能是就地编辑:

# Modify input files directly.
flipcase -i input.txt

# Create backup copies before modifying originals.
flipcase -i --backup-suffix '_BAK' input.txt
flipcase -i --backup-prefix 'BAK_' input.txt

# Regex for power users.
flipcase -i --backup-regex 's/foo/bar/' input.txt

在详细模式下,这个工具不应该写入标准输出,因为这会和上面提到的基本原则冲突。它应该写入标准错误或者用户自定义的日志文件。

flipcase -v         input.txt > output.txt
flipcase -v log.txt input.txt > output.txt

接下来,你可以添加递归功能。这里的方向不太明确,但我可以给你一些想法。在典型的递归情况下,程序的参数可能是目录,用户需要提供额外的选项来定义不同的过滤行为(也就是说,处理哪些类型的文件)。

flipcase -r -i --backup-suffix '_BAK' --filter-glob '*.txt' dir1 dir2
flipcase -r -i --backup-suffix '_BAK' --filter-glob '*.txt' --filter-glob 'log*.dat' dir
flipcase -r -i --backup-suffix '_BAK' --filter-regex 'log\w+\.(txt|log)$' dir1 dir2

# Don't do in-place editing. Instead create new files within the structure.
flipcase -r --newname-suffix '_NEW'              --filter-glob '*.txt' dir1 dir2
flipcase -r --newname-regex 's/\.txt$/_new.txt/' --filter-glob '*.txt' dir1 dir2

# Create the backups or the new files in a parallel directory
# structure rather than within the original structure.
flipcase -r -i --backup-tree 'backup_dir'   --filter-glob '*.txt' dir1 dir2
flipcase -r -i --new-tree    'newfiles_dir' --filter-glob '*.txt' dir1 dir2

撰写回答