传递包含“!!!!”的字符串时 argv 的奇怪行为

Posted

技术标签:

【中文标题】传递包含“!!!!”的字符串时 argv 的奇怪行为【英文标题】:Strange behavior of argv when passing string containing "!!!!" 【发布时间】:2018-07-19 02:43:45 【问题描述】:

我编写了一个小程序,它从*argv[] 获取一些输入参数并打印出来。在几乎所有用例中,我的代码都运行良好。只有当我在要作为参数传递的字符串末尾使用多个感叹号时才会出现问题...

这行得通:

./program -m "Hello, world!"

这不起作用:

./program -m "Hello, world!!!!"

^^ 如果我这样做,程序输出要么是该字符串的两倍,要么是我在 ./program 之前输入的命令。

但是,我绝对不明白:以下,奇怪的是,确实有效:

./program -m 'Hello, world!!!!'

^^ 输出正好是……

Hello, world!!!!

...随心所欲。

所以,我的问题是:

为什么在字符串中使用多个感叹号时会出现这种奇怪的行为? 据我所知,在 C 语言中,"" 用于字符串,'' 用于单个字符。那么为什么我在使用'' 时会得到想要的结果,但在使用"" 时却没有(在我的理解中)? 我的代码是否有错误,或者我需要更改什么才能输入任何字符串(无论是否使用、使用什么以及使用多少标点符号)并准确打印该字符串?

我的代码的相关部分:

// this is a simplified example that, in essence, does the same 
// as my (significantly longer) code
int main(int argc, char* argv[]) 
    char *msg = (char *)calloc(1024, sizeof(char));

    printf("%s", strcat(msg, argv[2])); // argv[1] is "-m"

    free(msg);

我已经尝试先将argv[2] 的内容复制到char* 缓冲区中,然后将'\0' 附加到其中,这并没有改变任何内容。

【问题讨论】:

为什么是printf("%s", strcat(msg, argv[2])) 而不是printf("%s", argv[2]))?? 空格,你需要转义它。另外,calloc() 没有多大意义。只是char msg[1024] 非常好。当您使用单个 qoutes 时,字符串按原样传递。这与argv 或c 编程语言无关,而是与shell 相关 @Michael Walz:因为我在 msg 中创建了一个更长的字符串。将 argv 的内容附加到它只是我完整代码中许多步骤中的第一步。很抱歉没有早点澄清。 感叹号是你的shell的一个特殊字符(可能是bash)。如果它没有放在单引号中,shell 会解释 !! 并将其替换为其他内容(历史上的前一个命令)。您的程序可以正常运行,它会在命令行中打印从 shell 接收到的内容。 @ci7i2en4: 1) 语法错误是在扩展过程中创建的 bash 语法错误,在这种情况下您的程序甚至没有启动,2) 您的程序应该负责验证输入值,但您不能“撤消” bash 完成的扩展。您应该做的事情是:检查argc 是否具有有效长度并检查参数是否有意义,然后可能会显示带有使用信息的错误消息。 【参考方案1】:

shell 在双引号字符串中进行扩展。如果您阅读 the Bash manual page(假设您使用 Bash,这是大多数 Linux 发行版的默认设置),那么如果您查看 the History Expansion section,您会看到 !! 的意思是

参考上一条命令。

所以双引号字符串中的!!!! 将扩展为上一个命令,两次。

这种扩展不适用于单引号字符串。

所以问题不在您的程序中,而是由于环境(shell)调用您的程序。

【讨论】:

【参考方案2】:

这与您的代码无关,而是与启动它的外壳有关。

在大多数 shell 中,!! 是最后运行的命令的简写。当您使用双引号时,shell 允许在字符串中使用history expansion(以及变量替换等),因此当您将!! 放在双引号字符串中时,它会替换最后运行的命令。

这对您的程序意味着,所有这些都发生在您的程序执行之前,因此程序除了检查传入的字符串是否有效外,无能为力。

相比之下,当您使用单引号时,shell 不会 进行任何替换,并且字符串会未经修改地传递给程序。

所以你需要使用单引号来传递这个字符串。如果他们不希望发生任何替换,您的用户将需要知道这一点。另一种方法是创建一个包装外壳脚本,提示用户输入要传入的字符串,然后该脚本随后会使用适当的参数调用您的程序。

【讨论】:

我明白了,谢谢!这给我留下了另一个问题:有没有办法确保我的程序的用户仍然可以使用“某事!!!!”作为输入参数而没有遇到这种行为? (不管他们使用什么外壳) @ci7i2en4 这取决于您的用户。也许他们希望这种扩张发生,也许不。 @ci7i2en4,在 Bash 中,set +o histexpandset +H 禁用历史扩展。其他 shell 可能有其他设置。 我会评论说,任何改变 shell 预期行为的解决方案都可能是一个坏主意。用户应该知道如何在他们使用的任何 shell 中发送输入。 ^^ 此外,这只会改变我 bash 的行为。如果其他人在他们的 shell 中运行我的程序,他们仍然会经历历史扩展......【参考方案3】:

除了提供的答案之外,您还应该记住 echo 是您的 shell 朋友。如果您在命令前加上“echo”,您将看到 shell 实际发送到您的脚本。

echo ./program -m "Hello, world!!!!"

这会让您感到有些陌生,并可能有助于引导您朝着正确的方向前进。

【讨论】:

echo 实际上是一个非常糟糕的工具选择——echo "hello world"echo "hello" "world" 毕竟具有完全相同的输出,尽管它们的命令非常不同。 请考虑:print_args() printf '%q ' "$@"; printf '\n'; -- 此后,print_args ./program -m "Hello, world!!!!" 将发出参数,即使在 echo 出错的情况下,它们的解释也会明确。 这只是查尔斯的意见。我很欣赏他在使用 echo 作为工具方面表现出的不足,特别是他提供了替代方案。对我来说,依赖一个函数存在于我访问(或复制/粘贴)的每个 Linux 环境中并记住该函数名称是一个糟糕的选择。读者现在有两个糟糕的选择。一个在答案中,另一个在评论中。 re: "opinion" -- 见the POSIX specification for echo,特别是应用程序部分,明确建议使用printf,除非传递的数据被限制在一个已知安全的子集中。 printf 保证在任何地方都可用(尽管如果以可移植性为目标,则可以使用不同的格式字符串,例如' <%s>\n');除非有人选择,否则不需要使用函数来包装它。也就是说——我并不特别支持我建议的print_args 函数是一个不错的选择。我绝对支持echo 是一个坏人,printf 是正确的替代品,并且有规范性文档支持我。

以上是关于传递包含“!!!!”的字符串时 argv 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

奇怪的字符串结果

python sys模块

main中的argv和argc

$_SERVER[]数组解析

如何在 Visual Studio 2015 调试中将“%”符号作为命令行参数 (argv) 传递?

sys.argv[]用法-转载