简体   繁体   English

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

[英]Writing a portable command line wrapper in C

I'm writing a perl module called perl5i . 我正在编写一个名为perl5i的perl模块。 Its aim is to fix a swath of common Perl problems in one module (using lots of other modules). 它的目标是在一个模块中修复一大堆常见的Perl问题(使用许多其他模块)。

To invoke it on the command line for one liners you'd write: perl -Mperl5i -e 'say "Hello"' I think that's too wordy so I'd like to supply a perl5i wrapper so you can write perl5i -e 'say "Hello"' . 要在命令行上为一个内容调用它,你会写: perl -Mperl5i -e 'say "Hello"'我认为这太冗长所以我想提供一个perl5i包装器所以你可以写perl5i -e 'say "Hello"' I'd also like people to be able to write scripts with #!/usr/bin/perl5i so it must be a compiled C program. 我也希望人们能够用#!/usr/bin/perl5i编写脚本,所以它必须是一个已编译的C程序。

I figured all I had to do was push "-Mperl5i" onto the front of the argument list and call perl. 我想我所要做的就是将“-Mperl5i”推到参数列表的前面并调用perl。 And that's what I tried. 这就是我尝试过的。

#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 complicates this approach. Windows使这种方法复杂化。 Apparently programs in Windows are not passed an array of arguments, they are passed all the arguments as a single string and then do their own parsing! 显然,Windows中的程序不传递参数数组,它们将所有参数作为单个字符串传递,然后进行自己的解析! Thus something like perl5i -e "say 'Hello'" becomes perl -Mperl5i -e say 'Hello' and Windows can't deal with the lack of quoting. 因此像perl5i -e "say 'Hello'"这样的东西变成perl -Mperl5i -e say 'Hello'而Windows无法处理缺少引用。

So, how can I handle this? 那么,我该怎么处理呢? Wrap everything in quotes and escapes on Windows? 在Windows上包装引号中的所有内容并转义? Is there a library to handle this for me? 有没有图书馆来处理这个问题? Is there a better approach? 有更好的方法吗? Could I just not generate a C program on Windows and write it as a perl wrapper as it doesn't support #! 我可以不在Windows上生成C程序并将其写为perl包装器,因为它不支持#! anyway? 无论如何?

UPDATE: Do be more clear, this is shipped software so solutions that require using a certain shell or tweaking the shell configuration (for example, alias perl5i='perl -Mperl5i' ) aren't satisfactory. 更新:更清楚,这是运送软件所以需要使用某个shell或调整shell配置的解决方案(例如, alias perl5i='perl -Mperl5i' )并不令人满意。

For Windows, use a batch file. 对于Windows,请使用批处理文件。

perl5i.bat perl5i.bat

@echo off
perl -Mperl5i %*

%* is all the command line parameters minus %0 . %*是所有命令行参数减去%0

On Unixy systems, a similar shell script will suffice. 在Unixy系统上,类似的shell脚本就足够了。

Update: 更新:

I think this will work, but I'm no shell wizard and I don't have an *nix system handy to test. 我认为这会有效,但我不是shell向导,我没有一个* nix系统可以方便地进行测试。

perl5i perl5i

#!bash

perl -Mperl5i $@

Update Again: 再次更新:

DUH! DUH! Now I understood your #! 现在我明白了你的#! comment correctly. 正确评论。 My shell script will work from the CLI but not in a #! 我的shell脚本将在CLI中运行,但不在#! line, since #!foo requries that foo is a binary file. 自从#!foo requries认为foo是一个二进制文件。

Disregard previous update. 忽略以前的更新。

It seems like Windows complicates everything. 似乎Windows使一切变得复杂。 I think your best there is to use a batch file. 我认为你最好使用批处理文件。

You could use a file association , associate .p5i with perl -Mperl5i %* . 您可以使用文件关联 ,将.p5iperl -Mperl5i %*关联。 Of course this means mucking about in the registry, which is best avoided IMO. 当然这意味着在注册表中捣乱,最好避免IMO。 Better to include instructions on how to manually add the association in your docs. 最好包含有关如何在文档中手动添加关联的说明。

Yet another update 又一次更新

You might want to look at how parl does it. 您可能想看看parl是如何做到的。

I can't reproduce the behaviour your describe: 我无法重现你描述的行为:

