在C中编写可移植的命令行包装器

时间:2009-07-07 23:07:44

标签: c windows perl command-line

我正在编写一个名为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包装器,因为它不支持#!呢?

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

6 个答案:

答案 0 :(得分:4)

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

perl5i.bat

@echo off
perl -Mperl5i %*

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

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

<强>更新

我认为这会有效,但我不是shell向导,我没有便于测试的* nix系统。

perl5i

#!bash

perl -Mperl5i $@

再次更新:

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

忽略之前的更新。

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

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

又一次更新

您可能希望了解parl如何做到这一点。

答案 1 :(得分: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。

答案 2 :(得分:0)

Windows总是奇怪的情况。就个人而言,我不会尝试代码为Windows环境例外。一些替代方案是使用“bat wrappers”或ftype / assoc注册表黑客进行文件扩展。

Windows从DOS命令shell运行时忽略了shebang行,但具有讽刺意味的是在Apache for Windows中CGI-ing Perl时使用它。我厌倦了直接在我的网络程序中编码#!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 webhost时不会中断,我可以使用标准问题shebang line“#!/ usr / bin / perl -w”,而不必先使用SED疯狂部署。 :)

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

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

然后从DOS命令行,您可以单独调用“program.pl”而不是“perl -Mperl5i program.pl”

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

答案 3 :(得分:0)

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

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

如果您想使用和不使用控制台运行相同的程序,您应该阅读有关该主题的Raymond Chen

答案 4 :(得分:0)

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

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

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

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

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

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

答案 5 :(得分:-1)

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

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