glibc 知:手册10:模式匹配

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 模式匹配

Pattern Matching

GNU C 库为两种模式提供模式匹配工具:正则表达式和文件名通配符。该库还提供了一种工具,用于扩展变量和命令引用,并以 shell 的方式将文本解析为单词。

2.1. 通配符匹配

Wildcard Matching

本节介绍如何将通配符模式与特定字符串进行匹配。结果是肯定或否定的答案:字符串是否符合模式。这里描述的符号都是在 fnmatch.h 中声明的。

函数:int fnmatch (const char *pattern, const char *string, int flags)

Preliminary: | MT-Safe env locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

此函数测试字符串字符串是否与模式模式匹配。如果它们匹配,则返回 0;否则,它返回非零值 FNM_NOMATCH。参数模式和字符串都是字符串。

参数 flags 是改变匹配细节的标志位的组合。有关已定义标志的列表,请参见下文。

在 GNU C 库中,fnmatch 有时可能会通过返回不等于 FNM_NOMATCH 的非零值来报告“错误”。

这些是 flags 参数的可用标志:

  • FNM_FILE_NAME

    特殊处理“/”字符,以匹配文件名。如果设置了此标志,则模式中的通配符构造不能匹配字符串中的“/”。因此,匹配“/”的唯一方法是在模式中使用显式的“/”。

  • FNM_PATHNAME

    这是 FNM_FILE_NAME 的别名;它来自 POSIX.2。我们不推荐使用此名称,因为我们不使用术语“路径名”作为文件名。

  • FNM_PERIOD

    如果“.”字符出现在字符串的开头,则对其进行特殊处理。如果设置了此标志,则模式中的通配符构造不能匹配“.”作为字符串的第一个字符。

    如果您同时设置 FNM_PERIOD 和 FNM_FILE_NAME,则特殊处理适用于“/”之后的“.”以及字符串开头的“.”。(shell 将 FNM_PERIOD 和 FNM_FILE_NAME 标志一起用于匹配文件名。)

  • FNM_NOESCAPE

    不要在模式中特别对待“\”字符。通常,‘’ 引用后面的字符,关闭它的特殊含义(如果有的话),以便它只匹配它自己。启用引号时,模式‘?’只匹配字符串‘?’,因为模式中的问号就像一个普通字符。

    如果你使用 FNM_NOESCAPE,那么‘\’就是一个普通字符。

  • FNM_LEADING_DIR

    忽略字符串中以“/”开头的尾随字符序列;也就是说,测试字符串是否以模式匹配的目录名称开头。

    如果设置了此标志,则“foo*”或“foobar”作为模式将匹配字符串“foobar/frobozz”。

  • FNM_CASEFOLD

    在将字符串与模式进行比较时忽略大小写。

  • FNM_EXTMATCH

    除了普通模式,还可以识别 ksh 中引入的扩展模式。模式以下表中解释的形式编写,其中模式列表是 |分隔的模式列表。

    ?(pattern-list)

    如果模式列表中的任何模式出现零次或一次允许匹配输入字符串,则模式匹配。

    *(pattern-list)

    如果模式列表中任何模式的零次或多次出现允许匹配输入字符串,则模式匹配。

    +(pattern-list)

    如果模式列表中任何模式的一次或多次出现允许匹配输入字符串,则该模式匹配。

    @(pattern-list)

    如果模式列表中的任何模式恰好出现一次允许匹配输入字符串,则该模式匹配。

    !(pattern-list)

    如果输入字符串不能与模式列表中的任何模式匹配,则模式匹配。

2.2. 通配符

Globbing

通配符的典型用途是匹配目录中的文件,并列出所有匹配项。这称为通配符。

您可以使用 fnmatch 执行此操作,方法是一一读取目录条目并使用 fnmatch 测试每个条目。但这会很慢(而且很复杂,因为您必须手动处理子目录)。

该库提供了一个函数 glob 来方便地使用通配符。glob 和本节中的其他符号在 glob.h 中声明。

2.2.1. 调用 glob

Calling glob

globbing 的结果是文件名(字符串)的向量。为了返回这个向量,glob 使用了一种特殊的数据类型 glob_t,它是一个结构。您将 glob 传递给结构的地址,它会填充结构的字段以告诉您结果。

数据类型:glob_t

此数据类型包含一个指向词向量的指针。更准确地说,它记录了词向量的地址和它的大小。GNU 实现包含更多非标准扩展的字段。

gl_pathc

向量中的元素数,如果使用 GLOB_DOOFFS 标志,则不包括初始空条目(请参阅下面的 gl_offs)。

gl_pathv

向量的地址。该字段的类型为 char **。

gl_offs

向量的第一个实数元素在 gl_pathv 字段中的标称地址的偏移量。与其他字段不同,这始终是 glob 的输入,而不是它的输出。

如果您使用非零偏移量,则向量开头的许多元素将保留为空。(glob 函数用空指针填充它们。)

仅当您使用 GLOB_DOOFFS 标志时, gl_offs 字段才有意义。否则,无论该字段中有什么,偏移量始终为零,并且第一个实数元素出现在向量的开头。

gl_closedir

closedir 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型为void (*) (void *)。

这是一个 GNU 扩展。

gl_readdir

用于读取目录内容的 readdir 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是struct dirent () (void *)。

gl_readdir 的实现需要初始化 struct dirent 对象的以下成员:

  • d_type

    如果已知,则应将此成员设置为条目的文件类型。否则,可以使用值 DT_UNKNOWN。在文件类型指示不需要数据的情况下,glob 函数可以使用指定的文件类型来避免回调。

  • d_ino

    该成员必须非零,否则 glob 可能会跳过当前条目并再次调用 gl_readdir 回调函数以检索另一个条目。

  • d_name

    此成员必须设置为条目的名称。它必须以空值结尾。