/* 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

That's with Visual Studio 2005. 那就是Visual Studio 2005。

On Windows, at the system level, the command-line is passed to the launched program as a single UTF-16 string, so any quotes entered in the shell are passed as is. 在Windows上,在系统级别,命令行作为单个UTF-16字符串传递给已启动的程序,因此在shell中输入的任何引号都将按原样传递。 So the double quotes from your example are not removed. 因此,不会删除示例中的双引号。 This is quite different from the POSIX world where the shell does the job of parsing and the launched program receives an array of strings. 这与POSIX世界完全不同,在这个世界中shell执行解析工作,并且启动的程序接收字符串数组。

I've described here the behavior at the system level. 我在这里描述了系统级别的行为。 However, between your C (or your Perl) program there is usually the C standard library that is parsing the system command line string to give it to main() or wmain() as argv[] . 但是,在您的C(或您的Perl)程序之间通常有C标准库正在解析系统命令行字符串以将其作为argv[]提供给main()wmain() This is done inside your process, but you can still access the original command line string with GetCommandLineW() if you really want to control how the parsing is done, or get the string in its full UTF-16 encoding. 这是在您的进程内完成的,但如果您真的想要控制解析的完成方式,或者以完整的UTF-16编码获取字符串,您仍然可以使用GetCommandLineW()访问原始命令行字符串。

To learn more about the Windows command-line parsing quirks, read the following: 要了解有关Windows命令行解析怪癖的更多信息,请阅读以下内容:

You may also be interested by the code of the wrapper I wrote for Padre on Win32: this is a GUI program (which means that it will not open a console if launched from the Start menu) called padre.exe that embeds perl to launch the padre Perl script. 您可能也对在Win32上为Padre 编写包装器的代码感兴趣:这是一个GUI程序(这意味着它不会打开控制台,如果从“开始”菜单启动),名为padre.exe ,它嵌入了perl来启动padre Perl脚本。 It also does a small trick: it changes argv[0] to point it to perl.exe so that $^X will be something usable to launch external perl scripts. 它还有一个小技巧:它更改argv[0]将其指向perl.exe以便$^X可用于启动外部perl脚本。

The execv you are using in your example code is just an emulation in the C library of the POSIX-like behavior. 您在示例代码中使用的execv只是类似POSIX行为的C库中的仿真。 In particular it will not add quotes around your arguments so that the launched perl works as expected. 特别是它不会在您的参数周围添加引号,以便启动的perl按预期工作。 You have to do that yourself. 你必须自己做。

Note that due to the fact that the client is responsible for parsing, each client client can do it the way it wants. 请注意,由于客户端负责解析,每个客户端客户端都可以按照自己的方式进行解析。 Many let the libc do it, but not all. 许多让libc这样做,但不是全部。 So generic command-line generation rules on Windows can not exist: the rule depend on the program launched. 因此,Windows上的通用命令行生成规则不存在:规则取决于启动的程序。 You may still be interested in "best effort" implementation such as Win32::ShellQuote . 您可能仍然对“尽力而为”的实现感兴趣,例如Win32 :: ShellQuote

Windows is always the odd case. Windows总是奇怪的情况。 Personally, I wouldn't try to code for the Windows environment exception. 就个人而言,我不会尝试代码为Windows环境例外。 Some alternatives are using "bat wrappers" or ftype/assoc Registry hacks for a file extension. 一些替代方案是使用“bat wrappers”或ftype / assoc Registry hacks来进行文件扩展。

Windows ignores the shebang line when running from a DOS command shell, but ironically uses it when CGI-ing Perl in Apache for Windows. 从DOS命令shell运行时,Windows忽略了shebang行,但具有讽刺意味的是在Apache for Windows中CGI-ing Perl时使用它。 I got tired of coding #!c:/perl/bin/perl.exe directly in my web programs because of portability issues when moving to a *nix environment. 我厌倦了直接在我的网络程序中编码#!c:/perl/bin/perl.exe,因为移动到* nix环境时可移植性问题。 Instead I created ac:\\usr\\bin directory on my workstation and copied the perl.exe binary from its default location, typically c:\\perl\\bin for AS Perl and c:\\strawberry\\perl\\bin for Strawberry Perl. 相反,我在我的工作站上创建了ac:\\ usr \\ bin目录,并从其默认位置复制了perl.exe二进制文件,对于AS Perl通常为c:\\ perl \\ bin,对于Strawberry Perl通常为c:\\ strawberry \\ perl \\ bin。 So in web development mode on Windows my programs wouldn't break when migrated to a Linux/UNIX webhost, and I could use a standard issue shebang line "#!/usr/bin/perl -w" without having to go SED crazy prior to deployment. 因此,在Windows上的Web开发模式中,我的程序在迁移到Linux / UNIX webhost时不会中断,我可以使用标准问题shebang line“#!/ usr / bin / perl -w”,而不必先使用SED疯狂部署。 :) :)

In the DOS command shell environment I just either set my PATH explicitly or create a ftype pointing to the actual perl.exe binary with embedded switch -Mperl5i. 在DOS命令shell环境中,我只是显式设置我的PATH或创建一个ftype,指向具有嵌入式开关-Mperl5i的实际perl.exe二进制文件。 The shebang line is ignored. shebang线被忽略了。

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

Then from the DOS command line you can just call "program.pl" by itself instead of "perl -Mperl5i program.pl" 然后从DOS命令行,你可以自己调用“program.pl”而不是“perl -Mperl5i program.pl”

So the "say" statement worked in 5.10 without any additional coaxing just by entering the name of the Perl program itself, and it would accept a variable number of command line arguments as well. 因此,只需输入Perl程序本身的名称,“say”语句就可以在5.10中工作而无需任何额外的哄骗,并且它也会接受可变数量的命令行参数。

Use CommandLineToArgvW to build your argv, or just pass your command line directly to CreateProcess . 使用CommandLineToArgvW构建您的argv,或者直接将命令行传递给CreateProcess

Of couse, this requires a separate Windows-specific solution, but you said you're okay with that, this is relatively simple, and often coding key pieces specifically to the target system helps integration (from the users' POV) significantly. 当然,这需要一个单独的Windows特定解决方案,但是你说你没关系,这是相对简单的,并且通常专门针对目标系统编写关键部分有助于显着地集成(来自用户的POV)。 YMMV. 因人而异。

If you want to run the same program both with and without a console, you should read Raymond Chen on the topic. 如果你想在有和没有控制台的情况下运行相同的程序,你应该阅读Raymond Chen的主题。

If you were able to use C++ then perhaps Boost.Program_options would help: 如果您能够使用C ++,那么Boost.Program_options可能会有所帮助:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM