2

Cのpopenに文字列を渡そうとしているというジレンマがありますが、文字列内の二重引用符を維持する必要があります。文字列は次のようになります。

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

このコマンドを実行して、リモートシステムのログから取得し、そのカウントを返すようにする必要があります。文字列を介してさまざまな引数を渡すので、ユーザー名とパスワード、および検索文字列とターゲットが生成されます。ただし、検索文字列には空白文字で区切られた複数の単語が含まれているため、検索文字列を正しく解析するには、二重引用符をそのまま使用する必要があります。ただし、これまでのところ、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

popen2つの引数でシェルを実行する子をフォークします:-cそしてあなたがpopenに渡したコマンド文字列。したがって、シェルにとって意味のある、渡す文字はすべてエスケープする必要があります。これにより、シェルはそれらの文字を正しく処理します。あなたの場合、リモートシェルが文字を取得できるように文字を保持するシェルが必要です。これは、文字を引用符で囲む"のが最も簡単です。'

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

いくつかの実験の後、これはあなたが必要とするものである可能性が高いようです:

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

バックスラッシュの複数のインタプリタが関係しているため、複数のバックスラッシュが必要です。

  1. Cコンパイラ:3つの各シーケンスの最初の2つの円記号を1つの円記号として扱います。3番目の円記号は二重引用符をエスケープし、文字列に二重引用符を埋め込みcommandます。その場合、コマンド文字列には\"2回含まれます。
  2. バックスラッシュを処理する別のプロセスがあり、それがどこにあるかを判断するのは少し難しいですが、実験で示されているように、正しい結果を得るにはバックスラッシュが必要です。

作業コード—リモート実行、正しい結果

これが動作するデモコードです。ローカルプログラムはと呼ばれpopます。私は怠惰なので、そこにあるものはすべてハードワイヤードです(ただし、実際にテストしたものと比較して、リモートホスト名を変更しました)。プログラム/u/jleffler/linux/x86_64/bin/alは、引数を受信したとおりに1行に1つずつリストします。このような状況で非常に便利なツールだと思います。alの引数は、引数が1対多として扱われる場合を示すために、二重のスペースで慎重に作成されていることに注意してください。

$ ./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">>
$

ローカルシェルは、二重引用符の前に円記号を必要としません。実際、それを追加すると間違ってしまいます。

コード

#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

検索文字列にスペースが含まれている場合、意図したとおりに機能しません。シェルは引用符を消費するため、最終的にgrepは引用符なしでコマンドラインを受け取り、誤って解釈します。

引用符を保持したい場合は、シェルのために引用符をエスケープする必要があります。実行したいコマンドは

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

を使用してこのような文字列を作成するには、および文字(Cコンパイラの場合)sprintfをエスケープする必要があります。これは、を意味します。最終的なフォーマット行は次のとおりです"\\"\\\"

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

もちろん、これはまだクリスドッドの答えで言及された他の問題に苦しむ可能性があります。

于 2012-12-03T19:11:57.253 に答える
0

他の人がここで説明しているように、引用は時々面倒になります。

過度の引用を避けるために、コマンドをsshの標準入力にパイプするだけです。次のbashコードのように:

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