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

时间:2012-12-03 18:52:17

标签: c ssh grep popen printf

我正处于一个两难境地,我正试图在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");

4 个答案:

答案 0 :(得分:2)

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, ...

答案 1 :(得分:0)

经过一些实验,这似乎可能就是你所需要的:

snprintf(command, sizeof(command), "ssh %s@%s grep -c \\\"%s\\\" %s%s",
         username, hostname, search_string, directory, file);

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

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

    以下是一些演示代码。本地程序称为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);
    }
    

答案 2 :(得分:0)

问题不在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);

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

答案 3 :(得分:0)

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

为了避免过多引用,只需将命令传递到ssh的标准输入中。如下面的bash代码:

ssh remote_host <<EOF
grep -c "search string" path
EOF