使用wordexp时保留引号

时间:2018-09-22 21:46:55

标签: c linux

我正在尝试使用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命令的一部分。

是否有一种方法可以使它适应我的要求?还是一些更简单的方法?

1 个答案:

答案 0 :(得分:1)

分段错误

在您的代码中,第二个测试字符串:

char const *str2 = "'\\'\"\\\"\\\"\"TEST2\"\\\"\\\"\"\\''";

产生语法错误。在这样的字符串上,应对C或shell转义规则显得有些可怕,但是您可以分析出,在字符串的末尾有不匹配的单引号。将C字符串文字转换为字符串会产生:

'\'"\"\""TEST2"\"\""\''

分析时,关键字符用脱字号标记:

'\'"\"\""TEST2"\"\""\''
^^^^^ ^ ^^    ^^ ^ ^^ ^
12345 6 78    91 1 11 1
               0 1 23 4
  1. 以单引号开头的字符串
  2. 反斜杠(在单引号中没有特殊含义)
  3. 单引号结尾的字符串
  4. 以双引号开头的字符串
  5. 第一个转义的双引号(字符串的一部分)
  6. 第二个转义的双引号(字符串的一部分)
  7. 以双引号结尾的字符串
  8. 单词TEST2是引号(字符串的一部分)之外的纯文本
  9. 以双引号开头的字符串
  10. 第一个转义的双引号(字符串的一部分)
  11. 第二个转义的双引号(字符串的一部分)
  12. 以双引号结尾的字符串
  13. 转义的单引号(字符串的一部分)
  14. 单引号字符串的开始

因为最后的单引号字符串没有结尾,所以存在语法错误,并且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.cescape.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代码都易于阅读;这很难读。但是复制粘贴可以使生活更轻松。