下面的示例显示了如何分配包含给定名称的结构 dirent 对象。

#include <dirent.h>
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

struct dirent *
mkdirent (const char *name)
{
  size_t dirent_size = offsetof (struct dirent, d_name) + 1;
  size_t name_length = strlen (name);
  size_t total_size = dirent_size + name_length;
  if (total_size < dirent_size)
    {
      errno = ENOMEM;
      return NULL;
    }
  struct dirent *result = malloc (total_size);
  if (result == NULL)
    return NULL;
  result->d_type = DT_UNKNOWN;
  result->d_ino = 1;            /* Do not skip this entry. */
  memcpy (result->d_name, name, name_length + 1);
  return result;
}

glob 函数读取上面列出的 struct dirent 成员,并在 gl_readdir 回调函数返回后立即复制 d_name 成员中的文件名。任何回调函数的未来调用可能会释放或重用缓冲区。glob 函数的调用者有责任在调用 glob 或使用回调函数时分配和取消分配缓冲区。例如,应用程序可以在 gl_readdir 回调函数中分配缓冲区,并在 gl_closedir 回调函数中释放它。

gl_readdir 成员是 GNU 扩展。

gl_opendir

opendir 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 void () (const char *)。

这是一个 GNU 扩展。

gl_stat

stat 函数的替代实现的地址,用于获取有关文件系统中对象的信息。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 int (*) (const char *, struct stat *)。

这是一个 GNU 扩展。

gl_lstat

lstat 函数的替代实现的地址,用于获取有关文件系统中对象的信息,而不是遵循符号链接。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 int (*) (const char *, struct stat *)。

这是一个 GNU 扩展。

gl_flags

调用 glob 时使用的标志。此外,可能会设置 GLOB_MAGCHAR。有关更多详细信息,请参阅用于通配符的标志。

这是一个 GNU 扩展。

为了在 glob64 函数中使用,glob.h 包含另一个非常相似类型的定义。glob64_t 与 glob_t 的不同之处仅在于成员 gl_readdir、gl_stat 和 gl_lstat 的类型。

数据类型:glob64_t

此数据类型包含一个指向词向量的指针。更准确地说,它记录了词向量的地址和它的大小。GNU 实现包含更多非标准扩展的字段。

gl_pathc

向量中的元素数,如果使用 GLOB_DOOFFS 标志,则不包括初始空条目(请参阅下面的 gl_offs)。

gl_pathv

向量的地址。该字段的类型为 char **。

gl_offs

向量的第一个实数元素在 gl_pathv 字段中的标称地址的偏移量。与其他字段不同,这始终是 glob 的输入,而不是它的输出。

如果您使用非零偏移量,则向量开头的许多元素将保留为空。(glob 函数用空指针填充它们。)

仅当您使用 GLOB_DOOFFS 标志时, gl_offs 字段才有意义。否则,无论该字段中有什么,偏移量始终为零,并且第一个实数元素出现在向量的开头。

gl_closedir

closedir 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型为void (*) (void *)。

这是一个 GNU 扩展。

gl_readdir

用于读取目录内容的 readdir64 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是struct dirent64 () (void *)。

这是一个 GNU 扩展。

gl_opendir

opendir 函数的替代实现的地址。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 void () (const char *)。

这是一个 GNU 扩展。

gl_stat

stat64 函数的替代实现的地址,用于获取有关文件系统中对象的信息。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 int (*) (const char *, struct stat64 *)。

这是一个 GNU 扩展。

gl_lstat

lstat64 函数的替代实现的地址,用于获取有关文件系统中对象的信息,而不是遵循符号链接。如果在标志参数中设置了 GLOB_ALTDIRFUNC 位,则使用它。该字段的类型是 int (*) (const char *, struct stat64 *)。

这是一个 GNU 扩展。

gl_flags

调用 glob 时使用的标志。此外,可能会设置 GLOB_MAGCHAR。有关更多详细信息,请参阅用于通配符的标志。

这是一个 GNU 扩展。

函数:int glob (const char *pattern, int flags, int (*errfunc) (const char *filename, int error-code), glob_t *vector-ptr)

Preliminary: | MT-Unsafe race:utent env sig:ALRM timer locale | AS-Unsafe dlopen plugin corrupt heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

函数 glob 使用当前目录中的模式模式进行 globbing。它将结果放入一个新分配的向量中,并将该向量的大小和地址存储到 *vector-ptr 中。参数 flags 是位标志的组合;有关标志的详细信息,请参阅用于通配符的标志。

globbing 的结果是一系列文件名。函数 glob 为每个结果词分配一个字符串,然后分配一个 char ** 类型的向量来存储这些字符串的地址。向量的最后一个元素是空指针。这个向量称为词向量。

为了返回这个向量,glob 将它的地址和它的长度(元素的数量,不包括终止的空指针)存储到 *vector-ptr 中。

通常,glob 在返回文件名之前按字母顺序对文件名进行排序。如果您想尽快获取信息,可以使用标志 GLOB_NOSORT 将其关闭。通常让 glob 对它们进行排序是一个好主意——如果你按字母顺序处理文件,用户会感觉到你的应用程序的进度。

如果 glob 成功,则返回 0。否则,返回以下错误代码之一:

GLOB_ABORTED

打开目录时出错,您使用了标志 GLOB_ERR 或您指定的 errfunc 返回了一个非零值。有关 GLOB_ERR 标志和 errfunc 的说明。

