【问题标题】:What are the best-practices for implementing a CLI tool in Perl?在 Perl 中实现 CLI 工具的最佳实践是什么?
【发布时间】:2009-07-26 06:17:24
【问题描述】:

我正在使用 Perl 实现一个 CLI 工具。 我们可以在这里遵循哪些最佳实践?

【问题讨论】:

  • 有任何关于用 Perl 或任何语言编写 CLI 工具的指南吗?

标签: perl command-line


【解决方案1】:

作为序言,我花了 3 年时间为一家大型金融公司设计和实施一个非常复杂的 Perl 命令行工具集。下面的想法基本上是我们团队设计指南的一部分。

用户界面

  1. 命令行选项:允许尽可能多的有默认值。

  2. 对于具有超过 2 个选项的任何命令,没有位置参数。

  3. 具有可读的选项名称。如果命令行长度是非交互式调用的一个问题(例如,一些未命名的遗留 shell 对命令行有较短的限制),请提供短别名 - GetOpt::Long 很容易做到这一点。

  4. 至少,在“-help”消息中打印所有选项的默认值。

    更好的是,打印所有选项的“当前”值(例如,如果参数和值与“-help”一起提供,帮助消息将从命令行打印参数值)。这样,人们可以为复杂的命令组装命令行字符串,并在实际运行之前通过附加“-help”来验证它。

  5. 遵循 Unix 标准约定,如果程序因错误终止,则以非零返回码退出。

  6. 如果您的程序可能会产生有用的(例如值得捕获/grepping/whatnot)输出,请确保将任何错误/诊断消息发送到 STDERR,以便它们易于分离。

  7. 理想情况下,允许用户通过命令行参数指定输入/输出文件,而不是强制“”重定向 - 这让需要使用您的命令构建复杂管道的人的生活更加简单.错误消息同上 - 有日志文件选项。

  8. 如果命令有副作用,那么使用“whatif/no_post”选项通常是一个非常好的主意。

实施

  1. 如前所述,不要重新发明轮子。使用标准命令行参数处理模块 - MooseX::Getopt 或 Getopt::Long

  2. 对于 Getopt::Long,将所有参数分配给单个哈希,而不是单个变量。许多有用的模式包括将该 CLI args 哈希传递给对象构造函数。

  3. 确保您的错误消息清晰且内容丰富...例如包括“$!”在任何与 IO 相关的错误消息中。值得在代码中花费额外的 1 分钟和 2 行来产生单独的“找不到文件”和“文件不可读”错误,而不是在生产紧急情况下花费 30 分钟,因为生产部门误诊了一个不可读的文件错误“无输入文件”的操作——这是一个真实的例子。

  4. 并非真正特定于 CLI,但验证所有参数,最好是在获取参数后立即验证。 CLI 不允许像 webapps 那样进行“前端”验证,所以要格外小心。

  5. 如上所述,模块化业务逻辑。除了已经列出的其他原因之外,我必须将现有的 CLI 工具重新实现为 Web 应用程序的次数非常多 - 如果逻辑已经是正确设计的 perm 模块,那么这并不难。

有趣的链接

CLI Design Patterns - I think this is ESR's

我会尝试添加更多我记得的项目符号。

