6、Linux 系统编程:管道友好程序、结果重定向与环境变量读取

Linux 系统编程:管道友好程序、结果重定向与环境变量读取

编写管道友好程序

在处理数据时,我们常常需要编写能与其他程序通过管道连接的程序,这样可以将多个小工具组合起来完成复杂的任务。下面我们将详细介绍如何编写一个管道友好的程序,将英里每小时(mph)转换为公里每小时(kph)。

示例数据与问题处理

假设我们有一个文件 avg.txt ,其中包含一些英里每小时的数据,但最后一个值是 nn ,这是一个非数值,属于测量错误。我们不希望在输出中显示错误信息,因此将标准错误输出(stderr)重定向到 /dev/null 。以下是具体的操作步骤:

$> (cat avg.txt | awk '{ print $3 }' | \
> ./mph-to-kph) 2> /dev/null

执行上述命令后,会得到干净的公里每小时输出:

98.2
88.5
86.9
83.7
83.7

为了让输出更清晰,我们希望在每行末尾添加 km/h 。可以使用 sed 来实现:

$> (cat avg.txt | awk '{ print $3 }' | \
> ./mph-to-kph) 2> /dev/null | sed 's/$/ km\/h/'

输出结果如下:

98.2 km/h
88.5 km/h
86.9 km/h
83.7 km/h
83.7 km/h
程序工作原理

这个程序与之前的类似,但增加了检查输入数据是否为数值的功能。如果输入不是数值,程序会终止并将错误信息打印到标准错误输出(stderr)。正常输出仍会打印到标准输出(stdout)。
以下是检查输入是否为数值的代码:

if( strspn(mph, "0123456789.-\n") == strlen(mph) )

strspn() 函数会读取指定字符集中的字符,并返回读取的字符数。我们将其与字符串的总长度进行比较,如果相等,则说明字符串中的每个字符都是数字、点、减号或换行符。为了使用 strspn() strlen() 函数,我们需要包含 <string.h> 头文件;为了使用 atof() 函数,需要包含 <stdlib.h> 头文件。

数据管道操作流程

整个数据处理过程可以用以下流程图表示:

graph LR
    A[avg.txt] --> B(cat)
    B --> C(awk '{ print $3 }')
    C --> D(./mph-to-kph)
    D -->|正常输出| E(sed 's/$/ km\/h/')
    D -->|错误输出| F[/dev/null]
    E --> G[最终输出]

具体步骤如下:
1. 使用 awk 程序选择第三列(英里每小时的值)。
2. 将 awk 程序的输出重定向到 mph-to-kph 程序。
3. 将错误信息重定向到 /dev/null ,使输出更干净。
4. 使用 sed 程序在每行末尾添加 km/h

结果重定向到文件

在实际应用中,我们可能需要将程序的输出和错误信息分别保存到不同的文件中。下面我们将改进之前的 mph-to-kph 程序,使其在遇到非数值时可以选择继续运行。

程序改进思路

之前的 mph-to-kph 程序在遇到非数值字符时会停止运行。为了让程序在处理长输入数据时更具灵活性,我们将添加一个选项 -c ,使程序在检测到非数值时可以继续运行。

代码实现步骤

以下是改进后的程序 mph-to-kph_v2.c 的详细实现步骤:
1. 添加特征宏和头文件

#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
  1. 添加帮助函数原型和 main() 函数
void printHelp(FILE *stream, char progname[]);
int main(int argc, char *argv[])
{
    char mph[10] = { 0 };
    int opt;
    int cont = 0;
  1. 解析命令行选项
    while ((opt = getopt(argc, argv, "ch")) != -1)
    {
        switch(opt)
        {
            case 'h':
                printHelp(stdout, argv[0]);
                return 0;
            case 'c':
                cont = 1;
                break;
            default:
                printHelp(stderr, argv[0]);
                return 1;
        }
    }
  1. 读取输入数据并处理
    while(fgets(mph, sizeof(mph), stdin) != NULL)
    {
        if( strspn(mph, "0123456789.-\n") == strlen(mph) )
        {
            printf("%.1f\n", (atof(mph)*1.60934) );
        }
        else
        {
            fprintf(stderr, "Found non-numeric value\n");
            if (cont == 1)
            {
                continue;
            }
            else
            {
                return 1;
            }
        }
    }
    return 0;
}
  1. 编写帮助函数
