我遇到了一个困境,我试图通过 C 中的 popen 传递一个字符串,但让它保留字符串中的双引号。该字符串如下所示:
ssh %s@%s grep -c \"%s\" %s%s
我需要运行此命令,以便它从远程系统上的日志中进行 grep 并返回其计数。我通过字符串传递不同的参数,因此它生成用户名和密码以及搜索字符串和目标。但是,搜索字符串包含由空格字符分隔的多个单词,因此我需要完整的双引号,以便它正确解析搜索字符串。然而,到目前为止,popen 已经从字符串中删除了双引号,因此搜索尚未完成。我不确定执行远程 ssh 命令的另一种方法,或者在语句中保留双引号。有什么想法吗?
谢谢!
*编辑:这是我用来生成字符串以及 popen 命令的完整 sprintf 语句。
sprintf(command, "ssh %s@%s grep -c \"%s\" %s%s", user, server, search, path, file);
fd = popen(command, "r");
popen
分叉一个子进程,该子进程使用 2 个参数执行 shell:-c
和您传递给 popen 的命令字符串。 因此,您传递的任何对 shell 有意义的字符都需要转义,以便 shell 对它们执行正确的操作。 在您的情况下,您需要 shell 保留 "
字符,以便远程 shell 能够获取它们,最简单的方法是在它们周围加上 '
引号:
sprintf(command, "ssh %s@%s grep -c '\"%s\"' %s%s", ...
但是,只有当您的搜索字符串不包含任何
'
或 "
字符时,这才有效 - 如果包含,您需要执行一些更复杂的转义。 您可以使用反斜杠转义符:
sprintf(command, "ssh %s@%s grep -c \\\"%s\\\" %s%s", ...
但是如果您的搜索字符串包含引号或多个连续空格或其他空格(例如制表符),则此操作将会失败。 要处理所有情况,您需要首先在搜索字符串中的所有相关字符之前插入反斜杠,加上
'
处理起来很痛苦:
// allocate 4*strlen(search)+1 space as escaped_search
for (p1 = search, p2 = escaped_search; *p1;) {
if (*p1 == '\'')
*p2++ = '\'';
if (strchr(" \t\r\n\"\\'{}()<>;&|`$", *p1))
*p2++ = '\\';
if (*p1 == '\'')
*p2++ = '\'';
*p2++ = *p1++;
}
*p2 = '\0';
sprintf(command, "ssh %s@%s grep -c '%s' %s%s", user, server, escaped_search, ...
问题不在于
sprintf
,也不在于popen
。问题在于调用 ssh
的 shell。它是剥去你的引号的外壳。
您只需打开终端并手动尝试即可。你会看到的
ssh user@server grep -c "search string" path
如果搜索字符串包含空格,将无法按预期工作。您的 shell 会消耗引号,因此最终
grep
会收到不带引号的命令行并错误地解释它。
如果你想让引号持续存在,你必须在 shell 中转义它们。您要执行的命令是
ssh user@server grep -c \"search string\" path
要使用
sprintf
形成这样的字符串,您还必须转义 "
和 \
字符(对于 C 编译器),这意味着 \"
会变成 \\\"
。最终的格式行如下
sprintf(command, "ssh %s@%s grep -c \\\"%s\\\" %s%s", user, server, search, path, file);
当然,这仍然可能受到克里斯·多德回答中提到的其他问题的影响。
经过一些实验,这似乎就是您所需要的:
snprintf(command, sizeof(command), "ssh %s@%s grep -c \\\"%s\\\" %s%s",
username, hostname, search_string, directory, file);
您需要多个反斜杠,因为涉及多个反斜杠解释器。
command
字符串中嵌入双引号。 命令字符串包含两次 \"
。这是一些正在运行的演示代码。 本地程序称为
pop
。 那里的所有内容都是硬连线的,因为我很懒(但我将远程主机名与我实际测试的主机名进行了比较)。 程序/u/jleffler/linux/x86_64/bin/al
列出了收到的参数,每行一个。 我发现它对于这种情况来说是一个非常有用的工具。 请注意,al
的参数经过精心设计,带有双空格,以显示参数何时被视为一个与多个。
$ ./pop
Command: <<ssh [email protected] /u/jleffler/linux/x86_64/bin/al \"x y z\" \"pp qq rr\">>
[email protected]'s password:
Response: <<x y z>>
Response: <<pp qq rr>>
$
#include <stdio.h>
#include <string.h>
int main(void)
{
char command[512];
char const *arg1 = "x y z";
char const *arg2 = "pp qq rr";
char const *cmd = "/u/jleffler/linux/x86_64/bin/al";
char const *hostname = "remote.example.com";
char const *username = "jleffler";
snprintf(command, sizeof(command), "ssh %s@%s %s \\\"%s\\\" \\\"%s\\\"",
username, hostname, cmd, arg1, arg2);
printf("Command: <<%s>>\n", command);
FILE *fp = popen(command, "r");
char line[512];
while (fgets(line, sizeof(line), fp) != 0)
{
line[strlen(line)-1] = '\0';
printf("Response: <<%s>>\n", line);
}
pclose(fp);
return(0);
}
$ ./pop1
Command: <<ssh [email protected] /u/jleffler/linux/x86_64/bin/al "x y z" "pp qq rr">>
[email protected]'s password:
Response: <<x>>
Response: <<y>>
Response: <<z>>
Response: <<pp>>
Response: <<qq>>
Response: <<rr>>
$
#include <stdio.h>
#include <string.h>
int main(void)
{
char command[512];
char const *arg1 = "x y z";
char const *arg2 = "pp qq rr";
char const *cmd = "/u/jleffler/linux/x86_64/bin/al";
char const *hostname = "remote.example.com";
char const *username = "jleffler";
snprintf(command, sizeof(command), "ssh %s@%s %s \"%s\" \"%s\"",
username, hostname, cmd, arg1, arg2);
printf("Command: <<%s>>\n", command);
FILE *fp = popen(command, "r");
char line[512];
while (fgets(line, sizeof(line), fp) != 0)
{
line[strlen(line)-1] = '\0';
printf("Response: <<%s>>\n", line);
}
pclose(fp);
return(0);
}
$ ./pop2
Command: <<al [email protected] /u/jleffler/linux/x86_64/bin/al \"x y z\" \"pp qq rr\">>
Response: <<[email protected]>>
Response: <</u/jleffler/linux/x86_64/bin/al>>
Response: <<"x>>
Response: <<y>>
Response: <<z">>
Response: <<"pp>>
Response: <<qq>>
Response: <<rr">>
$
本地 shell 不需要双引号前的反斜杠;事实上,添加它会导致错误。
#include <stdio.h>
#include <string.h>
int main(void)
{
char command[512];
char const *arg1 = "x y z";
char const *arg2 = "pp qq rr";
char const *cmd = "/u/jleffler/linux/x86_64/bin/al";
char const *hostname = "remote.example.com";
char const *username = "jleffler";
snprintf(command, sizeof(command), "al %s@%s %s \\\"%s\\\" \\\"%s\\\"",
username, hostname, cmd, arg1, arg2);
printf("Command: <<%s>>\n", command);
FILE *fp = popen(command, "r");
char line[512];
while (fgets(line, sizeof(line), fp) != 0)
{
line[strlen(line)-1] = '\0';
printf("Response: <<%s>>\n", line);
}
pclose(fp);
return(0);
}
$ ./pop3
Command: <<al [email protected] /u/jleffler/linux/x86_64/bin/al "x y z" "pp qq rr">>
Response: <<[email protected]>>
Response: <</u/jleffler/linux/x86_64/bin/al>>
Response: <<x y z>>
Response: <<pp qq rr>>
$
#include <stdio.h>
#include <string.h>
int main(void)
{
char command[512];
char const *arg1 = "x y z";
char const *arg2 = "pp qq rr";
char const *cmd = "/u/jleffler/linux/x86_64/bin/al";
char const *hostname = "remote.example.com";
char const *username = "jleffler";
snprintf(command, sizeof(command), "al %s@%s %s \"%s\" \"%s\"",
username, hostname, cmd, arg1, arg2);
printf("Command: <<%s>>\n", command);
FILE *fp = popen(command, "r");
char line[512];
while (fgets(line, sizeof(line), fp) != 0)
{
line[strlen(line)-1] = '\0';
printf("Response: <<%s>>\n", line);
}
pclose(fp);
return(0);
}
正如其他人在这里所解释的,引用有时会变得很麻烦。
为了避免过多引用,只需将命令通过管道传输到 ssh 的标准输入中。就像下面的 bash 代码一样:
ssh remote_host <<EOF
grep -c "search string" path
EOF