GLOB_NOMATCH

该模式与任何现有文件都不匹配。如果您使用 GLOB_NOCHECK 标志,那么您永远不会收到此错误代码,因为该标志告诉 glob 假装该模式与至少一个文件匹配。

GLOB_NOSPACE

分配内存来保存结果是不可能的。

如果发生错误,glob 会在 *vector-ptr 中存储有关它迄今为止找到的所有匹配项的信息。

重要的是要注意,如果 glob 函数遇到没有 LFS 接口就无法处理的目录或文件,它不会失败。glob 的实现应该在内部使用这些函数。这至少是 Unix 标准所做的假设。允许用户提供自己的目录处理和统计功能的 GNU 扩展使事情变得有点复杂。如果使用这些回调函数并且遇到大文件或目录,glob 可能会失败。

函数:int glob64 (const char *pattern, int flags, int (*errfunc) (const char *filename, int error-code), glob64_t *vector-ptr)

Preliminary: | MT-Unsafe race:utent env sig:ALRM timer locale | AS-Unsafe dlopen corrupt heap lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

glob64 函数是作为大型文件峰会扩展的一部分添加的,但不是原始 LFS 提案的一部分。原因很简单:没有必要。GNU glob 实现的扩展增加了 glob64 函数的必要性,它允许用户提供自己的目录处理和统计函数。readdir 和 stat 函数确实取决于 _FILE_OFFSET_BITS 的选择,因为 struct dirent 和 struct stat 类型的定义将根据选择而改变。

除了这个区别之外,glob64 在各个方面都像 glob 一样工作。

这个函数是一个 GNU 扩展。

2.2.2. 通配符

Flags for Globbing

本节介绍您可以在 glob 的 flags 参数中指定的标准标志。选择您想要的标志,并将它们与 C 位 OR 运算符 | 结合起来。

请注意,有更多用于 Globbing 的标志可作为 GNU 扩展使用。

GLOB_APPEND

将此扩展中的单词附加到先前调用 glob 产生的单词向量中。这样,您可以有效地扩展多个单词,就好像它们之间用空格连接一样。

为了使附加工作正常,您不能在调用 glob 之间修改词向量结构的内容。而且,如果您在第一次调用 glob 时设置了 GLOB_DOOFFS,则在追加到结果时也必须设置它。

请注意,在您第二次调用 glob 后,存储在 gl_pathv 中的指针可能不再有效,因为 glob 可能已经重新定位了向量。所以每次调用 glob 后总是从 glob_t 结构中获取 gl_pathv;永远不要在调用之间保存指针。

GLOB_DOOFFS

在单词向量的开头留下空白槽。gl_offs 字段表示要离开多少个插槽。空白槽包含空指针。

GLOB_ERR

如果为了完全扩展模式而必须读取的目录读取有任何困难,请立即放弃并报告错误。此类困难可能包括您没有必要访问权限的目录。通常,尽管有任何错误,glob 都会尽最大努力继续阅读它可以读取的任何目录。

您可以通过在调用 glob 时指定错误处理函数 errfunc 来进行更多控制。如果 errfunc 不是空指针,则 glob 在无法读取目录时不会立即放弃;相反,它使用两个参数调用 errfunc,如下所示:

(*errfunc) (filename, error-code)

参数 filename 是 glob 无法打开或无法读取的目录名称,error-code 是报告给 glob 的 errno 值。

如果错误处理函数返回非零值,则 glob 立即放弃。否则,它会继续。

GLOB_MARK

如果模式与目录名称匹配,则在返回目录名称时附加“/”。

GLOB_NOCHECK

如果模式与任何文件名都不匹配,则返回模式本身,就好像它是已匹配的文件名一样。(通常,当模式不匹配任何内容时,glob 会返回没有匹配项。)

GLOB_NOESCAPE

不要在模式中特别对待“\”字符。通常,‘’ 引用后面的字符,关闭它的特殊含义(如果有的话),以便它只匹配它自己。启用引号时,模式‘?’只匹配字符串‘?’,因为模式中的问号就像一个普通字符。

如果您使用 GLOB_NOESCAPE,则“\”是一个普通字符。

glob 通过重复调用函数 fnmatch 来完成它的工作。它通过在对 fnmatch 的调用中打开 FNM_NOESCAPE 标志来处理标志 GLOB_NOESCAPE。

GLOB_NOSORT

不要对文件名进行排序;不按特定顺序归还它们。(实际上,顺序将取决于目录中条目的顺序。)不排序的唯一原因是为了节省时间。

2.2.3. 更多用于通配符的标志

More Flags for Globbing

除了上一节中描述的标志之外,glob 的 GNU 实现还允许在 glob.h 文件中定义的更多标志。一些扩展实现了现代 shell 实现中可用的功能。

GLOB_PERIOD

这 。字符(句点)被特殊对待。不能用通配符匹配。请参阅通配符匹配,FNM_PERIOD。

GLOB_MAGCHAR

GLOB_MAGCHAR 值不会在 flags 参数中提供给 glob。相反,如果用于匹配的模式包含任何通配符,则 glob 在作为结果提供的 glob_t 结构的 gl_flags 元素中设置此位。

GLOB_ALTDIRFUNC

glob 实现不是使用普通函数来访问文件系统,而是使用用户提供的函数,该函数在 pglob 参数指向的结构中指定。有关函数的更多信息,请参阅有关目录处理的部分,请参阅访问目录和读取文件的属性。

GLOB_BRACE

如果给出了这个标志,模式中大括号的处理就会改变。现在要求大括号正确分组。即,对于每个左大括号,必须有一个右大括号。大括号可以递归使用。因此,可以在另一个括号表达式中定义一个括号表达式。需要注意的是,每个大括号表达式的范围都完全包含在外大括号表达式中(如果有的话)。

