【问题标题】:Preserve quotes when using wordexp使用 wordexp 时保留引号
【发布时间】:2018-09-22 21:46:55
【问题描述】:

我正在尝试使用wordexp 函数对某些字符串进行类似shell 的扩展。 wordexp 删除单引号和双引号,但是我想保留它们。我最初的想法是用另一对引号将输入字符串中的所有引号对包围起来,这次是转义的引号,wordexp 应该保持不变(或相反)。很遗憾 这对于更复杂的输入会失败。

例如,对于'""TEST""',我想以\'\"\"TEST\"\"\' 结尾,我编写了这个sn-p 来演示当我使用我的方法时实际发生的情况:

#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 命令的一部分。

有没有办法让它做我想做的事?或者更简单的方法?

【问题讨论】:

  • 我敢打赌,如果 a) 在尝试使用 exp 之前检查 wordexp() 的错误返回值,b) 检查以确保 exp.we_wordc 大于您尝试使用的exp.we_wordv 的索引。
  • 另外,'""TEST""' 是您想要显示的内容,并且您正在寻找 wordexp 将变成的字符串吗?或者你想在输出中打印出文字反斜杠?
  • 对我来说这听起来像是“我想计算 1+1 但我不想删除 +”。你想达到什么目的?你是不是例如尝试进行命令替换,然后用结果构建一个 shell 命令?
  • 我对 POSIX wordexp() 规范(和 Quotingwordexp() 规范所指的)的阅读并没有让我认为有一个输入可以很容易地产生输出 @ 987654337@ — 输入肯定远没有您显示的 '""TEST""' 输入那么简单。输入\'\"\"TEST\"\"\' 将生成'""TEST""'。 (从 shell 测试这个是额外的艰苦工作!同上 C 源代码。)
  • 如果您想要生成一个将通过wordexp() 并产生所需结果的字符串,那么您需要一个不同的非标准函数。我将它的一个实现称为escape()(但这不是线程安全的,因为它使用全局变量作为转义字符串)。我有变体escape_simple()escape_always()escape_chosen(),它们是线程安全的,因为它们只使用调用函数提供的空间。

标签: c linux


【解决方案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. Word 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""'

通过 shell 扩展,产生如下输出:

\''""TEST""'\'

我有一系列函数可以生成与之等效的字符串(尽管实际输出与我展示的不同;这些函数使用蛮力,上面的示例输出生成了一个稍微简单的字符串)。此代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 escape.cescape.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 代码都易于阅读;它非常难以阅读。但是复制粘贴让生活更轻松。

【讨论】:

  • 谢谢,这帮助我更好地理解了这个问题。
猜你喜欢
  • 2017-04-22
  • 2023-03-24
  • 2021-07-15
  • 1970-01-01
  • 2014-04-08
  • 2017-08-12
  • 1970-01-01
  • 1970-01-01
  • 2021-09-28
相关资源
最近更新 更多