42、UNIX系统编程中的杂项函数与实用技巧

UNIX系统编程中的杂项函数与实用技巧

在UNIX系统编程里,有许多实用的杂项函数和工具,能帮助开发者更高效地完成各种任务。下面将详细介绍这些函数和工具。

1. 密码输入与验证

在UNIX系统中, getpass 函数用于安全地获取用户输入的密码。它会打印提示信息,关闭终端的字符回显,读取密码,最后恢复终端模式。需要注意的是,该函数会将输入的密码截断为最多8个字符。

#include <stdlib.h>

char *getpass(const char *prompt);

密码验证的过程通常如下:程序在提示用户输入密码后,会在密码文件或影子密码文件中查找用户的密码(如果使用影子密码文件,程序必须以超级用户权限运行)。接着,将用户输入的值与盐值一起传递给 crypt 函数,并将结果与从密码文件中获取的值进行比较。若两者相同,则用户密码正确。示例代码如下:

#include <stdlib.h>
#include <crypt.h>

char *typed, *encrypted;

// 假设这里获取加密后的密码
encrypted = /* obtain the encrypted password */;
typed = getpass("Password: ");

if (strcmp(crypt(typed, encrypted), encrypted) == 0)
    // 密码正确
    /* okay... */
else
    // 密码错误
    /* not okay... */
2. 随机数生成

许多应用程序偶尔需要一个或多个随机数。所有版本的UNIX都提供了伪随机数生成器,主要包含 rand srand 两个函数。

#include <stdlib.h>

int rand(void);
void srand(int seed);

在请求随机数之前,需要调用 srand 函数为生成器设置种子。种子参数应为整数值, getpid time(0) 的输出通常是不错的选择。每次使用相同的种子调用 srand 函数时,随机数生成器的输出将保持一致。 rand 函数会返回一个范围在0到215 - 1之间的随机数。

部分基于BSD的UNIX版本还提供了 random srandom 函数,它们具有相似的语义。而System V版本的UNIX在 drand48 手册页中描述了其他一些随机数生成器,但由于它们在所有操作系统版本中不具备可移植性,因此使用频率较低。

3. 目录树遍历

SVR4提供了三个用于遍历目录树的函数,分别是 ftw nftw pathfind

3.1 ftw函数

ftw 函数会递归地遍历以 path 为根的目录层次结构。对于目录中的每个对象,它会调用用户定义的函数 fn 。该函数接受三个参数:对象的名称、指向 struct stat 结构的指针以及一个标志。标志的可能取值如下:
| 标志 | 含义 |
| ---- | ---- |
| FTW_F | 对象是一个文件 |
| FTW_D | 对象是一个目录 |
| FTW_DNR | 对象是一个无法读取的目录,该目录的子目录将不会被处理 |
| FTW_NS | 对对象调用 stat 函数失败,可能是由于权限问题或它是一个指向不存在文件的符号链接, struct stat 结构的内容未定义 |

ftw 函数的最后一个参数 depth 是对 ftw 可能使用的文件描述符数量的限制,树的每一层需要一个文件描述符。遍历会在访问目录的子目录之前先访问该目录(即调用 fn 函数)。遍历会持续进行,直到 fn 函数返回非零值或发生错误。如果遍历完整个树, ftw 将返回0;如果 fn 返回非零值, ftw 将停止遍历并返回该值。

以下是一个使用 ftw 函数的示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <ftw.h>

int process(const char *, const struct stat *, int);

int
main(int argc, char **argv)
{
    while (--argc) {
        printf("Directory %s:\n", *++argv);

        ftw(*argv, process, sysconf(_SC_OPEN_MAX) - 3);

        putchar('\n');
    }

    exit(0);
}