匹配大括号之间的字符串通过拆分 at ,(逗号)字符分隔为单个表达式。逗号本身被丢弃。请注意我们上面所说的关于递归大括号表达式的内容。用于分隔子表达式的逗号必须在同一级别。大括号子表达式中的逗号不匹配。它们在更深层次的大括号表达式的扩展过程中使用。下面的示例显示了这一点

glob ("{foo/{,bar,biz},baz}", GLOB_BRACE, NULL, &result)

等价于序列

glob ("foo/", GLOB_BRACE, NULL, &result)
glob ("foo/bar", GLOB_BRACE|GLOB_APPEND, NULL, &result)
glob ("foo/biz", GLOB_BRACE|GLOB_APPEND, NULL, &result)
glob ("baz", GLOB_BRACE|GLOB_APPEND, NULL, &result)

如果我们抛开错误处理。

GLOB_NOMAGIC

如果模式不包含通配符结构(它是文字文件名),则将其作为唯一的“匹配”单词返回,即使该名称不存在文件也是如此。

GLOB_TILDE

如果使用此标志,则字符 ~(波浪号)如果出现在模式的开头,则会被特殊处理。它不是逐字记录,而是用来表示已知用户的主目录。

如果 ~ 是模式中的唯一字符或者它后跟 /(斜杠),则替换进程所有者的主目录。使用 getlogin 和 getpwnam 从系统数据库中读取信息。以用户 bart 为例,他的主目录位于 /home/bart。对他来说,一个电话就像

glob ("~/bin/*", GLOB_TILDE, NULL, &result)

将返回目录 /home/bart/bin 的内容。除了引用自己的主目录,还可以命名其他用户的主目录。为此,必须在波浪号字符后附加用户名。所以用户 homer 的 bin 目录的内容可以通过

glob ("~homer/bin/*", GLOB_TILDE, NULL, &result)