【讨论】:

    【解决方案2】:

    使用 POD 记录您的工具,遵循手册页的指南;至少包括以下部分:名称、概要、描述、作者。一旦你有了正确的 POD,你就可以使用 pod2man 生成手册页,使用 perldoc your-script.pl 在控制台查看文档。

    使用为您处理命令行选项的模块。我真的很喜欢将Getopt::LongPod::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;
    }
    

    【讨论】:

    • 我不认为所有这些对退出值的担忧是一个好主意。 Perl 在没有退出或死亡的情况下提供了一个隐式的 exit(0)。如果您在出现问题时死亡并且没有做任何特别的事情,您将获得一个表现良好的程序。我认为完全放弃 main 可以改进这个骨架。 Perl 不是 C。
    • main 函数看起来很像 C 程序的原因是,如果没有它,您将在程序中声明的所有变量都将成为全局词法范围变量。使用“main”函数,所有内部变量都被限定为该单个函数。
    • 我认为任何使用“严格”的人都应该关心其变量的范围。这是一个示例,为什么有些人可能喜欢在一个函数中插入所有代码。想象一下,整个程序没有在词法范围内隔离其变量,而是添加了一个新函数。如果新函数中的变量不是用“my”声明的变量,并且它恰好存在于父作用域中,这将引起很多麻烦。在这种情况下,即使“使用严格”也无济于事。原因是程序把它所有的变量都变成了文件中的全局词法变量。
    • "如果新函数中的变量不使用 'my' 声明变量" - 如果你的函数没有本地化它们的变量,你已经做错了,并且写了一个 @ 987654324@ 子程序对你没有帮助,它只会隐藏你的代码错误的事实。
    • 有时会忘记在变量前面写 my (因为重构等)。这并不意味着代码有缺陷。在这种情况下,Perl 会在外部块中找到一个变量并使用它。喜欢或不喜欢普通 perl 脚本中不在块中的所有变量都是该文件中的全局变量。您可以忽略这一点并假装使用小写名称会告诉 perl 不要用作全局变量,但它不会。为此,我使用了一个主块。
    【解决方案3】:

    我学到的一些教训:

    1) 始终使用 Getopt::Long

    2) 通过 --help 提供使用帮助,最好是提供常见场景的示例。它可以帮助人们不知道或忘记如何使用该工具。 (即,在六个月内)。

    3) 除非用户很清楚为什么,否则不要长时间(>5s)没有输出给用户。 'print "Row $row...\n" unless ($row % 1000)' 之类的东西有很长的路要走。

    4) 对于长时间运行的操作,尽可能让用户恢复。过百万的 50 万,死了,然后重新开始,真的很糟糕。

    5) 将您正在做的事情的逻辑分离到模块中,并尽可能将实际的 .pl 脚本保留为准系统;解析选项,显示帮助,调用基本方法等。你不可避免地会找到你想要重用的东西,这让它变得容易多了。

    【讨论】:

    • for 3), always 为用户提供 -q 选项以保持安静
    • #3 仅对不以批处理模式运行的程序有用
    【解决方案4】:

    最重要的是拥有标准选项

    不要自作聪明,要与already existing tools保持一致。

    如何实现这一点也很重要,但只是第二

    实际上,这对所有 CLI 接口都是通用的。

    【讨论】:

      【解决方案5】:

      CPAN 上有几个模块可以让编写 CLI 程序变得更加容易:


      如果您的应用是基于 Moose 的,请查看 MooseX::GetoptMooseX::Runnable

      【讨论】:

      • 这是否具有用于命令执行的制表符补全功能??
      • @Anandan:这些模块帮助您构建 Perl 应用程序,而不是从 shell/CLI 运行。您的评论似乎指向了一个与迄今为止每个人都已回答的问题不同的问题。如果您的问题是“如何模拟 CLI”,那么我建议您提出一个新问题和/或在 CPAN 上查找 IO::Prompt 等模块。希望有帮助吗?
      【解决方案6】:

      以下几点并非 Perl 特有的,但我发现许多 Perl CL 脚本在这些方面存在不足:

      1. 使用常用命令行选项。要显示版本号,请使用 -v 或 --version 而不是 --ver。对于递归处理 -r (或者也许 -R 虽然在我的 Gnu/Linux 经验中 -r 更常见)不是 --rec。如果人们能记住参数,他们就会使用你的脚本。如果您能记住“它的工作方式类似于 grep”或其他一些熟悉的实用程序,那么学习新命令很容易。

      2. 许多命令行工具处理“当前目录”中的“事物”(文件或目录)。虽然这很方便,但请确保您还添加了命令行选项以明确标识要处理的文件或目录。这样可以更轻松地将您的实用程序放入管道中,而无需开发人员发出一堆 cd 命令并记住它们所在的目录。

      【讨论】:

        【解决方案7】:

        您应该使用Perl modules 使您的代码可重用且易于理解。
        应该看看Perl best practices

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多