【发布时间】:2009-07-26 06:17:24
【问题描述】:
我正在使用 Perl 实现一个 CLI 工具。 我们可以在这里遵循哪些最佳实践?
【问题讨论】:
-
有任何关于用 Perl 或任何语言编写 CLI 工具的指南吗?
标签: perl command-line
我正在使用 Perl 实现一个 CLI 工具。 我们可以在这里遵循哪些最佳实践?
【问题讨论】:
标签: perl command-line
作为序言,我花了 3 年时间为一家大型金融公司设计和实施一个非常复杂的 Perl 命令行工具集。下面的想法基本上是我们团队设计指南的一部分。
命令行选项:允许尽可能多的有默认值。
对于具有超过 2 个选项的任何命令,没有位置参数。
具有可读的选项名称。如果命令行长度是非交互式调用的一个问题(例如,一些未命名的遗留 shell 对命令行有较短的限制),请提供短别名 - GetOpt::Long 很容易做到这一点。
至少,在“-help”消息中打印所有选项的默认值。
更好的是,打印所有选项的“当前”值(例如,如果参数和值与“-help”一起提供,帮助消息将从命令行打印参数值)。这样,人们可以为复杂的命令组装命令行字符串,并在实际运行之前通过附加“-help”来验证它。
遵循 Unix 标准约定,如果程序因错误终止,则以非零返回码退出。
如果您的程序可能会产生有用的(例如值得捕获/grepping/whatnot)输出,请确保将任何错误/诊断消息发送到 STDERR,以便它们易于分离。
理想情况下,允许用户通过命令行参数指定输入/输出文件,而不是强制“”重定向 - 这让需要使用您的命令构建复杂管道的人的生活更加简单.错误消息同上 - 有日志文件选项。
如果命令有副作用,那么使用“whatif/no_post”选项通常是一个非常好的主意。
如前所述,不要重新发明轮子。使用标准命令行参数处理模块 - MooseX::Getopt 或 Getopt::Long
对于 Getopt::Long,将所有参数分配给单个哈希,而不是单个变量。许多有用的模式包括将该 CLI args 哈希传递给对象构造函数。
确保您的错误消息清晰且内容丰富...例如包括“$!”在任何与 IO 相关的错误消息中。值得在代码中花费额外的 1 分钟和 2 行来产生单独的“找不到文件”和“文件不可读”错误,而不是在生产紧急情况下花费 30 分钟,因为生产部门误诊了一个不可读的文件错误“无输入文件”的操作——这是一个真实的例子。
并非真正特定于 CLI,但验证所有参数,最好是在获取参数后立即验证。 CLI 不允许像 webapps 那样进行“前端”验证,所以要格外小心。
如上所述,模块化业务逻辑。除了已经列出的其他原因之外,我必须将现有的 CLI 工具重新实现为 Web 应用程序的次数非常多 - 如果逻辑已经是正确设计的 perm 模块,那么这并不难。
CLI Design Patterns - I think this is ESR's
我会尝试添加更多我记得的项目符号。
【讨论】:
使用 POD 记录您的工具,遵循手册页的指南;至少包括以下部分:名称、概要、描述、作者。一旦你有了正确的 POD,你就可以使用 pod2man 生成手册页,使用 perldoc your-script.pl 在控制台查看文档。
使用为您处理命令行选项的模块。我真的很喜欢将Getopt::Long 与Pod::Usage 结合使用,这样调用 --help 将显示一个很好的帮助消息。
确保您的脚本无论成功与否都返回正确的退出值。
这是执行所有这些操作的脚本的一个小框架:
#!/usr/bin/perl
=head1 NAME
simplee - simple program
=head1 SYNOPSIS
simple [OPTION]... FILE...
-v, --verbose use verbose mode
--help print this help message
Where I<FILE> is a file name.
Examples:
simple /etc/passwd /dev/null
=head1 DESCRIPTION
This is as simple program.
=head1 AUTHOR
Me.
=cut
use strict;
use warnings;
use Getopt::Long qw(:config auto_help);
use Pod::Usage;
exit main();
sub main {
# Argument parsing
my $verbose;
GetOptions(
'verbose' => \$verbose,
) or pod2usage(1);
pod2usage(1) unless @ARGV;
my (@files) = @ARGV;
foreach my $file (@files) {
if (-e $file) {
printf "File $file exists\n" if $verbose;
}
else {
print "File $file doesn't exist\n";
}
}
return 0;
}
【讨论】:
我学到的一些教训:
1) 始终使用 Getopt::Long
2) 通过 --help 提供使用帮助,最好是提供常见场景的示例。它可以帮助人们不知道或忘记如何使用该工具。 (即,你在六个月内)。
3) 除非用户很清楚为什么,否则不要长时间(>5s)没有输出给用户。 'print "Row $row...\n" unless ($row % 1000)' 之类的东西有很长的路要走。
4) 对于长时间运行的操作,尽可能让用户恢复。过百万的 50 万,死了,然后重新开始,真的很糟糕。
5) 将您正在做的事情的逻辑分离到模块中,并尽可能将实际的 .pl 脚本保留为准系统;解析选项,显示帮助,调用基本方法等。你不可避免地会找到你想要重用的东西,这让它变得容易多了。
【讨论】:
【讨论】:
CPAN 上有几个模块可以让编写 CLI 程序变得更加容易:
如果您的应用是基于 Moose 的,请查看 MooseX::Getopt 和 MooseX::Runnable
【讨论】:
以下几点并非 Perl 特有的,但我发现许多 Perl CL 脚本在这些方面存在不足:
使用常用命令行选项。要显示版本号,请使用 -v 或 --version 而不是 --ver。对于递归处理 -r (或者也许 -R 虽然在我的 Gnu/Linux 经验中 -r 更常见)不是 --rec。如果人们能记住参数,他们就会使用你的脚本。如果您能记住“它的工作方式类似于 grep”或其他一些熟悉的实用程序,那么学习新命令很容易。
许多命令行工具处理“当前目录”中的“事物”(文件或目录)。虽然这很方便,但请确保您还添加了命令行选项以明确标识要处理的文件或目录。这样可以更轻松地将您的实用程序放入管道中,而无需开发人员发出一堆 cd 命令并记住它们所在的目录。
【讨论】:
您应该使用Perl modules 使您的代码可重用且易于理解。
应该看看Perl best practices
【讨论】: