我正在尝试使用wordexp
函数在某些字符串上进行类似shell的扩展。 wordexp
删除单引号和双引号,但是我想保留它们。我最初的做法是将输入字符串中的所有引号对都换用另一对引号,这次是转义的引号,wordexp
应该保持不变(反之亦然)。不幸
对于更复杂的输入,这将失败。
例如,对于'""TEST""'
,我想以\'\"\"TEST\"\"\'
结尾,我编写了以下代码段来演示使用方法时实际发生的情况:
#include <stdio.h>
#include <wordexp.h>
static void expansion_demo(char const *str)
{
printf("Before expansion: %s\n", str);
wordexp_t exp;
wordexp(str, &exp, 0);
printf("After expansion: %s\n", exp.we_wordv[0]);
wordfree(&exp);
}
int main(void)
{
char const *str1 = "\\''\\\"\"\"\\\"TEST1\\\"\"\"\\\"'\\'";
expansion_demo(str1);
char const *str2 = "'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''";
expansion_demo(str2);
return 0;
}
结果是:
Before expansion: \''\"""\"TEST1\"""\"'\'
After expansion: '\"""\"TEST1\"""\"'
Before expansion: '\'"\"\""TEST2"\"\""\''
Segmentation fault (core dumped)
此操作失败,因为双引号嵌套在单引号内 在这种情况下,天真地将每对引号与转义引号引起来是行不通的(尽管我不确定为什么会发生段错误)。
我还考虑过暂时将引号与其他ascii字符交换,但是没有任何内容不能成为某些有效的shell命令的一部分。
是否有一种方法可以使它适应我的要求?还是一些更简单的方法?
答案 0 :(得分:1)
在您的代码中,第二个测试字符串:
char const *str2 = "'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''";
产生语法错误。在这样的字符串上,应对C或shell转义规则显得有些可怕,但是您可以分析出,在字符串的末尾有不匹配的单引号。将C字符串文字转换为字符串会产生:
'\'"\"\""TEST2"\"\""\''
分析时,关键字符用脱字号标记:
'\'"\"\""TEST2"\"\""\''
^^^^^ ^ ^^ ^^ ^ ^^ ^
12345 6 78 91 1 11 1
0 1 23 4
TEST2
是引号(字符串的一部分)之外的纯文本因为最后的单引号字符串没有结尾,所以存在语法错误,并且wordexp()
的返回值是WRDE_SYNTAX
,这表明。而且您会遇到分段错误,因为exp
成员中的exp.we_wordv
结构已设置为空指针。
此代码的较安全版本说明了这一点:
/* SO 5246-1162 */
#include <stdio.h>
#include <wordexp.h>
static const char *worderror(int errnum)
{
switch (errnum)
{
case WRDE_BADCHAR:
return "One of the unquoted characters - <newline>, '|', '&', ';', '<', '>', '(', ')', '{', '}' - appears in an inappropriate context";
case WRDE_BADVAL:
return "Reference to undefined shell variable when WRDE_UNDEF was set in flags to wordexp()";
case WRDE_CMDSUB:
return "Command substitution requested when WRDE_NOCMD was set in flags to wordexp()";
case WRDE_NOSPACE:
return "Attempt to allocate memory in wordexp() failed";
case WRDE_SYNTAX:
return "Shell syntax error, such as unbalanced parentheses or unterminated string";
default:
return "Unknown error from wordexp() function";
}
}
static void expansion_demo(char const *str)
{
printf("Before expansion: [%s]\n", str);
wordexp_t exp;
int rc;
if ((rc = wordexp(str, &exp, 0)) == 0)
{
for (size_t i = 0; i < exp.we_wordc; i++)
printf("After expansion %zu: [%s]\n", i, exp.we_wordv[i]);
wordfree(&exp);
}
else
printf("Expansion failed (%d: %s)\n", rc, worderror(rc));
}
int main(void)
{
char const *str1 = "\\''\\\"\"\"\\\"TEST1\\\"\"\"\\\"'\\'";
expansion_demo(str1);
char const *str2 = "'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''";
expansion_demo(str2);
return 0;
}
输出为:
Before expansion: [\''\"""\"TEST1\"""\"'\']
After expansion 0: ['\"""\"TEST1\"""\"']
Before expansion: ['\'"\"\""TEST2"\"\""\'']
Expansion failed (6: Shell syntax error, such as unbalanced parentheses or unterminated string)
wordexp()
的作用 wordexp()
函数旨在(或多或少)执行与命令行将字符串作为命令行一部分的shell相同的扩展。这是一个可以说明这一点的简单程序。这是对Running 'wc' using execvp()
recognizes /home/usr/foo.txt
but not ~/foo.txt
(源文件wexp79.c
)答案的改编。
#include "stderr.h"
#include <stdio.h>
#include <stdlib.h>
#include <wordexp.h>
static const char *worderror(int errnum)
{
switch (errnum)
{
case WRDE_BADCHAR:
return "One of the unquoted characters - <newline>, '|', '&', ';', '<', '>', '(', ')', '{', '}' - appears in an inappropriate context";
case WRDE_BADVAL:
return "Reference to undefined shell variable when WRDE_UNDEF was set in flags to wordexp()";
case WRDE_CMDSUB:
return "Command substitution requested when WRDE_NOCMD was set in flags to wordexp()";
case WRDE_NOSPACE:
return "Attempt to allocate memory in wordexp() failed";
case WRDE_SYNTAX:
return "Shell syntax error, such as unbalanced parentheses or unterminated string";
default:
return "Unknown error from wordexp() function";
}
}
static void do_wordexp(const char *name)
{
wordexp_t wx = { 0 };
int rc;
if ((rc = wordexp(name, &wx, WRDE_NOCMD | WRDE_SHOWERR | WRDE_UNDEF)) != 0)
err_remark("Failed to expand word [%s]\n%d: %s\n", name, rc, worderror(rc));
else
{
printf("Expansion of [%s]:\n", name);
for (size_t i = 0; i < wx.we_wordc; i++)
printf("%zu: [%s]\n", i+1, wx.we_wordv[i]);
wordfree(&wx);
}
}
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc <= 1)
{
char *buffer = 0;
size_t buflen = 0;
int length;
while ((length = getline(&buffer, &buflen, stdin)) != -1)
{
buffer[length-1] = '\0';
do_wordexp(buffer);
}
free(buffer);
}
else
{
for (int i = 1; i < argc; i++)
do_wordexp(argv[i]);
}
return 0;
}
(是:代码重复-不好。)
这可以与命令行参数一起运行(这意味着您必须与shell战斗-或至少确保shell不干扰您指定的内容),或者它将从标准输入中读取行。无论哪种方式,它都在字符串上运行wordexp()
并打印结果。给定一个输入文件:
*.c
*[mM]*
*.[ch] *[mM]* ~/.profile $HOME/.profile
它将产生:
Expansion of [*.c]:
1: [esc11.c]
2: [so-5246-1162-a.c]
3: [so-5246-1162-b.c]
4: [wexp19.c]
5: [wexp79.c]
Expansion of [*[mM]*]:
1: [README.md]
2: [esc11.dSYM]
3: [makefile]
4: [so-5246-1162-b.dSYM]
5: [wexp19.dSYM]
6: [wexp79.dSYM]
Expansion of [*.[ch] *[mM]* ~/.profile $HOME/.profile]:
1: [esc11.c]
2: [so-5246-1162-a.c]
3: [so-5246-1162-b.c]
4: [wexp19.c]
5: [wexp79.c]
6: [README.md]
7: [esc11.dSYM]
8: [makefile]
9: [so-5246-1162-b.dSYM]
10: [wexp19.dSYM]
11: [wexp79.dSYM]
12: [/Users/jleffler/.profile]
13: [/Users/jleffler/.profile]
请注意,它是如何扩展波浪号和$HOME
的。
您所追求的似乎是保留字符串之类的代码,例如
'""TEST""'
通过外壳扩展,产生如下输出:
\''""TEST""'\'
我有一系列函数可以产生与之等效的字符串(尽管实际输出与我展示的结果有所不同;这些函数使用蛮力,而上面的示例输出会生成一个稍微简单的字符串)。此代码可在我在GitHub上的SOQ(堆栈溢出问题)存储库中以src/libsoq子目录中的文件escape.c
和escape.h
的形式获得。这是一个使用escape_simple()
的程序,该程序会转义任何包含便携式文件名字符集([-A-Za-z0-9_.,/]
)之外的字符的字符串。
/* SO 5246-1162 */
#include <stdio.h>
#include "escape.h"
int main(void)
{
static const char *words[] =
{
"'\"\"TEST\"\"'",
"\\''\\\"\"\"\\\"TEST1\\\"\"\"\\\"'\\'",
"'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''",
};
enum { NUM_WORDS = sizeof(words) / sizeof(words[0]) };
for (int i = 0; i < NUM_WORDS; i++)
{
printf("Word %d: [[%s]]\n", i, words[i]);
char buffer[256];
if (escape_simple(words[i], buffer, sizeof(buffer)) >= sizeof(buffer))
fprintf(stderr, "Escape failed - not enough space!\n");
else
printf("Escaped: [[%s]]\n", buffer);
}
return 0;
}
请注意,解释C字符串相当混乱。这是程序的输出:
Word 0: [['""TEST""']]
Escaped: [[''\''""TEST""'\''']]
Word 1: [[\''\"""\"TEST1\"""\"'\']]
Escaped: [['\'\'''\''\"""\"TEST1\"""\"'\''\'\''']]
Word 2: [['\'"\"\""TEST2"\"\""\'']]
Escaped: [[''\''\'\''"\"\""TEST2"\"\""\'\'''\''']]
正如我指出的那样,转义代码使用蛮力。它输出一个单引号,然后处理字符串,用'\''
替换遇到的每个单引号。此顺序:
\'
)在单引号内,仅单引号需要特殊处理。显然,更复杂的解析器将更聪明地处理(重复)字符串开头或结尾的单引号,并识别重复的单引号并对其进行更简洁的编码。
您可以在printf
命令(与功能相反)中使用转义的输出,如下所示:
$ printf "%s\n" ''\''""TEST""'\''' '\'\'''\''\"""\"TEST1\"""\"'\''\'\''' ''\''\'\''"\"\""TEST2"\"\""\'\'''\'''
'""TEST""'
\''\"""\"TEST1\"""\"'\'
'\'"\"\""TEST2"\"\""\''
$
无法断定那里的任何shell代码都易于阅读;这很难读。但是复制粘贴可以使生活更轻松。