如果用户名无效或由于某种原因无法确定主目录,则该模式保持不变,并将其本身用作结果。即,如果在最后一个示例中 home 不可用,波浪号扩展会产生“~homer/bin/*”,并且 glob 不会寻找名为 ~homer 的目录。

如果设置了 nonomatch 标志,则此功能等效于 C-shell 中可用的功能。

GLOB_TILDE_CHECK

如果使用此标志,glob 的行为就像给出了 GLOB_TILDE。唯一的区别是,如果用户名不可用或由于其他原因无法确定主目录,则会导致错误。glob 将返回 GLOB_NOMATCH 而不是使用模式本身作为名称。

如果未设置 nonomatch 标志,则此功能等效于 C-shell 中可用的功能。

GLOB_ONLYDIR

如果使用此标志,则通配函数将此作为提示,即调用者只对匹配模式的目录感兴趣。如果有关文件类型的信息很容易获得,则将拒绝非目录,但不会进行额外的工作来确定每个文件的信息。即,调用者必须仍然能够过滤掉目录。

此功能仅适用于 GNU glob 实现。它主要在内部用于提高性能,但也可能对用户有用,因此在此处记录。

在大多数情况下,调用 glob 会分配用于表示函数调用结果的资源。如果在对 glob 的多次调用中使用 glob_t 类型的相同对象,则资源将被释放或重用,因此不会出现泄漏。但这不包括所有 glob 调用完成的时间。

函数:void globfree (glob_t *pglob)

Preliminary: | MT-Safe | AS-Unsafe corrupt heap | AC-Unsafe corrupt mem | See POSIX Safety Concepts.

globfree 函数释放先前调用与 pglob 指向的对象相关联的 glob 分配的所有资源。只要不再使用当前使用的 glob_t 类型对象,就应该调用此函数。

函数:void globfree64 (glob64_t *pglob)

Preliminary: | MT-Safe | AS-Unsafe corrupt lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

此函数等效于 globfree,但它释放由 glob64 分配的 glob64_t 类型的记录。

2.3. 正则表达式匹配

Regular Expression Matching

GNU C 库支持两个用于匹配正则表达式的接口。一个是标准的 POSIX.2 接口,另一个是 GNU C 库多年来一直拥有的。

这两个接口都在头文件 regex.h 中声明。如果定义 _POSIX_C_SOURCE,则仅声明 POSIX.2 函数、结构和常量。

2.3.1. POSIX正则表达式编译

POSIX Regular Expression Compilation

在真正匹配正则表达式之前,您必须对其进行编译。这不是真正的编译——它产生一种特殊的数据结构,而不是机器指令。但它和普通的编译一样,它的目的是让你能够快速“执行”模式。(请参阅匹配已编译的 POSIX 正则表达式,了解如何使用已编译的正则表达式进行匹配。)

编译的正则表达式有一种特殊的数据类型:

数据类型:regex_t

这种类型的对象包含一个编译的正则表达式。它实际上是一个结构。它只有一个领域是你的程序应该关注的:

re_nsub

此字段包含已编译的正则表达式中括号子表达式的数量。

还有其他几个字段,但我们不在这里描述它们,因为只有库中的函数应该使用它们。

创建 regex_t 对象后,可以通过调用 regcomp 将正则表达式编译到其中。

函数:int regcomp (regex_t *restrict compiled, const char *restrict pattern, int cflags)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.

函数 regcomp 将正则表达式“编译”成数据结构,您可以使用该数据结构与 regexec 匹配字符串。编译后的正则表达式格式是为高效匹配而设计的。regcomp 将其存储到 *compiled 中。

您可以分配一个 regex_t 类型的对象并将其地址传递给 regcomp。

参数 cflags 允许您指定控制正则表达式的语法和语义的各种选项。请参阅 POSIX 正则表达式的标志

如果使用标志 REG_NOSUB,则 regcomp 从编译的正则表达式中省略记录子表达式实际匹配方式所需的信息。在这种情况下,您不妨在调用 regexec 时为 matchptr 和 nmatch 参数传递 0。

如果不使用 REG_NOSUB,那么编译后的正则表达式确实有能力记录子表达式如何匹配。此外,regcomp 通过将数字存储在compiled->re_nsub 中来告诉您模式有多少子表达式。您可以使用该值来决定分配数组多长时间来保存有关子表达式匹配的信息。

regcomp 如果成功编译正则表达式,则返回 0;否则,它返回一个非零错误代码(见下表)。您可以使用 regerror 生成描述非零值原因的错误消息字符串;请参阅 POSIX 正则表达式匹配清理

以下是 regcomp 可以返回的可能的非零值:

  • REG_BADBR

    正则表达式中存在无效的“{…}”结构。有效的“{…}”结构必须包含一个数字,或两个以逗号分隔的递增顺序数字。

  • REG_BADPAT

    正则表达式中存在语法错误。

  • REG_BADRPT

    诸如“?”或“*”之类的重复运算符出现在错误的位置(没有前面的子表达式可操作)。

  • REG_ECOLLATE

    正则表达式引用了无效的整理元素(未在当前语言环境中为字符串整理定义的元素)。请参阅区域设置类别

  • REG_ECTYPE

    正则表达式引用了无效的字符类名。

  • REG_EESCAPE

    正则表达式以“\”结尾。

  • REG_ESUBREG

    “\digit”结构中存在无效数字。

  • REG_EBRACK

    正则表达式中有不平衡的方括号。

  • REG_EPAREN

    扩展正则表达式的括号不平衡,或者基本正则表达式的“(”和“)”不平衡。

  • REG_EBRACE

    正则表达式有不平衡的‘{’和‘}’。

  • REG_ERANGE

    范围表达式中的端点之一无效。

  • REG_ESPACE

    regcomp 内存不足。

2.3.2. POSIX 正则表达式的标志

Flags for POSIX Regular Expressions

这些是您可以在使用 regcomp 编译正则表达式时在 cflags 操作数中使用的位标志。

REG_EXTENDED

将模式视为扩展的正则表达式,而不是基本的正则表达式。

REG_ICASE

匹配字母时忽略大小写。

REG_NOSUB

不要费心存储 matchptr 数组的内容。

REG_NEWLINE

将字符串中的换行符视为将字符串分成多行,这样“$”可以在换行符之前匹配,“”可以在换行符之后匹配。此外,不允许“.”匹配换行符,也不允许“[…]”匹配换行符。

否则,换行符就像任何其他普通字符一样。

2.3.3. 匹配编译后的 POSIX 正则表达式

Matching a Compiled POSIX Regular Expression

编译正则表达式后,如 POSIX 正则表达式编译中所述,您可以使用 regexec 将其与字符串匹配。除非正则表达式包含锚字符(“^”或“$”),否则字符串中任何位置的匹配都算成功。

函数:int regexec (const regex_t *restrict compiled, const char *restrict string, size_t nmatch, regmatch_t matchptr[restrict], int eflags)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.

此函数尝试匹配已编译的正则表达式 *compiled 与字符串。

如果正则表达式匹配,regexec 返回 0;否则,它返回一个非零值。请参阅下表了解非零值的含义。您可以使用 regerror 生成描述非零值原因的错误消息字符串;请参阅 POSIX 正则表达式匹配清理。

参数 eflags 是启用各种选项的位标志字。

如果要获取有关字符串的哪一部分实际匹配正则表达式或其子表达式的信息,请使用参数 matchptr 和 nmatch。否则,为 nmatch 传递 0,为 matchptr 传递 NULL。请参阅使用子表达式匹配结果。

您必须将正则表达式与编译正则表达式时生效的同一组当前语言环境匹配。

函数 regexec 在 eflags 参数中接受以下标志:

  • REG_NOTBOL

    不要将指定字符串的开头视为一行的开头;更一般地说,不要对可能在它之前的文本做出任何假设。

  • REG_NOTEOL

    不要将指定字符串的结尾视为一行的结尾;更一般地说,不要对后面可能出现的文本做出任何假设。

    以下是 regexec 可以返回的可能的非零值:

  • REG_NOMATCH

    模式与字符串不匹配。这并不是真正的错误。

  • REG_ESPACE

    regexec 内存不足。

2.3.4. 匹配结果与子表达式

Match Results with Subexpressions

当 regexec 匹配模式的括号子表达式时,它会记录它们匹配的字符串部分。它通过将偏移量存储到一个数组中返回该信息,该数组的元素是 regmatch_t 类型的结构。数组的第一个元素(索引 0)记录匹配整个正则表达式的字符串部分。数组的每个其他元素记录匹配单个括号子表达式的部分的开始和结束。

数据类型:regmatch_t

这是您传递给 regexec 的 matchptr 数组的数据类型。它包含两个结构字段,如下所示:

rm_so

子字符串开头的字符串偏移量。将此值添加到字符串以获取该部分的地址。

rm_eo

子字符串结尾的字符串偏移量。

数据类型:regoff_t

regoff_t 是另一个有符号整数类型的别名。regmatch_t 的字段具有 regoff_t 类型。

regmatch_t 元素在位置上对应于子表达式;第一个元素(索引 1)记录第一个子表达式匹配的位置,第二个元素记录第二个子表达式,依此类推。子表达式的顺序是它们开始的顺序。

当您调用 regexec 时,您使用 nmatch 参数指定 matchptr 数组的长度。这告诉 regexec 要存储多少元素。如果实际的正则表达式有多个 nmatch 子表达式,那么您将无法获得其余的偏移量信息。但这不会改变模式是否匹配特定的字符串。

如果您不希望 regexec 返回有关子表达式匹配位置的任何信息,您可以为 nmatch 提供 0,或者在使用 regcomp 编译模式时使用标志 REG_NOSUB。

2.3.5. 子表达式匹配的复杂性

Complications in Subexpression Matching

有时,子表达式匹配没有字符的子字符串。当‘f(o*)’匹配字符串‘fum’时会发生这种情况。(它实际上只匹配“f”。)在这种情况下,两个偏移量都标识了字符串中找到空子字符串的点。在此示例中,偏移量均为 1。

有时整个正则表达式可以完全匹配而不使用它的某些子表达式——例如,当‘ba(na)*’匹配字符串‘ba’时,不使用括号子表达式。发生这种情况时,regexec 将 -1 存储在该子表达式的元素的两个字段中。

有时匹配整个正则表达式可以匹配一个特定的子表达式不止一次——例如,当‘ba(na)*’匹配字符串‘bananana’时,括号内的子表达式匹配3次。发生这种情况时,regexec 通常会存储与子表达式匹配的字符串最后一部分的偏移量。在“香蕉”的情况下,这些偏移量是 6 和 8。

但最后一场比赛并不总是被选中的那一场。更准确地说,最后一次匹配的机会是优先的。这意味着当一个子表达式出现在另一个子表达式中时,为内部子表达式报告的结果反映了在外部子表达式的最后一次匹配中发生的任何事情。例如,考虑匹配字符串“bananas bas”的“(ba(na)s )”。内部表达式最后一次实际匹配是在第一个单词的末尾附近。但它在第二个单词中再次被考虑,并且无法在那里匹配。regexec 报告未使用“na”子表达式。

该规则适用的另一个地方是当正则表达式

\(ba\(na\)*s \|nefer\(ti\)* \)*

匹配“香蕉纳芙蒂蒂”。“na”子表达式在第一个单词中匹配,但在第二个单词中不匹配,因为那里使用了另一个替代项。再一次,外部子表达式的第二次重复覆盖了第一次,并且在第二次重复中,不使用“na”子表达式。因此 regexec 报告未使用“na”子表达式。

2.3.6. POSIX 正则表达式匹配清理

POSIX Regexp Matching Cleanup

当您使用完一个已编译的正则表达式后,您可以通过调用 regfree 来释放它使用的存储空间。

函数:void regfree (regex_t *compiled)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

调用 regfree 会释放 *compiled 指向的所有存储空间。这包括本手册中未记录的 regex_t 结构的各种内部字段。

regfree 不会释放对象 *compiled 本身。

在使用该结构编译另一个正则表达式之前,您应该始终使用 regfree 释放 regex_t 结构中的空间。

当 regcomp 或 regexec 报错时,可以使用函数 regerror 将其变成错误信息字符串。

函数:size_t regerror (int errcode, const regex_t *restrict compiled, char *restrict buffer, size_t length)

Preliminary: | MT-Safe env | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

此函数为错误代码 errcode 生成错误消息字符串,并将字符串存储在从缓冲区开始的长度字节内存中。对于已编译的参数,请提供 regcomp 或 regexec 在遇到错误时使用的相同编译正则表达式结构。或者,您可以为编译提供 NULL;您仍然会收到一条有意义的错误消息,但可能没有那么详细。

如果错误消息不能容纳在长度字节中(包括终止空字符),则 regerror 将其截断。regerror 存储的字符串始终以空值结尾,即使它已被截断。

regerror 的返回值是存储整个错误消息所需的最小长度。如果这小于长度,则错误消息未被截断,您可以使用它。否则,您应该使用更大的缓冲区再次调用 regerror。

这是一个使用 regerror 的函数,但总是为错误消息动态分配一个缓冲区:

char *get_regerror (int errcode, regex_t *compiled)
{
  size_t length = regerror (errcode, compiled, NULL, 0);
  char *buffer = xmalloc (length);
  (void) regerror (errcode, compiled, buffer, length);
  return buffer;
}

2.4. Shell 风格的词扩展

Shell-Style Word Expansion

词扩展是指将字符串拆分为词并替换变量、命令和通配符的过程,就像 shell 所做的那样。

例如,当你写‘ls -l foo.c’时,这个字符串被分成三个单独的词——‘ls’、‘-l’和‘foo.c’。这是扩词最基本的功能。

当你写‘ls .c’时,它可以变成很多词,因为‘.c’这个词可以替换为任意数量的文件名。这称为通配符扩展,也是单词扩展的一部分。

当您使用“echo $PATH”打印路径时,您正在利用变量替换,这也是单词扩展的一部分。

普通程序可以通过调用库函数wordexp来像shell一样进行词扩展。

2.4.1. 词扩展的阶段

The Stages of Word Expansion

当单词扩展应用于单词序列时,它会按照此处显示的顺序执行以下转换:

  1. 波浪号扩展:用“foo”的主目录名称替换“~foo”。
  2. 接下来,在同一步骤中应用三个不同的转换,从左到右:
    • 变量替换:环境变量被替换为诸如‘$foo’之类的引用。
    • 命令替换:诸如‘cat foo’和等价的‘$(cat foo)’之类的结构被替换为内部命令的输出。
    • 算术扩展:诸如“ ( ( (( ((x-1))”之类的构造被替换为算术计算的结果。
  3. 字段拆分:将文本细分为单词。
  4. 通配符扩展:用“.c”文件名列表替换“*.c”等结构。通配符扩展一次适用于整个单词,并将该单词替换为 0 个或多个本身就是单词的文件名。
  5. 引号删除:删除字符串引号,因为它们已经通过在适当的时候抑制上述转换来完成他们的工作。
    有关这些转换的详细信息,以及如何编写使用它们的构造,请参阅 The BASH 手册(出现)。

2.4.2. 调用wordexp

字扩展的所有函数、常量和数据类型都在头文件 wordexp.h 中声明。

词扩展产生一个词向量(字符串)。为了返回这个向量,wordexp 使用了一种特殊的数据类型 wordexp_t,它是一个结构。您将结构的地址传递给 wordexp,它会填充结构的字段以告诉您结果。

数据类型:wordexp_t

此数据类型包含一个指向词向量的指针。更准确地说,它记录了词向量的地址和它的大小。

we_wordc

向量中的元素数。

we_wordv

向量的地址。该字段的类型为 char **。

we_offs

向量的第一个实数元素从 we_wordv 字段中的标称地址的偏移量。与其他字段不同,这始终是 wordexp 的输入,而不是它的输出。

如果您使用非零偏移量,则向量开头的许多元素将保留为空。(wordexp 函数用空指针填充它们。)

仅当您使用 WRDE_DOOFFS 标志时,we_offs 字段才有意义。否则,无论该字段中有什么,偏移量始终为零,并且第一个实数元素出现在向量的开头。

函数:int wordexp (const char *words, wordexp_t *word-vector-ptr, int flags)

Preliminary: | MT-Unsafe race:utent const:env env sig:ALRM timer locale | AS-Unsafe dlopen plugin i18n heap corrupt lock | AC-Unsafe corrupt lock fd mem | See POSIX Safety Concepts.

对字符串words进行词扩展,将结果放入一个新分配的向量中,并将这个向量的大小和地址存入*word-vector-ptr。参数 flags 是位标志的组合;有关标志的详细信息,请参阅字扩展标志。

除非被引用,否则不应在字符串单词中使用任何字符“|&;<>”;同样适用于换行符。如果您使用不带引号的这些字符,您将收到 WRDE_BADCHAR 错误代码。不要使用括号或大括号,除非它们被引用或属于单词扩展结构的一部分。如果您使用引号字符’‘"`’,它们应该成对出现以保持平衡。

词扩展的结果是一个词的序列。函数 wordexp 为每个结果词分配一个字符串,然后分配一个 char ** 类型的向量来存储这些字符串的地址。向量的最后一个元素是空指针。这个向量称为词向量。

为了返回这个向量,wordexp 将它的地址和它的长度(元素的数量,不包括终止的空指针)存储到 *word-vector-ptr 中。

如果 wordexp 成功,则返回 0。否则,返回以下错误代码之一:

  • WRDE_BADCHAR

    输入字符串 words 包含不带引号的无效字符,例如“|”。

  • WRDE_BADVAL

    输入字符串引用了一个未定义的 shell 变量,并且您使用标志 WRDE_UNDEF 来禁止此类引用。

  • WRDE_CMDSUB

    输入字符串使用命令替换,并且您使用标志 WRDE_NOCMD 来禁止命令替换。

  • WRDE_NOSPACE

    分配内存来保存结果是不可能的。在这种情况下,wordexp 可以存储部分结果——尽可能多地分配空间。

  • WRDE_SYNTAX

    输入字符串中存在语法错误。例如,不匹配的引号字符是语法错误。此错误代码还用于表示除以零和算术扩展中的溢出。

函数:void wordfree (wordexp_t *word-vector-ptr)

Preliminary: | MT-Safe | AS-Unsafe corrupt heap | AC-Unsafe corrupt mem | See POSIX Safety Concepts.

释放 *word-vector-ptr 指向的词串和向量的存储空间。这不会释放结构 *word-vector-ptr 本身——只是释放它指向的其他数据。

2.4.3. 字扩展标志

Flags for Word Expansion

本节介绍您可以在 wordexp 的 flags 参数中指定的标志。选择您想要的标志,并将它们与 C 运算符 | 结合起来。

WRDE_APPEND

将此扩展中的单词附加到先前调用 wordexp 产生的单词向量中。这样,您可以有效地扩展多个单词,就好像它们之间用空格连接一样。

为了使附加工作正常,您不能在对 wordexp 的调用之间修改词向量结构的内容。而且,如果您在第一次调用 wordexp 时设置了 WRDE_DOOFFS,则在追加到结果时也必须设置它。

WRDE_DOOFFS

在单词向量的开头留下空白槽。we_offs 字段表示要离开多少个插槽。空白槽包含空指针。

WRDE_NOCMD

不要做命令替换;如果输入请求命令替换,则报告错误。

WRDE_REUSE

重用之前调用 wordexp 生成的词向量。这个对 wordexp 的调用将使用已经存在的向量,而不是分配一个新的词向量(如果需要,将其变大)。

请注意,向量可能会移动,因此保存旧指针并在调用 wordexp 后再次使用它是不安全的。您必须在每次调用后重新获取 we_pathv。

WRDE_SHOWERR

显示由命令替换运行的命令打印的任何错误消息。更准确地说,允许这些命令继承当前进程的标准错误输出流。默认情况下,wordexp 会为这些命令提供一个标准错误流,该流会丢弃所有输出。

WRDE_UNDEF

如果输入引用了未定义的 shell 变量,则报告错误。

2.4.4. wordexp示例

wordexp Example

这是一个使用 wordexp 扩展多个字符串并使用结果运行 shell 命令的示例。它还显示了使用 WRDE_APPEND 连接扩展和使用 wordfree 来释放 wordexp 分配的空间。

int
expand_and_execute (const char *program, const char **options)
{
  wordexp_t result;
  pid_t pid
  int status, i;

  /* Expand the string for the program to run.  */
  switch (wordexp (program, &result, 0))
    {
    case 0:			/* Successful.  */
      break;
    case WRDE_NOSPACE:
      /* If the error was WRDE_NOSPACE,
         then perhaps part of the result was allocated.  */
      wordfree (&result);
    default:                    /* Some other error.  */
      return -1;
    }

  /* Expand the strings specified for the arguments.  */
  for (i = 0; options[i] != NULL; i++)
    {
      if (wordexp (options[i], &result, WRDE_APPEND))
        {
          wordfree (&result);
          return -1;
        }
    }

  pid = fork ();
  if (pid == 0)
    {
      /* This is the child process.  Execute the command. */
      execv (result.we_wordv[0], result.we_wordv);
      exit (EXIT_FAILURE);
    }
  else if (pid < 0)
    /* The fork failed.  Report failure.  */
    status = -1;
  else
    /* This is the parent process.  Wait for the child to complete.  */
    if (waitpid (pid, &status, 0) != pid)
      status = -1;

  wordfree (&result);
  return status;
}