void printHelp(FILE *stream, char progname[])
{
    fprintf(stream, "%s [-c] [-h]\n", progname);
    fprintf(stream, " -c continues even though a non-numeric value was detected in the input\n"
                    " -h print help\n");
}
编译和测试程序

使用 make 编译程序:

$> make mph-to-kph_v2
cc     mph-to-kph_v2.c   -o mph-to-kph_v2

不使用选项运行程序:

$> ./mph-to-kph_v2 
60
96.6
40
64.4
hello
Found non-numeric value

使用 -c 选项运行程序:

$> ./mph-to-kph_v2 -c
50
80.5
90
144.8
hello
Found non-numeric value
10
16.1
20
32.2

可以看到,使用 -c 选项后,程序在遇到非数值时会继续运行。

处理包含垃圾数据的文件

我们准备一个包含更多非数值的文件 avg-with-garbage.txt ,并使用 awk 提取第三列的值:

$> cat avg-with-garbage.txt | awk '{ print $3 }'
61
55
54
52
52
nn
49
47
nn
data
43

然后使用 -c 选项运行 mph-to-kph_v2 程序:

$> cat avg-with-garbage.txt | awk '{ print $3 }' \
> | ./mph-to-kph_v2 -c
98.2
88.5
86.9
83.7
83.7
Found non-numeric value
78.9
75.6
Found non-numeric value
Found non-numeric value
69.2

最后,将标准输出和标准错误输出分别重定向到不同的文件:

$> (cat avg-with-garbage.txt | awk '{ print $3 }' \
> | ./mph-to-kph_v2 -c) 2> errors.txt 1> output.txt

查看两个文件的内容:

$> cat output.txt 
98.2
88.5
86.9
83.7
83.7
78.9
75.6
69.2
$> cat errors.txt 
Found non-numeric value
Found non-numeric value
Found non-numeric value
读取环境变量

除了通过命令行选项和文件进行配置外,我们还可以通过环境变量与 shell 进行通信并配置程序。环境变量包含了用户和系统设置的各种信息,如用户名、主目录、首选编辑器等。

编写读取环境变量的程序

以下是一个简单的 C 程序 env-var.c ,用于读取一些常见的环境变量并打印相关信息:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    printf("Your username is %s\n", getenv("USER"));
    printf("Your home directory is %s\n", getenv("HOME"));
    printf("Your preferred editor is %s\n", getenv("EDITOR"));
    printf("Your shell is %s\n", getenv("SHELL"));
    if ( strstr(getenv("TERM"), "256color")  )
    {
        printf("\033[0;31mYour \033[0;32mterminal "
               "\033[0;35msupport "
               "\033[0;33mcolors\033[0m\n");
    }
    else
    {
        printf("Your terminal doesn't support colors\n");
    }
    return 0;
}
编译和运行程序

使用 gcc 编译程序:

$> gcc env-var.c -o env-var

运行程序:

$> ./env-var 
Your username is jake
Your home directory is /home/jake
Your preferred editor is vim
Your shell is /bin/bash
Your terminal support colors
查看和修改环境变量

我们可以使用 echo 命令查看环境变量的值:

$> echo $USER
jake
$> echo $HOME
/home/jake
$> echo $EDITOR
vim
$> echo $SHELL
/bin/bash
$> echo $TERM
screen-256color

如果将 $TERM 变量修改为不支持颜色的 xterm ,程序的输出会相应改变:

$> export TERM=xterm
$> ./env-var 
Your username is jake
Your home directory is /home/jake
Your preferred editor is vim
Your shell is /bin/bash
Your terminal doesn't support colors

在程序运行结束后,我们可以将 $TERM 变量恢复到原来的值:

$> export TERM=screen-256color

我们还可以临时设置环境变量:

$> echo $TERM
xterm-256color
$> TERM=xterm ./env-var
Your username is jake
Your home directory is /home/jake
Your preferred editor is vim
Your shell is /bin/bash
Your terminal doesn't support colors
$> echo $TERM
xterm-256color

使用 env 命令可以打印所有环境变量的列表:

$> env
环境变量操作总结
操作 命令示例 说明
查看环境变量 echo $VAR 查看名为 VAR 的环境变量的值
设置环境变量 export VAR=value VAR 环境变量的值设置为 value ,使其成为全局变量
临时设置环境变量 VAR=value ./program 在运行 program 时临时将 VAR 的值设置为 value
查看所有环境变量 env 打印所有环境变量的列表

