在C中将字符串传递给popen时保留双引号

我正处于一个两难的境地,我试图在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分叉一个子popen该子popen有2个参数: -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。 它是剥去你的报价的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); 

您需要多个反斜杠,因为涉及多个反斜杠解释器。

  1. C编译器:它将每个三个序列中的前两个反斜杠视为一个反斜杠。 第三个反斜杠转义双引号,在command字符串中嵌入双引号。 然后命令字符串包含两次。
  2. 还有另一个处理反斜杠的过程,确定它的位置有点棘手,但需要它们来获得正确的结果,如实验所示。

工作代码 – 远程执行,正确的结果

这是一些工作的演示代码。 本地程序称为pop 。 那里的所有东西都是硬连线的,因为我很懒(但我把远程主机名与我实际测试的那个相比)。 程序/u/jleffler/linux/x86_64/bin/al列出了收到的参数,每行一个。 对于这样的情况,我发现它是一个非常有用的工具。 请注意, al的参数是用双空格精心设计的,以显示何时将参数视为一对多。

 $ ./pop Command: <> jleffler@remote.example.com's password: Response: <> Response: <> $ 

 #include  #include  int main(void) { char command[512]; char const *arg1 = "xyz"; 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); } 

变式1 – 远程执行,错误结果

 $ ./pop1 Command: <> jleffler@remote.example.com's password: Response: <> Response: <> Response: <> Response: <> Response: <> Response: <> $ 

 #include  #include  int main(void) { char command[512]; char const *arg1 = "xyz"; 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); } 

变式2 – 本地执行,(不同)错误结果

 $ ./pop2 Command: <> Response: <> Response: <> Response: <<"x>> Response: <> Response: <> Response: <<"pp>> Response: <> Response: <> $ 

本地shell在双引号之前不需要反斜杠; 事实上,添加它是错误的。

 #include  #include  int main(void) { char command[512]; char const *arg1 = "xyz"; 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); } 

变式3 – 本地执行,正确结果

 $ ./pop3 Command: <> Response: <> Response: <> Response: <> Response: <> $ 

 #include  #include  int main(void) { char command[512]; char const *arg1 = "xyz"; 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 <