用C语言编写一个可移植的命令行包装器

12

我正在编写一个名为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 -esay'Hello',而Windows无法处理缺少引号的情况。

那么,我该如何处理呢? 在Windows上将所有内容都用引号和转义符包含起来吗? 有没有库可以为我处理这个问题? 是否有更好的方法? 我能否只在Windows上编写一个perl包装器而不生成C程序,因为它根本不支持#!?

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

6个回答

4

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

perl5i.bat

@echo off
perl -Mperl5i %*

%*是除了%0之外的所有命令行参数。

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

更新:

我认为这个方法可以运行,但我不是shell专家,也没有*nix系统可用于测试。

perl5i

#!bash

perl -Mperl5i $@

再次更新:

哎呀!现在我正确理解了你的#!评论。我的shell脚本可以在CLI中工作,但不能在#!行中工作,因为#!foo要求foo是一个二进制文件。

请忽略之前的更新。

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

你可以使用文件关联,将.p5iperl -Mperl5i %*相关联。当然,这意味着要在注册表中修改,但我认为最好避免这样做。最好在文档中包含手动添加关联的说明。

还有一个更新

你可能需要看看parl是如何实现的。


1
谢谢,我可能会使用Windows的批处理文件。对于Unix来说,shell脚本是不够的,因为#!只会支持编译过的程序。 - Schwern
3
然而,在Windows上使用批处理文件的一个缺点是,如果从批处理脚本中^C退出,会弹出“终止批处理进程”提示。对于像这样简单的包装脚本来说,这个提示几乎没有用处,因为即使你选择“N”,它仍然会终止perl。 - MiffTheFox
2
你应该用双引号将$@括起来,以防止参数分割。perl -Mperl5i“$@”请参阅bash手册中的“特殊参数”以获取详细信息。 - Hynek -Pichi- Vychodil
请使用 perl5i.cmd 而不是 perl5i.bat,除非您需要在 Win9x 计算机上运行它。 - Brad Gilbert
1
@Brad 有什么特别的原因吗? - Schwern
显示剩余4条评论

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'””,并注意它们如何被分割。 - Schwern
是的,但这在Windows shell中很正常。 我不明白这会使您的包装器与perl本身有任何区别。 - hexten
包装器在argv上接收“-e”,“say 'hello'”。 shell已经剥离了“say 'hello'”周围的引号。 如果您只是将其传递给另一个程序,它将获得“-e say 'hello'”,而Windows不知道如何处理它。 包装器(更可能是shell)剥离引号,因此内部程序无法从中受益。 如果您的程序尝试使用argv调用另一个程序,则会看到它。 - Schwern

0

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

当然,这需要一个单独的Windows特定解决方案,但你说你可以接受这个,这相对简单,并且通常针对目标系统编写关键部分有助于显著提高集成(从用户的角度)的效果。 你的情况可能会有所不同。

如果你想要在有控制台和没有控制台的情况下运行同一个程序,你应该阅读Raymond Chen关于这个主题的文章。


0

在Windows系统中,命令行以单个UTF-16字符串的形式传递给启动的程序,因此在shell中输入的任何引号都会原样传递。因此,您示例中的双引号不会被删除。这与POSIX世界非常不同,其中shell执行解析工作,启动的程序接收一个字符串数组。

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

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

你可能也会对我为Win32写的包装器代码以及它嵌入perl来启动padre Perl脚本的GUI程序Padre感兴趣。这是一个GUI程序(这意味着如果从开始菜单启动,它将不会打开控制台),名为padre.exe。它还有一个小技巧:它更改argv[0]以指向perl.exe,以便$^X将成为可用于启动外部perl脚本的内容。
你在示例代码中使用的execv只是C库中类似POSIX行为的仿真。特别是它不会在你的参数周围添加引号,以便启动的perl按预期工作。你必须自己做这件事。
请注意,由于客户端负责解析的事实,每个客户端都可以按照自己的方式进行解析。许多客户端使用libc进行解析,但并非所有客户端都是这样。因此,在Windows上无法存在通用的命令行生成规则:该规则取决于所启动的程序。您仍然可能对“尽力而为”的实现感兴趣,例如Win32::ShellQuote

0

Windows一直是个奇怪的情况。就我个人而言,我不会尝试为Windows环境编写例外代码。一些替代方案是使用“bat包装器”或ftype / assoc注册表hack来处理文件扩展名。

当在DOS命令shell中运行时,Windows忽略了shebang行,但具有讽刺意味的是,在Apache for Windows中CGI-ing Perl时使用它。由于可移植性问题,我已经厌倦了在我的Web程序中直接编写#!c:/perl/bin/perl.exe,当移动到*nix环境时。相反,我在我的工作站上创建了一个c:\usr\bin目录,并从其默认位置复制了perl.exe二进制文件,通常为AS Perl的c:\perl\bin和Strawberry Perl的c:\strawberry\perl\bin。因此,在Windows上的Web开发模式中,当我的程序迁移到Linux / UNIX Web主机时,我的程序不会崩溃,并且我可以使用标准问题shebang行“#!/usr/bin/perl -w”,而无需在部署之前进行SED修改。:)

在DOS命令shell环境中,我只需显式设置我的PATH或创建ftype,指向实际的perl.exe二进制文件并嵌入开关-Mperl5i。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程序本身的名称即可,并且它也可以接受可变数量的命令行参数。


-1

抱歉,只能使用标准的 ANSI C 89。我不知道他们可能有哪些编译器(如果有的话)。 - Schwern

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接