2

我正处于两难境地,我试图通过 C 中的 popen 传递一个字符串,但让它保持字符串中的双引号。该字符串如下所示:

ssh %s@%s grep -c \"%s\" %s%s

我需要运行此命令,以便它从远程系统上的日志中 greps 并返回它的计数。我通过字符串传递不同的参数,因此它生成用户名和密码以及搜索字符串和目标。但是,搜索字符串包含由空格字符分隔的多个单词,因此我需要完整的双引号,以便正确解析搜索字符串。但是,到目前为止,popen 一直在从字符串中去除双引号,因此搜索没有完成。我不确定执行远程 ssh 命令的另一种方法,或者在语句中保留双引号。有任何想法吗?

谢谢!

*编辑:这是我用来生成字符串以及 popen 命令的完整 sprintf 语句。

sprintf(command, "ssh %s@%s grep -c \"%s\" %s%s", user, server, search, path, file);
fd = popen(command, "r");
4

4 回答 4

3

popen派生一个执行带有 2 个参数的 shell 的子进程:-c以及传递给 popen 的命令字符串。因此,您传递的任何对 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, ...
于 2012-12-03T19:04:34.117 回答
0

正如其他人在这里解释的那样,引用有时会很麻烦。

为了避免过度引用,只需将命令通过管道传输到 ssh 的标准输入中。就像下面的 bash 代码一样:

ssh remote_host <<EOF
grep -c "search string" path
EOF
于 2012-12-03T19:57:11.633 回答
0

经过一些实验,这似乎是您所需要的:

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: <<ssh jleffler@remote.example.com /u/jleffler/linux/x86_64/bin/al \"x  y  z\" \"pp qq rr\">>
jleffler@remote.example.com'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);
}

变体 1——远程执行,错误的结果

$ ./pop1
Command: <<ssh jleffler@remote.example.com /u/jleffler/linux/x86_64/bin/al "x  y  z" "pp qq rr">>
jleffler@remote.example.com'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);
}

变体 2 — 本地执行,(不同)错误结果

$ ./pop2
Command: <<al jleffler@remote.example.com /u/jleffler/linux/x86_64/bin/al \"x  y  z\" \"pp qq rr\">>
Response: <<jleffler@remote.example.com>>
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);
}

变体 3 — 本地执行,正确的结果

$ ./pop3  
Command: <<al jleffler@remote.example.com /u/jleffler/linux/x86_64/bin/al "x  y  z" "pp qq rr">>
Response: <<jleffler@remote.example.com>>
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);
}
于 2012-12-03T19:06:17.370 回答
0

问题不在于也不sprintf在于popen。问题是调用ssh. 它是剥去你的引号的外壳。

您可以简单地打开一个终端并手动尝试。你会看到

ssh user@server grep -c "search string" path

如果搜索字符串包含空格,则无法按预期工作。您的 shell 使用引号,因此最终grep会收到不带引号的命令行并错误地解释它。

如果您希望引号持续存在,则必须为外壳转义它们。您要执行的命令是

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);

当然,这仍然会受到克里斯多德回答中提到的其他问题的影响。

于 2012-12-03T19:11:57.253 回答