程序是否可以在POSIX中获取命令行参数之间的空格数?


23

假设我用下面的代码写了一个程序:

int main(int argc, char** argv)

现在,通过检查的内容,知道将哪些命令行参数传递给它argv

程序可以检测参数之间有多少空格吗?就像我在bash中键入以下内容一样:

ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog       aaa      bbb

环境是现代Linux(例如Ubuntu 16.04),但是我想答案应该适用于任何POSIX兼容系统。


22
出于好奇,您的程序为什么需要知道这一点?
nxnev

2
@nxnev我曾经写过一些Windows程序,我知道那是可能的,所以我想知道Linux(或Unix)中是否有类似的东西。
iBug

9
我隐约记得在CP / M中,程序必须解析自己的命令行-这意味着每个C运行时都必须实现Shell解析器。他们所做的略有不同。
Toby Speight,

3
@iBug有,但是在调用命令时需要引用参数。这就是在POSIX(和类似的)shell上完成的过程。
康拉德·鲁道夫

3
@iBug,... Windows具有与Toby在上文CP / M中提到的设计相同的设计。UNIX没有做到这一点-从所谓过程的角度来看,参与运行它没有命令行。
查尔斯·达菲

Answers:


39

谈论“参数之间的空间”没有意义。那是壳的概念。

Shell的工作是获取整行输入并将它们形成为参数数组以开始命令。这可能涉及解析带引号的字符串,扩展变量,文件通配符和代字号表达式等。该命令以标准exec系统调用开始,该系统调用接受字符串向量。

存在其他方法来创建字符串向量。许多程序使用预定的命令调用分叉并执行它们自己的子流程-在这种情况下,永远不会出现“命令行”之类的东西。类似地,当用户将文件图标拖放到命令小部件上时,图形(桌面)shell可能会启动一个过程-再次,没有文本行在参数之间添加字符。

就所调用的命令而言,shell或其他父/前体进程中发生的事情是私有且隐藏的-我们仅看到标准C指定main()可以接受的字符串数组。


好的答案-对于Unix新手要指出这一点很重要,他们经常认为,如果新手运行,tar cf texts.tar *.txt那么tar程序将获得两个参数,并且必须自己扩展第二个(*.txt)。在开始编写自己的处理参数的脚本/程序之前,许多人都没有意识到它的真正作用。
劳伦斯·伦肖

58

一般来说,没有。命令行解析由外壳完成,该外壳不会使未解析的行可用于调用的程序。实际上,您的程序可能是从另一个创建argv的程序执行的,而不是通过解析字符串而是通过以编程方式构造参数数组来创建argv的。


9
您可能要提一下execve(2)
iBug

3
您是对的,作为a脚的借口,我可以说我当前正在使用电话并查找手册页有点乏味:-)
Hans-Martin Mosner

1
是POSIX的相关部分。
史蒂芬·基特

1
@ Hans-MartinMosner:Termux ...?;-)
DevSolar

9
“一般”是为了防止在可能的情况下引用特殊的复杂情况,例如,suid根进程可能能够检查调用shell的内存并找到未解析的命令行字符串。
汉斯·马丁·莫斯纳

16

不可以,除非空格是参数的一部分,否则这是不可能的。

该命令从数组(以一种形式或另一种形式,取决于编程语言)访问数组中的各个参数,并且实际的命令行可能会保存到历史记录文件(如果在具有历史记录文件的shell中以交互式提示键入)。绝不以任何形式传递给命令。

Unix上的所有命令最终都由exec()一系列功能之一执行。它们采用命令名称和参数列表或数组。它们都不采用在shell提示符下键入的命令行。该system()函数可以,但是其字符串参数稍后由执行execve(),该参数再次使用参数数组而不是命令行字符串。


2
@LightnessRacesinOrbit我把它放在这里,以防万一关于“参数之间的空间”有些混乱。在hello和之间用引号引起world来的空格实际上是两个参数之间的空格。
库沙兰丹

5
@Kusalananda -哦,不......把空格之间的报价hello,并world字面上提供的三个参数的第二位。
杰里米

@Jeremy正如我说的那样,以防万一,“争论之间”的含义是什么。是的,如果可以的话,请作为另外两个参数之间的第二个参数。
库萨兰达

您的示例很好,很有启发性。
杰里米(Jeremy)

