【问题标题】:Writing a portable command line wrapper in C用 C 编写一个可移植的命令行包装器
【发布时间】:2026-01-07 08:30:01
【问题描述】:

我正在编写一个名为 perl5i 的 perl 模块。它的目的是修复一个模块中的一系列常见 Perl 问题(使用许多其他模块)。

要在命令行上为您编写的一个衬垫调用它:perl -Mperl5i -e 'say "Hello"' 我认为这太罗嗦了,所以我想提供一个 perl5i 包装器,以便您可以编写 perl5i -e 'say "Hello"'。我还希望人们能够使用#!/usr/bin/perl5i 编写脚本,因此它必须是编译后的 C 程序。

我想我所要做的就是将“-Mperl5i”推到参数列表的前面并调用 perl。这就是我尝试过的。

#include <unistd.h>
#include <stdlib.h>

/*
 * Meant to mimic the shell command
 *     exec perl -Mperl5i "$@"
 *
 * This is a C program so it works in a #! line.
 */

int main (int argc, char* argv[]) {
    int i;
    /* This value is set by a program which generates this C file */
    const char* perl_cmd = "/usr/local/perl/5.10.0/bin/perl";
    char* perl_args[argc+1];
    perl_args[0] = argv[0];
    perl_args[1] = "-Mperl5i";

    for( i = 1;  i <= argc;  i++ ) {
        perl_args[i+1] = argv[i];
   }

   return execv( perl_cmd, perl_args );
}

Windows 使这种方法复杂化。显然,Windows 中的程序没有传递参数数组,而是将所有参数作为单个字符串传递,然后进行自己的解析!因此,perl5i -e "say 'Hello'" 之类的东西变成了perl -Mperl5i -e say 'Hello',Windows 无法处理缺少引用的问题。

那么,我该如何处理呢?在 Windows 上用引号和转义符包裹所有内容?有图书馆可以帮我处理吗?有更好的方法吗?我可以不在 Windows 上生成 C 程序并将其编写为 perl 包装器,因为它不支持 #!还是?

更新:更清楚一点,这是随附的软件,因此需要使用特定外壳或调整外壳配置(例如,alias perl5i='perl -Mperl5i')的解决方案并不令人满意。