通过以上步骤,我们学习了如何编写管道友好的程序、将程序的输出和错误信息重定向到不同的文件,以及如何读取和修改环境变量。这些技巧可以帮助我们更好地编写和配置 Linux 系统程序。

Linux 系统编程:管道友好程序、结果重定向与环境变量读取

环境变量的进一步操作

在 C 程序中,我们还可以使用 setenv() 函数来改变环境变量或创建新的环境变量。不过需要注意的是,在子进程中设置的环境变量不会影响到父进程(即启动该程序的 shell)。

以下是一个使用 setenv() 函数的示例程序 env-var-set.c

#define _POSIX_C_SOURCE 200112L
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    setenv("FULLNAME", "Jack-Benny", 1);
    printf("Your full name is %s\n", getenv("FULLNAME"));
    return 0;
}

编译并运行该程序:

$> gcc env-var-set.c -o env-var-set
$> ./env-var-set 
Your full name is Jack-Benny

尝试从 shell 中读取 $FULLNAME 变量:

$> echo $FULLNAME

可以看到,在 shell 中该变量并不存在。这是因为子进程无法改变父进程的环境变量,但在子进程中启动的其他程序可以看到这些变量。

程序设计的规则

在开发 Linux 和 Unix 软件时,有一些优秀的规则值得遵循,例如 Eric S. Raymond 在《The Art of Unix Programming》中提到的规则。

模块化规则

模块化规则要求我们编写简单的部分,并通过清晰的接口将它们连接起来。在我们编写的程序中,每个工具都只负责一项特定的任务,例如 awk 负责提取字段, mph-to-kph 负责单位转换, sed 负责添加文本。这样的设计使得每个工具都可以独立开发、测试和维护,并且可以方便地与其他工具组合使用。

组合规则

组合规则强调编写可以与其他程序连接的程序。我们通过管道将多个程序连接起来,实现了复杂的数据处理任务。例如,将 awk mph-to-kph sed 组合在一起,完成了从文件中提取数据、进行单位转换并添加文本的操作。这种方式使得程序具有更高的灵活性和可扩展性。

总结与最佳实践

通过以上的学习,我们掌握了编写管道友好程序、结果重定向和读取环境变量的方法。以下是一些总结和最佳实践:

编写管道友好程序
  • 确保程序只输出必要的信息,避免输出无关的提示或错误信息,以便于与其他程序组合使用。
  • 对输入数据进行严格的验证,确保程序在遇到异常数据时能够正确处理,避免程序崩溃。
结果重定向
  • 将标准输出和标准错误输出分开处理,将错误信息重定向到日志文件,将正常输出用于后续处理,这样可以保证输出的干净和可处理性。
  • 在处理长输入数据时,考虑添加选项让程序在遇到错误时可以选择继续运行,提高程序的健壮性。
环境变量的使用
  • 利用环境变量来配置程序,使程序能够根据用户的环境进行自适应调整。
  • 注意环境变量的作用域,区分局部变量和全局变量,避免在子进程中修改父进程的环境变量时出现混淆。
操作步骤回顾

以下是本文中涉及的主要操作步骤的回顾:

编写管道友好程序
graph LR
    A[avg.txt] --> B(cat)
    B --> C(awk '{ print $3 }')
    C --> D(./mph-to-kph)
    D -->|正常输出| E(sed 's/$/ km\/h/')
    D -->|错误输出| F[/dev/null]
    E --> G[最终输出]
  1. 使用 awk 选择第三列数据。
  2. awk 输出传递给 mph-to-kph 进行单位转换。
  3. 重定向错误信息到 /dev/null
  4. 使用 sed 添加 km/h 文本。
结果重定向到文件
graph LR
    A[avg-with-garbage.txt] --> B(cat)
    B --> C(awk '{ print $3 }')
    C --> D(./mph-to-kph_v2 -c)
    D -->|正常输出| E(output.txt)
    D -->|错误输出| F(errors.txt)
  1. 准备包含垃圾数据的文件。
  2. 使用 awk 提取第三列数据。
  3. 使用 -c 选项运行 mph-to-kph_v2 程序。
  4. 将标准输出和标准错误输出分别重定向到不同的文件。
读取环境变量
  1. 编写 env-var.c 程序读取环境变量。
  2. 编译并运行程序。
  3. 使用 echo 查看环境变量。
  4. 修改和临时设置环境变量。
  5. 使用 env 查看所有环境变量。

通过遵循这些步骤和最佳实践,我们可以编写出更加健壮、灵活和易于维护的 Linux 系统程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值