У меня возникла дилемма, когда я пытаюсь передать строку через popen в C, но она поддерживает двойные кавычки в строке. Строка читается следующим образом:
ssh %s@%s grep -c \"%s\" %s%s
Мне нужно запустить эту команду, чтобы она загоралась из журнала в удаленной системе и возвращает счетчик для него. Я передаю разные аргументы через строку, поэтому он генерирует имя пользователя и пароль, а также строку поиска и цель. Тем не менее, строка поиска содержит несколько слов, разделенных пробельными символами, поэтому мне нужны двойные кавычки без изменений, чтобы они правильно разбирали строку поиска. Тем не менее, до сих пор popen удаляет двойные кавычки из строки, поэтому поиск не завершается. Я не уверен в другом способе выполнения удаленной команды ssh или сохранения двойных кавычек в инструкции. Есть идеи?
Спасибо!
* EDIT: Вот полный оператор sprintf, который я использую для создания строки, а также команды popen.
sprintf(command, "ssh %s@%s grep -c \"%s\" %s%s", user, server, search, path, file); fd = popen(command, "r");
popen
создает дочерний элемент, который запускает оболочку с двумя аргументами: -c
и командной строки, которую вы передали в popen. Таким образом, любые символы, которые вы передаете, которые имеют смысл для оболочки, должны быть экранированы, поэтому shell делает с ними что-то нужное. В вашем случае вам понадобится shell, чтобы ХРАНИТЬ символы, чтобы удаленная shell их получала, что проще всего сделать, обернув вокруг себя кавычки:
sprintf(command, "ssh %s@%s grep -c '\"%s\"' %s%s", ...
Однако это будет работать только в том случае, если ваша строка поиска не содержит никаких символов '
или "
Если это так, вам нужно выполнить более сложное экранирование. Вместо этого вы можете использовать обратную косую черту:
sprintf(command, "ssh %s@%s grep -c \\\"%s\\\" %s%s", ...
но это не удастся, если ваша строка поиска содержит кавычки или несколько последовательных пробелов или других пробелов, таких как вкладки int it. Чтобы справиться со всеми случаями, вам нужно сначала вставить обратную косую черту перед всеми соответствующими символами в строке поиска, плюс '
это боль, с которой приходится иметь дело:
// 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, ...
Проблема не в sprintf
а не в popen
. Проблема заключается в оболочке, которая вызывает ssh
. Это shell, которая разделяет ваши кавычки.
Вы можете просто открыть терминал и попробовать его вручную. Вы увидите, что
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);
Конечно, это может по-прежнему страдать от других проблем, упомянутых в ответе Криса Додда.
После некоторых экспериментов это, вероятно, будет тем, что вам нужно:
snprintf(command, sizeof(command), "ssh %s@%s grep -c \\\"%s\\\" %s%s", username, hostname, search_string, directory, file);
Вам нужно несколько обратных косых черт, потому что задействованы несколько интерпретаторов обратных косых черт.
command
строку. Затем строка команды содержит \"
дважды. Вот какой демо-код работает. Локальная программа называется pop
. Все в нем жестко связано, потому что я ленив (но я искал удаленное имя хоста по сравнению с тем, с которым я действительно тестировался). Программа /u/jleffler/linux/x86_64/bin/al
перечисляет свои аргументы как полученные, по одному в каждой строке. Я считаю это очень полезным инструментом для подобных ситуаций. Обратите внимание, что аргументы в al
тщательно обрабатываются с помощью двойных пробелов, чтобы показать, когда аргументы рассматриваются как один против многих.
$ ./pop Command: <> jleffler@remote.example.com's password: Response: <> Response: <> $
#include #include int main(void) { char command[512]; char const *arg1 = "xyz"; 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); }
$ ./pop1 Command: <> jleffler@remote.example.com's password: Response: <> Response: <> Response: <> Response: <> Response: <> Response: <> $
#include #include int main(void) { char command[512]; char const *arg1 = "xyz"; 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); }
$ ./pop2 Command: <> Response: <> Response: <> Response: <<"x>> Response: <> Response: <> Response: <<"pp>> Response: <> Response: <> $
Локальной оболочке не требуется обратная косая черта перед двойной кавычкой; на самом деле, добавив, что это неправильно.
#include #include int main(void) { char command[512]; char const *arg1 = "xyz"; 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); }
$ ./pop3 Command: <> Response: <> Response: <> Response: <> Response: <> $
#include #include int main(void) { char command[512]; char const *arg1 = "xyz"; 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); }
Как объяснили другие, цитирование иногда становится громоздким.
Чтобы избежать чрезмерного цитирования, просто соедините команды с стандартным входом ssh. Как и в следующем коде bash:
ssh remote_host <