【问题讨论】:

    标签: c windows perl command-line


    【解决方案1】:

    对于 Windows,使用批处理文件。

    perl5i.bat

    @echo off
    perl -Mperl5i %*
    

    %* 是所有命令行参数减去%0

    在 Unixy 系统上,类似的 shell 脚本就足够了。

    更新:

    我认为这会起作用,但我不是 shell 向导,而且我没有方便测试的 *nix 系统。

    perl5i

    #!bash
    
    perl -Mperl5i $@
    

    再次更新:

    呃!现在我正确理解了您的#! 评论。我的 shell 脚本可以在 CLI 中运行,但不能在 #! 行中运行,因为 #!foo 要求 foo 是二进制文件。

    忽略之前的更新。

    Windows 似乎使一切都复杂化了。 我认为您最好使用批处理文件。

    您可以use a file association,将.p5iperl -Mperl5i %* 关联。当然,这意味着在注册表中捣乱,最好避免 IMO。最好在文档中包含有关如何手动添加关联的说明。

    又一次更新

    您可能想看看parl 是如何做到的。

    【讨论】:

    • 谢谢,我可能会直接使用 Windows 的批处理文件。 shell 脚本对于 Unix 来说是不够的,因为 #!只会尊重已编译的程序。
    • 然而,Windows 上批处理文件的一个缺点—— ^C-ing 出批处理脚本将导致“终止批处理”提示。对于像这样的简单包装脚本,该提示几乎没有用,因为即使您选择“N”,它仍然会终止 perl。
    • 在 Unix 中,你的 shell 应该有某种形式的别名函数,这样就不需要创建 shell 脚本了。
    • 您应该将 $@ 括在双引号中以防止参数拆分。 perl -Mperl5i "$@" 有关详细信息,请参阅 bash 手册“特殊参数”。
    • @Brad 有什么特别的原因吗?
    【解决方案2】:

    我无法重现您描述的行为:

    /* main.c */
    
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        int i;
        for (i = 0; i < argc; i++) {
            printf("%s\n", argv[i]);
        }
        return 0;
    }
    
    C:\> ShellCmd.exe a b c
    ShellCmd.exe
    a
    b
    c
    

    Visual Studio 2005 就是如此。

    【讨论】:

    • 尝试使用引号中的 args,例如 '-e "say 'hello'"',并注意它们是如何分开的。
    • 是的,但这对于 Windows shell 来说是正常的。我看不出这使您的包装器与 perl 本身有何不同。
    • 包装器在 argv 上接收“-e”、“say 'hello'”。 "say 'hello'" 周围的引号已被 shell 删除。如果您只是将其传递给另一个程序,它将得到“-e say 'hello'”,Windows 不知道如何处理。包装器(更可能是外壳)去除引号,因此内部程序没有它们的好处。如果您的程序尝试使用 argv 调用另一个程序,您会看到它。
    【解决方案3】:

    Windows 总是奇怪的情况。就个人而言,我不会尝试为 Windows 环境异常编写代码。一些替代方法是使用“bat wrappers”或 ftype/assoc Registry hacks 作为文件扩展名。

    当从 DOS 命令 shell 运行时,Windows 会忽略 shebang 行,但讽刺的是,在 Apache for Windows 中对 Perl 进行 CGI 处理时会使用它。由于迁移到 *nix 环境时的可移植性问题,我厌倦了直接在我的 Web 程序中编码 #!c:/perl/bin/perl.exe。相反,我在我的工作站上创建了一个 c:\usr\bin 目录,并从其默认位置复制了 perl.exe 二进制文件,通常是 c:\perl\bin 用于 AS Perl,c:\strawberry\perl\bin 用于 Strawberry Perl。因此,在 Windows 上的 Web 开发模式下,我的程序在迁移到 Linux/UNIX 虚拟主机时不会中断,并且我可以使用标准问题 shebang 行“#!/usr/bin/perl -w”,而不必事先让 SED 发疯到部署。 :)

    在 DOS 命令 shell 环境中,我要么显式设置我的 PATH,要么创建一个 ftype 指向带有嵌入式开关 -Mperl5i 的实际 perl.exe 二进制文件。 shebang 线被忽略。

    ftype p5i=c:\strawberry\perl\bin\perl.exe -Mperl5i %1 %*
    assoc .pl=p5i
    

    然后从 DOS 命令行你可以只调用“program.pl”而不是“perl -Mperl5i program.pl”

    所以“say”语句在 5.10 中工作,无需任何额外的哄骗,只需输入 Perl 程序本身的名称,它也可以接受可变数量的命令行参数。

    【讨论】:

      【解决方案4】:

      使用CommandLineToArgvW 构建您的argv,或者直接将您的命令行传递给CreateProcess

      当然,这需要一个单独的特定于 Windows 的解决方案,但您说您可以接受,这相对简单,并且通常专门针对目标系统编写关键部分有助于显着集成(来自用户的 POV) . YMMV。

      如果您想在有控制台和没有控制台的情况下运行相同的程序,您应该阅读Raymond Chen 的主题。

      【讨论】:

        【解决方案5】:

        在 Windows 上,在系统级别,命令行作为单个 UTF-16 字符串传递给启动的程序,因此在 shell 中输入的任何引号都按原样传递。因此,您的示例中的双引号不会被删除。这与 POSIX 世界完全不同,在 POSIX 世界中,shell 负责解析,启动的程序接收字符串数组。

        我在这里描述了系统级别的行为。但是,在您的 C(或您的 Perl)程序之间,通常有 C 标准库解析系统命令行字符串以将其提供给 main()wmain() 作为 argv[]。这是在您的进程中完成的,但如果您真的想控制解析的完成方式,或者以完整的 UTF-16 编码获取字符串,您仍然可以使用 GetCommandLineW() 访问原始命令行字符串。

        要了解有关 Windows 命令行解析怪癖的更多信息,请阅读以下内容:

        您可能还对 Win32 上 Padrewrapper I wrote 的代码感兴趣:这是一个名为 padre.exe 的 GUI 程序(这意味着如果从“开始”菜单启动它不会打开控制台)嵌入 perl 以启动 padre Perl 脚本。它还有一个小技巧:将argv[0] 更改为指向perl.exe,这样$^X 就可以用于启动外部perl 脚本了。

        您在示例代码中使用的execv 只是C 库中类似POSIX 行为的模拟。特别是它不会在您的参数周围添加引号,以便启动的 perl 按预期工作。你必须自己做。

        请注意,由于客户端负责解析,每个客户端客户端都可以按照自己的方式进行。许多人让 libc 来做,但不是全部。因此,Windows 上不存在通用的命令行生成规则:该规则取决于启动的程序。 您可能仍然对“尽力而为”的实现感兴趣,例如 Win32::ShellQuote

        【讨论】:

          【解决方案6】:

          如果您能够使用 C++,那么 Boost.Program_options 可能会有所帮助:

          http://www.boost.org/doc/libs/1_39_0/doc/html/program_options.html

          【讨论】:

          • 对不起,只是标准的 ANSI C 89。我不知道他们可能有什么编译器(如果有的话)。
          最近更新 更多