2.4.5. 波浪号扩展细节

Details of Tilde Expansion

这是 shell 语法的标准部分,您可以在文件名的开头使用“”来代表您自己的主目录。您可以使用“user”代表用户的主目录。

波浪号扩展是将这些缩写转换为它们所代表的目录名称的过程。

波浪号扩展适用于“~”以及后面的所有字符,直到空格或斜杠。它仅发生在单词的开头,并且仅在没有以任何方式引用要转换的字符时发生。

普通的“~”使用环境变量 HOME 的值作为正确的主目录名称。‘~’ 后跟用户名使用 getpwname 在用户数据库中查找该用户,并使用那里记录的任何目录。因此,如果 HOME 的值不是您的主目录,则“”后跟您自己的名字可以给出与普通“”不同的结果。

2.4.6. 变量替换细节

Details of Variable Substitution

普通 shell 语法的一部分是使用“$variable”将 shell 变量的值替换为命令。这称为变量替换,它是进行单词扩展的一部分。

有两种基本方法可以编写用于替换的变量引用:

  • ${variable}

    如果你在变量名周围写上大括号,那么变量名的结尾是完全明确的。您可以通过在右大括号后立即写入其他字母来将其他字母连接到变量值的末尾。例如,“${foo}s”扩展为“拖拉机”。

  • $variable

    如果您没有在变量名周围加上大括号,则变量名由所有字母数字字符和“ ” 后 面 的 下 划 线 组 成 。 下 一 个 标 点 字 符 结 束 变 量 名 。 因 此 , ′ ”后面的下划线组成。下一个标点字符结束变量名。因此,' 线foo-bar’ 引用变量 foo 并扩展为’tractor-bar’。