1
好了,伙计们,这些例子显然是造成混乱和误解的根源。我删除了它们,因为它们没有增加答案的价值。
Kusalananda

9

通常,不可能像其他几个答案一样解释。

但是,Unix shell普通程序(它们正在解释命令行并对其进行遍历,即在执行&为此之前扩展命令)。请参阅有关shell操作的说明您可以编写自己的外壳程序(或可以修补一些现有的免费软件外壳程序,例如GNU bash)并将其用作外壳程序(甚至您的登录外壳程序,请参阅passwd(5)shells(5))。forkexecvebash

例如,你可能有自己的shell程序把完整的命令行中的一些环境变量(想象MY_COMMAND_LINE例如) -或使用其他任何形式的进程间通信的命令行外壳,从儿童步骤-传输。

我不明白您为什么要这样做,但是您可能会以这种方式对行为进行编码(但我建议不要这样做)。

顺便说一句,程序可以由不是外壳程序(但先执行fork(2)然后执行execve(2)或只是execve在当前进程中启动程序)的某个程序启动。在这种情况下,根本就没有命令行,并且您的程序可以在没有命令的情况下启动...

请注意,您可能具有未安装任何外壳的某些(专用)Linux系统。这很奇怪和不同寻常,但是可能。然后,您需要编写一个专门的init程序,根据需要启动其他程序-无需使用任何Shell,而是通过forkexecve系统调用。

另请阅读操作系统:三个简单的部分,不要忘记,execve它实际上总是一个系统调用(在Linux上,它们在syscalls(2)中列出,另请参阅intro(2)),它们会重新初始化虚拟地址空间(以及其他一些初始化)事情)的过程


这是最好的答案。我假设(没有查过)argv[0] 程序名称和参数的其余元素是POSIX规范,不能更改。argv[-1]我假设运行时环境可以为命令行指定...
彼得-恢复莫妮卡

不,不能。请仔细阅读execve文档。您不能使用argv[-1],使用它是未定义的行为。
Basile Starynkevitch

是的,很好(也暗示我们有系统调用)-这个想法有些人为。运行时的所有三个组件(shell,stdlib和OS)都需要进行协作。Shell需要execvepluscmd使用额外的参数(或argv约定)调用特殊的非POSIX 函数,syscall为main构造一个参数矢量,该矢量在指向程序名称的指针之前包含指向命令行的指针,然后传递地址argv调用程序的名称时指向程序名称的指针main...
彼得-恢复莫妮卡

无需重新编写外壳,只需使用引号即可。该功能可以从bourn shell获得sh。所以不是新的。
ctrl-alt-delor

使用引号需要更改命令行。和OP不想这样
巴西莱Starynkevitch

3

您总是可以告诉您的Shell告诉应用程序什么Shell代码导致它们执行。例如,使用zsh,通过$SHELL_CODE使用preexec()挂钩将信息传递到环境变量中(printenv作为示例,您将getenv("SHELL_CODE")在程序中使用):

$ preexec() export SHELL_CODE=$1
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv  SHELL_CODE
printenv  CODE
$ $(echo printenv SHELL_CODE)
$(echo printenv SHELL_CODE)
$ for i in SHELL_CODE; do printenv "$i"; done
for i in SHELL_CODE; do printenv "$i"; done
$ printenv SHELL_CODE; : other command
printenv SHELL_CODE; : other command
$ f() printenv SHELL_CODE
$ f
f

所有这些都将执行printenv为:

execve("/usr/bin/printenv", ["printenv", "SHELL_CODE"], 
       ["PATH=...", ..., "SHELL_CODE=..."]);

允许printenv检索导致printenv使用这些参数执行的zsh代码。我不清楚您要如何处理这些信息。

使用时bash,最接近zshs 的功能preexec()$BASH_COMMANDDEBUG陷阱中使用它,但是请注意,该功能会进行bash某种程度的重写(特别是重构一些用作定界符的空白),并将其应用于每个(很好的)命令运行,而不是提示符下输入的整个命令行(另请参阅functrace选项)。

