Запазване на двойни кавички при подаване на низ за отваряне в C

Имам дилема, когато се опитвам да предам низ през popen в C, но го накарам да поддържа двойните кавички в низа. Низът гласи следното:

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

Трябва да изпълня тази команда, така че тя greps от регистрационен файл на отдалечена система и да върне броя за нея. Предавам различни аргументи през низа, така че той генерира потребителското име и паролата, както и низа за търсене и целта. Низът за търсене обаче съдържа множество думи, разделени с празни знаци, така че имам нужда от непокътнати двойни кавички, за да анализира правилно низа за търсене. Въпреки това, досега popen премахва двойните кавички от низа, така че търсенето да не завърши. Не съм сигурен за друг начин да направя отдалечената ssh команда или да запазя двойните кавички в израза. Някакви идеи?

Благодаря!

*РЕДАКТИРАНЕ: Ето пълния оператор sprintf, който използвам за генериране на низа, както и командата popen.

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

person Nick L.    schedule 03.12.2012    source източник
comment
Публикувайте пълното sprintf обаждане. А може би и обаждането popen.   -  person AnT    schedule 03.12.2012


Отговори (4)


popen разклонява дете, което изпълнява обвивка с 2 аргумента: -c и командния низ, който сте предали на отваряне. Така че всички символи, които предавате, които са значими за обвивката, трябва да бъдат екранирани, така че обвивката да направи правилното нещо с тях. Във вашия случай се нуждаете от обвивката, за да ЗАПАЗИТЕ " символите, така че отдалечената обвивка да ги получи, което е най-лесно да направите, като увиете ' кавички около тях:

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, ...
person Chris Dodd    schedule 03.12.2012
comment
Здравей, Крис, изпробвах второто предложение, което предложи тук, и работи чудесно. Регистрационните файлове, които препращам, обикновено са само типични изявления, като например грешка 100. За моите цели обаче това реши проблема ми и оценявам обяснението защо се случи на първо място. :) - person Nick L.; 04.12.2012

Проблемът не е в sprintf и не в popen. Проблемът е черупката, която извиква ssh. Това е черупката, която оголва цитатите ви.

Можете просто да отворите терминал и да го опитате ръчно. Ще видите това

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

не работи по предназначение, ако низът за търсене съдържа интервали. Вашата обвивка консумира кавичките, така че накрая 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);

Разбира се, това все още може да страда от други проблеми, споменати в отговора на Крис Дод.

person AnT    schedule 03.12.2012

След известно експериментиране това изглежда вероятно е това, от което се нуждаете:

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

Локалната обвивка не се нуждае от обратната наклонена черта преди двойните кавички; всъщност добавянето му прави грешка.

Код

#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);
}
person Jonathan Leffler    schedule 03.12.2012
comment
Простото удвояване или учетворяване на обратните наклонени черти НИКОГА няма да работи -- трябва да завършите с нечетен брой обратни наклонени черти преди ", за да получите " в низа. Както е написано, вашият код просто ще даде синтактична грешка поради това, че %s не е в низ... - person Chris Dodd; 03.12.2012

Както други обясниха тук, цитирането понякога става тромаво.

За да избегнете прекомерно цитиране, просто прекарайте командите в стандартния вход на ssh. Като в следния bash код:

ssh remote_host <<EOF
grep -c "search string" path
EOF
person Maxim Egorushkin    schedule 03.12.2012