int
process(const char *path, const struct stat *st, int flag)
{
    printf("%-24s", path);

    switch (flag) {
    case FTW_F:
        printf("file, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_D:
        printf("directory, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_DNR:
        printf("unreadable directory, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_NS:
        printf("unknown; stat() failed\n");
        break;
    }

    return(0);
}
3.2 nftw函数

nftw 函数与 ftw 函数类似,但它多了一个 flags 参数,该参数可以指定以下值的按位或组合:
| 标志 | 含义 |
| ---- | ---- |
| FTW_PHYS | 进行“物理”遍历,不跟随符号链接,默认情况下, nftw 会跟随符号链接 |
| FTW_MOUNT | 不跨越文件系统挂载点 |
| FTW_DEPTH | 进行深度优先搜索,先访问目录的子目录,再访问该目录本身 |
| FTW_CHDIR | 在读取每个目录之前切换到该目录 |

fn 函数还多了一个 struct FTW 类型的参数,该结构包含两个字段: base 字段包含文件名在路径名参数中的偏移量, level 字段包含当前在树中的层级。 nftw 函数还允许向 fn 传递另外两个标志:
| 标志 | 含义 |
| ---- | ---- |
| FTP_DP | 对象是一个其子目录已经被访问过的目录 |
| FTW_SL | 对象是一个指向不存在文件的符号链接 |

以下是一个使用 nftw 函数的示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <ftw.h>

int process(const char *, const struct stat *, int, struct FTW *);

int
main(int argc, char **argv)
{
    while (--argc) {
        printf("Directory %s:\n", *++argv);

        nftw(*argv, process, sysconf(_SC_OPEN_MAX) - 3, 0);

        putchar('\n');
    }

    exit(0);
}

int
process(const char *path, const struct stat *st, int flag, struct FTW *info)
{
    int i;

    for (i = 0; i < info->level; i++)
        printf("  ");

    printf("%-*s", 36 - 2 * info->level, &path[info->base]);

    switch (flag) {
    case FTW_F:
        printf("file, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_D:
    case FTW_DP:
        printf("directory, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_SL:
        printf("symbolic link to nowhere\n");
        break;
    case FTW_DNR:
        printf("unreadable directory, mode %o\n", st->st_mode & 07777);
        break;
    case FTW_NS:
        printf("unknown; stat() failed\n");
        break;
    }

    return(0);
}
3.3 pathfind函数

pathfind 函数类似于 find 命令的库实现,也可以使用 ftw nftw 函数轻松实现。要使用 pathfind 函数,程序必须链接 -lgen 库。

#include <libgen.h>

char *pathfind(const char *path, const char *name, const char *mode);

pathfind 函数会在 path 中以分号分隔的目录中搜索名为 name 且模式匹配 mode 的文件。 mode 参数是一个包含以下一个或多个字符的字符串:
| 字符 | 含义 |
| ---- | ---- |
| r | 对象可被用户读取 |
| w | 对象可被用户写入 |
| x | 对象可被用户执行 |
| f | 对象是一个普通文件 |
| b | 对象是一个块特殊设备文件 |
| c | 对象是一个字符特殊设备文件 |
| d | 对象是一个目录 |
| p | 对象是一个FIFO(管道) |
| u | 对象设置了set-user-id位 |
| g | 对象设置了set-group-id位 |
| k | 对象设置了“粘滞”位 |
| s | 对象的大小不为零 |

如果找到符合要求的项, pathfind 将返回 path name 的拼接结果;如果未找到对象, pathfind 将返回 NULL 。以下是一个使用 pathfind 函数的示例:

#include <stdlib.h>
#include <libgen.h>

int
main(int argc, char **argv)
{
    char *p, *path;

    if ((path = getenv("PATH")) == NULL) {
        fprintf(stderr, "cannot find path in environment.\n");
        exit(1);
    }

    while (--argc) {
        if ((p = pathfind(path, *++argv, "x")) == NULL)
            printf("%s: not found in search path.\n", *argv);
        else
            printf("%s: %s\n", *argv, p);
    }

    exit(0);
}
4. 数据库管理

大多数版本的UNIX提供了一个库来维护基本的数据库。这个数据库基本上是一个磁盘上的哈希表,设计目的是提高效率。这些例程可以处理非常大的数据库(最多可达十亿个块),并且只需要一两次文件系统访问就能检索一个项。虽然在大多数SVR4版本中不是必需的,但HP-UX 10.x需要链接 -lndbm 库才能使用这些函数。

#include <ndbm.h>

DBM *dbm_open(char *file, int flags, int mode);
void dbm_close(DBM *db);
int dbm_store(DBM *db, datum key, datum content, int flags);
datum dbm_fetch(DBM *db, datum key);
int dbm_delete (DBM *db, datum key);
datum dbm_firstkey(DBM *db);
datum dbm_nextkey(DBM *db);
int dbm_clearerr(DBM *db);
int dbm_error(DBM *db);

在使用其他函数之前,必须使用 dbm_open 函数打开数据库。数据库存储在两个文件中,一个后缀为 .dir ,另一个后缀为 .pag 。应将文件的根名称(不包含后缀)作为 file 参数传递给 dbm_open flags mode 参数与 open 函数的参数相同。如果成功, dbm_open 将返回一个指向 DBM 类型的指针;否则返回 NULL 。可以使用 dbm_close 函数关闭数据库。

键和内容使用 datum 类型的对象来描述:

typedef struct {
    char    *dptr;
    int      dsize;
} datum;

dptr 字段指向数据, dsize 字段表示数据的大小。需要注意的是,键和内容可以是任意数据类型。

可以通过调用 dbm_store 函数将项存储在数据库中。 db 参数是指向已打开数据库的指针。 key 参数是存储 content 参数中数据的键。 flags 参数可以是以下值之一:
| 标志 | 含义 |
| ---- | ---- |
| DBM_INSERT | 将项插入数据库。如果数据库中已经存在具有此键的项,不将其替换为新值。如果找到现有条目, dbm_store 返回1,否则返回0 |
| DBM_REPLACE | 将项插入数据库。如果数据库中已经存在具有此键的项,将其替换为新值 |

要从数据库中检索项,可以使用 dbm_fetch 函数。 db 参数指定已打开的数据库, key 参数给出项的键。该键的内容将作为 datum 类型返回;需要注意的是,返回的是结构本身,而不是指向结构的指针。如果未找到该键的项,则 datum 结构的 dptr 字段将为 NULL

可以使用 dbm_delete 函数从 db 引用的数据库中删除具有键 key 的项。

可以使用 dbm_firstkey dbm_nextkey 函数线性遍历数据库中的所有键,示例如下:

#include <ndbm.h>

// ...

for (key = dbm_firstkey(db); key.dptr != NULL; key = dbm_nextkey(db)) {
    // ...
    content = dbm_fetch(db, key);
    // ...
}

// ...

dbm_error 函数在读取或写入 db 引用的数据库时发生错误时返回非零值; dbm_clearerr 函数清除错误条件。

一些特别旧的UNIX版本可能只提供 -lndbm 库的前身 -ldbm 库。这个版本的库使用相同名称的函数,但没有前导的 dbm_ 。它们不接受 db 参数,并且一次只能处理一个打开的数据库。将这些函数替换为较新的函数很简单。

下面用mermaid流程图展示数据库操作的基本流程:

graph TD
    A[打开数据库: dbm_open] --> B{操作类型}
    B -->|存储| C[dbm_store]
    B -->|检索| D[dbm_fetch]
    B -->|删除| E[dbm_delete]
    B -->|遍历| F[dbm_firstkey和dbm_nextkey]
    C --> G[关闭数据库: dbm_close]
    D --> G
    E --> G
    F --> G

通过上述介绍,我们了解了UNIX系统编程中一些重要的杂项函数和工具,包括密码输入与验证、随机数生成、目录树遍历和数据库管理等。这些函数和工具为开发者提供了强大的功能,帮助他们更高效地完成各种任务。

UNIX系统编程中的杂项函数与实用技巧

5. 模式匹配

在UNIX系统里,很多shell和文本编辑器都支持用户使用单个字符串来匹配大量的项,为了方便开发者实现这类功能,系统提供了相应的库函数。

5.1 shell模式匹配

shell 中的模式匹配(也称为通配符匹配)主要用于生成文件名列表。在 shell 模式中,部分字符具有特殊含义:
| 字符 | 含义 |
| ---- | ---- |
| * | 匹配任意字符串,包括空字符串 |
| ? | 匹配任意单个字符 |
| [] | 匹配方括号内的任意一个字符。两个用 - 分隔的字符匹配这两个字符之间的任意一个字符(例如 [a - z] 匹配 a z 之间的任意字符)。如果方括号内的第一个字符是 ! ,则匹配除方括号内字符之外的任意字符 |

这些特殊字符(也称为元字符)可以用反斜杠进行转义,例如 \? 匹配实际的问号字符。

在程序中,可以使用 gmatch 函数进行 shell 模式匹配,该函数包含在 -lgen 库中:

#include <libgen.h>

int gmatch(const char *str, const char *pattern);

gmatch 函数在 pattern 中的 shell 模式匹配 str 中的字符串时返回非零值;不匹配时返回 0。需要注意的是, gmatch 不支持 C - shell 提供的额外模式匹配字符,如 {}

在 HP - UX 10.x 中, gmatch 函数不可用,但可以使用 fnmatch 函数来模拟 gmatch

int
gmatch(const char *str, const char *pattern)
{
    return(!fnmatch(patter, str, 0));
}

以下是一个使用 gmatch 函数的示例,该程序会在指定文件中搜索与指定模式匹配的行:

#include <libgen.h>
#include <stdio.h>

int
main(int argc, char **argv)
{
    FILE *fp;
    char line[BUFSIZ];
    char *pattern, *filename;

    // 检查参数
    if (argc != 3) {
        fprintf(stderr, "Usage: %s pattern file\n", *argv);
        exit(1);
    }

    pattern = *++argv;
    filename = *++argv;

    // 打开文件
    if ((fp = fopen(filename, "r")) == NULL) {
        perror(filename);
        exit(1);
    }

    // 从文件中读取行
    while (fgets(line, sizeof(line), fp) != NULL) {
        // 去除换行符
        line[strlen(line) - 1] = '\0';

        // 如果匹配,打印该行
        if (gmatch(line, pattern) != 0)
            puts(line);
    }

    fclose(fp);
    exit(0);
}
5.2 正则表达式

正则表达式通过使用特殊字符来指定一组字符串。大多数文本编辑器和 grep 系列命令都支持正则表达式。在 ed 文本编辑器中,正则表达式的定义如下:
- 单个字符(除特殊字符外)是一个匹配自身的单字符正则表达式。
- 反斜杠后面跟特殊字符会使该字符失去其特殊含义。
- 点号( . )是一个匹配任意单个字符的单字符正则表达式。
- 方括号( [ ] )内的字符串是一个匹配该字符串中任意单个字符的单字符正则表达式。如果字符串的第一个字符是 ^ ,则该字符串是一个匹配不在该字符串中的任意单个字符的正则表达式。 ^ 只有在字符串的第一个字符位置才有特殊含义。
- 在方括号内,连字符( - )可用于指定字符范围,如 [0 - 9] [0123456789] 匹配相同的内容。如果连字符是字符串的第一个(紧跟 ^ 之后)或最后一个字符,则它失去其特殊含义。
- 右方括号( ] )只有在作为字符串的第一个字符时才能包含在方括号内。
- 其他特殊字符在方括号内没有特殊含义。
- 正则表达式可以连接在一起形成更大的正则表达式。
- 以 ^ 开头的正则表达式只能在一行的开头匹配。
- 以 $ 结尾的正则表达式只能在一行的结尾匹配。
- 以 ^ 开头并以 $ 结尾的正则表达式只能匹配整行。
- 后跟星号( * )的正则表达式匹配该正则表达式的零次或多次出现。例如, ab*c 匹配 ac abc abbc 等。当有多种选择时,将选择最左边的最长匹配。
- 包含在 \( \) 之间的正则表达式匹配与未包含括号的正则表达式相同的字符串。
- 正则表达式 \n 匹配同一正则表达式中第 n 个包含在 \( \) 之间的正则表达式所匹配的相同字符串。例如, \(abc\)\1 匹配字符串 abcabc
- 后跟 \{m\} 的正则表达式匹配该正则表达式的恰好 m 次出现;后跟 \{m,\} 匹配至少 m 次出现;后跟 \{m,n\} 匹配至少 m 次且不超过 n 次出现。此表示法最初在 PWB UNIX 中引入,并随后进入 System V。非 PWB UNIX 派生的 UNIX 版本(如基于 Berkeley 的版本)不支持此表示法。
- 以 \< 开头的正则表达式只能在一行的开头或跟在非数字、下划线或字母的字符后面匹配;以 \> 结尾的正则表达式只能在一行的结尾或在非数字、下划线或字母的字符之前匹配。此表示法允许正则表达式限制为匹配单词。SVR4 之前的 ed 版本不支持此表示法。

在程序中使用正则表达式的基本函数是 regcmp regex

#include <libgen.h>

char *regcmp(const char *str1, /* const char *str2 */, ... , NULL);
char *regex(const char *re, const char *str, /* char *ret0 */, ...);

extern char *__loc1;

regcmp 函数会编译由其连接参数组成的正则表达式,并返回指向编译形式的指针。用于存储编译形式的内存是使用 malloc 分配的,用户有责任在不再需要时释放该内存。如果某个参数包含错误, regcmp 将返回 NULL

regex 函数将编译后的正则表达式 re 应用于 str 中的字符串。可以提供额外的参数来接收返回值。如果模式匹配,将返回 str 中下一个未匹配字符的指针,并且外部字符指针 __loc1 将指向匹配开始的位置。如果模式不匹配, regex 将返回 NULL

HP - UX 10.x 需要链接 -lPW 库才能使用这些函数。 regcmp regex 使用的正则表达式与上述描述的略有不同:
- 美元符号( $ )匹配字符串的结尾; \n 匹配换行符。
- 后跟加号( + )的正则表达式匹配该正则表达式的一次或多次出现。
- 花括号表示法不使用反斜杠来转义花括号。例如, ed 使用 \{m\} ,而 regcmp regex 使用 {m}
- ed 中的括号表示法( \(...\) )已被替换为:
- (...)$n :匹配正则表达式的字符串部分将被返回。该值将存储在调用 regex str 之后的第 (n + 1) 个参数所指向的字符串中。最多可以返回十个字符串。
- (...) :括号用于分组。 * + {} 运算符可以作用于单个字符或括号内的正则表达式。

SVR4 还提供了第二组用于实现正则表达式的函数,即 compile advance step 。这些函数实现的正则表达式与 ed grep 中的相同,但它们的用法复杂,并且由于在其他操作系统版本中不可用,因此不具备可移植性。更多信息请参考 regexpr(5) 手册页。

以下是一个使用正则表达式搜索文件的示例,类似于 grep 命令:

#include <libgen.h>
#include <stdio.h>

int
main(int argc, char **argv)
{
    FILE *fp;
    char line[BUFSIZ];
    char *re, *pattern, *filename;

    // 检查参数
    if (argc != 3) {
        fprintf(stderr, "Usage: %s pattern file\n", *argv);
        exit(1);
    }

    pattern = *++argv;
    filename = *++argv;

    // 编译正则表达式
    if ((re = regcmp(pattern, NULL)) == NULL) {
        fprintf(stderr, "bad regular expression.\n");
        exit(1);
    }

    // 打开文件
    if ((fp = fopen(filename, "r")) == NULL) {
        perror(filename);
        exit(1);
    }

    // 从文件中读取行
    while (fgets(line, sizeof(line), fp) != NULL) {
        // 去除换行符
        line[strlen(line) - 1] = '\0';

        // 如果匹配,打印该行
        if (regex(re, line) != NULL)
            puts(line);
    }

    fclose(fp);
    exit(0);
}

下面用 mermaid 流程图展示正则表达式匹配的基本流程:

graph TD
    A[输入正则表达式和待匹配字符串] --> B[编译正则表达式: regcmp]
    B --> C{编译是否成功}
    C -->|是| D[应用正则表达式: regex]
    C -->|否| E[输出错误信息]
    D --> F{是否匹配}
    F -->|是| G[输出匹配结果]
    F -->|否| H[无匹配结果]

综上所述,UNIX 系统编程中的这些杂项函数和工具在不同的场景下发挥着重要作用。无论是安全地处理用户密码、生成随机数、遍历目录树、管理数据库,还是进行模式匹配,它们都为开发者提供了强大而便捷的功能。通过合理运用这些函数,开发者能够更加高效地完成各种复杂的编程任务,提升程序的性能和安全性。

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参调度等方面的有效性,为低碳能源系统的设计运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行二次开发仿真验证。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值