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

问题描述 投票:0回答:4

我遇到了一个困境,我试图通过 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");
c ssh grep popen printf
4个回答
3
投票

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

0
投票

问题不在于

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

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


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

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

$ ./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);
}

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

$ ./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);
}

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

$ ./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);
}

0
投票

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

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

ssh remote_host <<EOF
grep -c "search string" path
EOF
© www.soinside.com 2019 - 2024. All rights reserved.