$ trap 'export SHELL_CODE="$BASH_COMMAND"' DEBUG
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv $(echo 'SHELL_CODE')
printenv $(echo 'SHELL_CODE')
$ for i in SHELL_CODE; do printenv "$i"; done; : other command
printenv "$i"
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printf '%s\n' "$(printenv "SHELL_CODE")"
$ set -o functrace
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printenv "SHELL_CODE"
$ print${-+env  }    $(echo     'SHELL_CODE')
print${-+env  } $(echo     'SHELL_CODE')

了解如何将一些在shell语言语法中作为分隔符的空格压缩为1,以及如何不将完整的命令行不总是传递给该命令。因此可能对您没有用。

请注意,我不建议您这样做,因为您可能会将敏感信息泄漏给每个命令,如下所示:

echo very_secret | wc -c | untrustedcmd

会泄漏这个秘密到两个wcuntrustedcmd

当然,您可以针对非Shell的其他语言执行此类操作。例如,在C语言中,您可以使用一些宏,这些宏将执行命令的C代码导出到环境中:

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define WRAP(x) (setenv("C_CODE", #x, 1), x)

int main(int argc, char *argv[])
{
  if (!fork()) WRAP(execlp("printenv", "printenv", "C_CODE", NULL));
  wait(NULL);
  if (!fork()) WRAP(0 + execlp("printenv",   "printenv", "C_CODE", NULL));
  wait(NULL);
  if (argc > 1 && !fork()) WRAP(execvp(argv[1], &argv[1]));
  wait(NULL);
  return 0;
}

例:

$ ./a.out printenv C_CODE
execlp("printenv", "printenv", "C_CODE", NULL)
0 + execlp("printenv", "printenv", "C_CODE", NULL)
execvp(argv[1], &argv[1])

了解C预处理程序如何压缩某些空间,就像在bash情况下一样。在大多数(如果不是全部)语言中,定界符中使用的空间量没有什么区别,因此,编译器/解释器在这里使用它们会产生一些自由也就不足为奇了。


当我对此进行测试时,它BASH_COMMAND不包含用于分隔参数的原始空格,因此这不适用于OP的文字要求。这个答案是否包含针对该特定用例的任何演示?
查尔斯·达菲

@CharlesDuffy,我只想在bash中指示zsh的preexec()的最接近等价项(因为这是OP所指的外壳),并指出它不能用于该特定用例,但我同意它不能用于该特定用例。非常清楚。参见编辑。这个答案旨在更通用地说明如何将导致执行的源代码(此处是zsh / bash / C)传递给正在执行的命令(不是有用的东西,但是我希望这样做的时候,尤其是与例子,我证明了它不是非常有用)
斯特凡Chazelas

0

我将添加其他答案中缺少的内容。

没有

查看其他答案

也许有点

在程序中无法执行任何操作,但是在运行程序时外壳中可以执行某些操作。

您需要使用引号。所以代替

./myprog      aaa      bbb

你需要做其中之一

./myprog "     aaa      bbb"
./myprog '     aaa      bbb'

这会将带有所有空格的单个参数传递给程序。两者之间是有区别的,第二个是文字的,与显示的字符串完全相同(除非'必须键入\')。第一个将解释一些字符,但分为多个参数。有关更多信息,请参见shell引用。因此,无需重写外壳,外壳设计人员已经想到了这一点。但是,因为它现在是一个参数,所以您将不得不在程序中进行更多的传递。

选项2

通过标准输入传递数据。这是将大量数据放入命令的正常方法。例如

./myprog << EOF
    aaa      bbb
EOF

要么

./myprog
Tell me what you want to tell me:
aaaa bbb
ctrl-d

(斜体是程序的输出)


从技术上讲,shell代码:(./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"通常在子进程中)执行存储在其中的文件,./myprog并向其传递两个参数:./myprog␣␣␣␣␣aaa␣␣␣␣␣␣bbbargv[0]argc[1]argc为2),和在OP中一样,分隔这两个参数的空间不会以任何方式传递到myprog
斯特凡Chazelas

但是,你正在改变命令,OP不想改变它
巴西莱Starynkevitch

@BasileStarynkevitch在您发表评论后,我再次阅读了问题。您正在做一个假设。OP在任何地方都没有说过他们不想更改程序的运行方式。也许这是真的,但他们对此无话可说。因此,这个答案可能就是他们所需要的。
ctrl-alt-delor

OP 明确询问参数之间的间距,而不是询问包含空格的单个参数
Basile Starynkevitch,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.