c - 使用wordexp时保留引号

标签 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)

由于双引号嵌套在单引号中,因此失败
在这种情况下,天真地用转义引号包围每一对引号是行不通的(尽管我不确定segfault为什么会发生)。
我还考虑过用其他ascii字符临时交换引号,但是没有任何引号不能作为有效shell命令的一部分。
有没有办法让它适应我的要求?或者用更简单的方法?

最佳答案

分段故障
在代码中,第二个测试字符串:

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

产生语法错误。在这样的字符串上处理C或shell转义规则是相当可怕的,但是您可以分析在字符串末尾有一个不匹配的单引号。将C字符串文本转换为字符串会产生:
'\'"\"\""TEST2"\"\""\''

分析时,关键字符由插入符号标记:
'\'"\"\""TEST2"\"\""\''
^^^^^ ^ ^^    ^^ ^ ^^ ^
12345 6 78    91 1 11 1
               0 1 23 4

开始单引号字符串
反斜杠(单引号字符串中没有特殊含义)
结束单引号字符串
开始双引号字符串
第一个转义双引号(字符串的一部分)
第二个转义双引号(字符串的一部分)
结束双引号字符串
WordTEST2是引号外的纯文本(字符串的一部分)
开始双引号字符串
第一个转义双引号(字符串的一部分)
第二个转义双引号(字符串的一部分)
结束双引号字符串
转义单引号(字符串的一部分)
单引号字符串的开头
因为最后一个单引号字符串没有结尾,所以有语法错误,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]

注意它是如何扩展tilde符号和$HOME的。
转义字符串
似乎您所追求的是将保留以下字符串的代码
'""TEST""'

通过外壳的膨胀,产生如下输出:
\''""TEST""'\'

我有一系列函数可以生成与之等价的字符串(尽管实际输出与我展示的不同;这些函数使用蛮力,上面的示例输出生成的字符串稍微简单一些)。此代码在GitHub上的mySOQ(堆栈溢出问题)存储库中作为文件escape.csrc/libsoq子目录中的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代码都很容易阅读;阅读起来非常困难。但是复制粘贴使生活更容易。

关于c - 使用wordexp时保留引号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52461162/

相关文章:

c - 匹配 2 个文件中的文本

linux - 如何在Linux中使用zip递归压缩所有txt文件?

c++ - 如何访问具有多个括号的一维数组以提高可读性?

linux - 字典的grep过滤

linux - 在日志文件中报告状态 crontab 行 Linux RedHat

Linux shell : LOOP for create file in each folder

linux - 我可以从 Linux 命令行打印 UTF-8 编码的文件吗?

c - OpenSSL BIGNUM — 是否有按位 & 函数?

c - 使用 Windows 10 和 Cygwin 从命令提示符运行 c 程序

c - 为 char **foobar 或 char *foobar[] 分配 malloc,如何进行?