当您使用大括号时,您还可以使用各种构造来修改被替换的值,或者以各种方式对其进行测试。

  • ${variable:-default}

    替换变量的值,但如果它为空或未定义,请改用默认值。

  • ${variable:=default}

    替换变量的值,但如果它为空或未定义,请改用默认值并将变量设置为默认值。

  • ${variable:?message}

    如果变量已定义且不为空,则替换其值。

    否则,将 message 作为标准错误流上的错误消息打印,并认为单词扩展失败。

  • ${variable:+replacement}

    替换替换,但前提是变量已定义且非空。否则,不要用任何东西代替这个构造。

  • ${#variable}

    替换一个数字,该数字以十进制表示变量值中的字符数。‘${#foo}’代表‘7’,因为‘tractor’是七个字符。

    这些变量替换的变体允许您在替换之前删除变量的部分值。前缀和后缀不仅仅是字符串;它们是通配符模式,就像用于匹配多个文件名的模式一样。但在这种情况下,它们匹配变量值的一部分而不是文件名。

  • ${variable%%suffix}

    替换变量的值,但首先从该变量中丢弃与模式后缀匹配的任何部分。

    如果对于如何匹配后缀有不止一种选择,则此构造使用可能的最长匹配。

    因此,‘${foo%%r*}’ 替换了’t’,因为在’tractor’ 末尾与’r*’ 的最大匹配是’ractor’。

  • ${variable%suffix}

    替换变量的值,但首先从该变量中丢弃与模式后缀匹配的任何部分。

    如果对于如何匹配后缀有多个替代方案,则此构造使用最短的替代方案。

    因此,‘${foo%r*}’ 替代了 ‘tracto’,因为在 ‘tractor’ 末尾与 ‘r*’ 的最短匹配只是 ‘r’。

  • ${variable##prefix}

    替换变量的值,但首先从该变量中丢弃与模式前缀匹配的开头的任何部分。

    如果对于如何匹配前缀有多个替代方案,则此构造使用可能的最长匹配。

    因此,‘${foo##*t}’ 替代了’or’,因为在’tractor’ 开头的’*t’ 的最大匹配是’tract’。

  • ${variable#prefix}

    替换变量的值,但首先从该变量中丢弃与模式前缀匹配的开头的任何部分。

    如果对于如何匹配前缀有多个替代方案,则此构造使用最短的替代方案。

    因此,‘${foo#*t}’ 替代了’ractor’,因为在’tractor’ 开头的’*t’ 的最短匹配就是’t’。

3. 参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值