分段错误
在您的代码中,第二个测试字符串:
char const *str2 = "'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''";
产生语法错误。在这样的字符串上处理 C 或 shell 转义规则是相当可怕的,但您可以分析在字符串末尾有一个不匹配的单引号。将 C 字符串文字转换为字符串会产生:
'\'"\"\""TEST2"\"\""\''
分析时,关键字符由插入符号标记:
'\'"\"\""TEST2"\"\""\''
^^^^^ ^ ^^ ^^ ^ ^^ ^
12345 6 78 91 1 11 1
0 1 23 4
- 开始单引号字符串
- 反斜杠(在单引号字符串中没有特殊含义)
- 结束单引号字符串
- 开始双引号字符串
- 第一个转义的双引号(字符串的一部分)
- 第二个转义双引号(字符串的一部分)
- 结束双引号字符串
- Word
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""'
通过 shell 扩展,产生如下输出:
\''""TEST""'\'
我有一系列函数可以生成与之等效的字符串(尽管实际输出与我展示的不同;这些函数使用蛮力,上面的示例输出生成了一个稍微简单的字符串)。此代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 escape.c 和 escape.h 的形式在 src/libsoq 子目录中找到。这是一个使用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 代码都易于阅读;它非常难以阅读。但是复制粘贴让生活更轻松。