C语言(Head First C)-4_1:创建小工具:标准输入输出和重定向

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 4_1 创建小工具

 

 创建小工具:做一件事并把它做好

 操作系统都有小工具,C语言小工具执行特定的小任务,如读写文件,过滤数据;

 完成复杂工作,可以使用多个工具;

 

 如何构建小工具?

 接下来我们学习创建小工具的基本要素并学会控制命令行选项、操纵信息流、重定向,快速建立自己的小工具;

 

 有如下场景:我们传入了地图的GPS数据,但是现有的程序无法识别;程序的某个模块需要转化数据的格式,这样的任务用小工具来完成再合适不过了;

 

 示例:从命令行读取逗号分隔的数据(比如说是GPS数据),然后以json格式显示:

 (Code4_1)

/*
 * 从命令行读取逗号分隔的数据,然后以json格式显示
 */

#include <stdio.h>

int main() {
    
    float latitude;
    float longitude;
    char info[80];
    int started = 0;
    
    puts("data=[");
    //我们用scanf()输入多条数据
    //scanf()总是接收地址参数
    //scanf()函数返回成功读取的数据条数
    while (scanf("%f,%f,%79[^\n]",&latitude,&longitude ,info ) == 3) {//[^\n]相当于在说:把这一行余下的字符都给我
        if (started) {
            printf(",\n");//如果已经显示了一行数据 就用逗号把他隔开
        }else{
            started = 1;//循环执行一遍之后 置1 表示真
        }
        printf("latitude:%f,longitude:%f,info:'%s'",latitude,longitude,info);
    }
    
    printf("\n]");
    return 0;
}

 log:

 data=[

 22.0,22.0,flower1

 latitude:22.000000,longitude:22.000000,info:'flower1'

 33.0,33.0,flower2

 ,

 latitude:33.000000,longitude:33.000000,info:'flower2'

 wq

 

 ]

 

 可以看出上面的log中,显示的数据需要手动输入,然后显示,输入和输出混作一团,而且数据量很大的话……

 所以,我们需要一款小工具:数据可以从文件中读取;最好把输出信息也放到文件里;

 

 场景:

 -GPS采集数据到gpsdata.csv文件;

 -通过geo2json工具逐行读取gpsdata.csv文件中的内容;

 -然后以json格式把数据写到一个叫output.json的文件中;

 显然仅仅是在键盘输入,显示器输出是不够的...

 

 过滤器(filter):

 有一种小工具叫过滤器(filter),它逐行读取数据,对数据进行处理,再把数据输出到某个地方;

 在Unix系统中,已经拥有了很多过滤器工具了:

 head:显示文件前几行的内容;

 tail:显示文件最后几行的内容;

 sed:流编辑器(stream editor),用来搜索和替换文本;

 一会会看到如何将多个过滤器组合在一起,形成过滤器链;

 

 可以用重定向:

 在用scanf()从键盘读取数据;printf()向显示器写数据时;这两个函数其实并没有直接使用键盘、显示器,而是用了标准输入和标准输出;

 程序运行时,操作系统会创建标准输入和标准输出;

 

 操作系统控制系统如何进行标准输入输出:

 如果在命令提示符或终端运行程序,操作系统会把所有键盘输入都发送到标准输入;默认,如果操作系统从标准输出中读取数据,就会发送到显示器;

 

 scanf()和printf()并不知道数据从何而来,到何处去;他们只管从标准输入读数据,向标准输出写数据;

 

 之所以使用标准输入输出是因为:

 可以重定向标准输入、标准输出,让程序从键盘以外的地方读数据、往显示器以外的地方写数据,例如文件;

 

 可以用<重定向标准输入:

 可以使用<操作符从文件中读取数据;

 

 我们生成一个文件gpsdata.csv,内容编辑为:

 11.0,11.0,flower1

 12.0,11.0,flower2

 13.0,11.0,flower3

 14.0,11.0,flower4

 15.0,11.0,flower5

 16.0,11.0,flower6

 17.0,11.0,flower7

 18.0,11.0,flower8

 19.0,11.0,flower9

 20.0,11.0,flower10

 21.0,11.0,flower11

 22.0,11.0,flower12

 23.0,11.0,flower13

 

 这些数据可以作为GPS设备的输出,现在让操作系统吧数据从文件发送到程序的标准输入:

 bogon:0807-2 huaqiang$ ./4_1 < gpsdata.csv

 这里4_1使Code4_1代码对应的可执行文件;

 log:

 data=[

 latitude:11.000000,longitude:11.000000,info:'flower1',

 latitude:12.000000,longitude:11.000000,info:'flower2',

 latitude:13.000000,longitude:11.000000,info:'flower3',

 latitude:14.000000,longitude:11.000000,info:'flower4',

 latitude:15.000000,longitude:11.000000,info:'flower5',

 latitude:16.000000,longitude:11.000000,info:'flower6',

 latitude:17.000000,longitude:11.000000,info:'flower7',

 latitude:18.000000,longitude:11.000000,info:'flower8',

 latitude:19.000000,longitude:11.000000,info:'flower9',

 latitude:20.000000,longitude:11.000000,info:'flower10',

 latitude:21.000000,longitude:11.000000,info:'flower11',

 latitude:22.000000,longitude:11.000000,info:'flower12',

 latitude:23.000000,longitude:11.000000,info:'flower13'

 ]

 

 我们看到了程序程序输出的json数据;

 

 <操作符告诉操作系统,程序的标准输入应该与gpsdata.csv相连,而不是键盘,所以可以把数据从文件发送到程序;

 

 接下来重定向程序的输出:

 使用>操作符:./4_1 < gpsdata.csv > output.json

 在执行文件所在目录,生成了output.json文件,其中内容如下:

 data=[

 latitude:11.000000,longitude:11.000000,info:'flower1',

 latitude:12.000000,longitude:11.000000,info:'flower2',

 latitude:13.000000,longitude:11.000000,info:'flower3',

 latitude:14.000000,longitude:11.000000,info:'flower4',

 latitude:15.000000,longitude:11.000000,info:'flower5',

 latitude:16.000000,longitude:11.000000,info:'flower6',

 latitude:17.000000,longitude:11.000000,info:'flower7',

 latitude:18.000000,longitude:11.000000,info:'flower8',

 latitude:19.000000,longitude:11.000000,info:'flower9',

 latitude:20.000000,longitude:11.000000,info:'flower10',

 latitude:21.000000,longitude:11.000000,info:'flower11',

 latitude:22.000000,longitude:11.000000,info:'flower12',

 latitude:23.000000,longitude:11.000000,info:'flower13'

 ]

 

 因为重定向了标准输出,所以屏幕上没有出现任何数据;

 现在这个json文件就可以提供给相应的程序使用(它们支持json,但不支持csv,这也是我们这个小工具的作用:格式装换);

 

 再看一个场景:

 如果GPS数据中出现了错误数据会怎样?比如数据的格式录入错误...

 

 我们的程序不会检查读入的数据,他只是改变了数字的格式,输出到文件,所以我们需要校验数据:

 -对数值进行范围校验(-90~90),若不在则显示一条错误消息;

 -并退出程序,把错误状态码置为2;

 我们修改一些之前代码和文件:

 .csv文件内容:

 11.0,11.0,flower1

 12.0,11.0,flower2

 13.0,11.0,flower3

 94.0,11.0,flower4

 15.0,11.0,flower5

 16.0,11.0,flower6

 17.0,11.0,flower7

 18.0,11.0,flower8

 19.0,11.0,flower9

 20.0,11.0,flower10

 21.0,11.0,flower11

 22.0,11.0,flower12

 23.0,11.0,flower13

 

 (Code4_2)

/*
 * 从命令行读取逗号分隔的数据,然后以json格式显示
 */

#include <stdio.h>

int main() {
    
    float latitude;
    float longitude;
    char info[80];
    int started = 0;
    
    puts("data=[");
    //我们用scanf()输入多条数据
    //scanf()总是接收地址参数
    //scanf()函数返回成功读取的数据条数
    while (scanf("%f,%f,%79[^\n]",&latitude,&longitude ,info ) == 3) {//[^\n]相当于在说:把这一行余下的字符都给我
        if (started) {
            printf(",\n");//如果已经显示了一行数据 就用逗号把他隔开
        }else{
            started = 1;//循环执行一遍之后 置1 表示真
        }
        
        if (latitude < -90.0 || latitude > 90) {
            printf("Invaild latitude:%f\n",latitude);
            return 2;
        }
        if (longitude < -90.0 || longitude > 90) {
            printf("Invaild longitude:%f\n",longitude);
            return 2;
        }
        
        printf("latitude:%f,longitude:%f,info:'%s'",latitude,longitude,info);
    }
    
    printf("\n]");
    return 0;
}

 4_2是Code4_2对应的可执行文件;

 ./4_2 < gpsdataerror.csv > outputerror.json

 json:

 data=[

 latitude:11.000000,longitude:11.000000,info:'flower1',

 latitude:12.000000,longitude:11.000000,info:'flower2',

 latitude:13.000000,longitude:11.000000,info:'flower3',

 Invaild latitude:94.000000

 

 好吧,出问题了,这不是我们想要的:程序没有继续处理数据,而是输出了一条错误消息;而且错误消息也被重定向到了json文件;如果不查看json文件,我们永远也不会知道出错了!

 

 那么,怎样再能在重定向输出的同时显示错误消息呢?

 还记得:echo $? 吗?程序在数据中发现错误就会推出,推出状态置为2,那么如何在程序结束之后检查错误状态呢,我们就是用echo $?命令:

 bogon:0807-2 huaqiang$ ./4_2 < gpsdataerror.csv > outputerror.json

 bogon:0807-2 huaqiang$ echo $?

 2

 

 提问:

 要是有一种针对错误的输出就好了,这样就不会把错误消息混到标准输出里了;

 

 隆重推出标准错误:

 标准输出是程序输出数据的默认方式;如果想要区分错误消息和普通输出,就可以使用标准错误:一个用来发送错误消息的二号输出(标准输出是一号输出);

 默认情况下,标准错误会发送到显示器上;

 这意味着,当某人把标准输入和标准输出重定向到了文件,标准错误仍然会把数据发送到显示器上;

 只要用标准错误显示错误信息,之前的问题就解决了;

 

 具体该怎么做呢?

 

 fprintf()打印到数据流:

 printf()函数可以将数据发送到标准输出,但是printf()其实只是fprintf()函数的特例;

 (1)-printf("flower");

 (2)-fprintf(stdout, "flower");

 当调用(1)时,其实调用的是(2),stdout标示标准输出数据流;上述两者完全等价;

 

 fprintf()函数可以让你决定把文本发送到哪里;可以是stdout(标准输出),也可以是stderr(标准错误);

 如你所料stdin表示标准输入,如果你够细心,我们之前见过他的;类似的还有scanf()/fscanf(stdin, ...);

 

 在修改一下代码:

 (Code4_3)

/*
 * 从命令行读取逗号分隔的数据,然后以json格式显示
 */

#include <stdio.h>

int main() {
    
    float latitude;
    float longitude;
    char info[80];
    int started = 0;
    
    puts("data=[");
    //我们用scanf()输入多条数据
    //scanf()总是接收地址参数
    //scanf()函数返回成功读取的数据条数
    while (scanf("%f,%f,%79[^\n]",&latitude,&longitude ,info ) == 3) {//[^\n]相当于在说:把这一行余下的字符都给我
        if (started) {
            printf(",\n");//如果已经显示了一行数据 就用逗号把他隔开
        }else{
            started = 1;//循环执行一遍之后 置1 表示真
        }
        
        if (latitude < -90.0 || latitude > 90) {
            fprintf(stderr,"Invaild latitude:%f\n",latitude);
            return 2;
        }
        if (longitude < -90.0 || longitude > 90) {
            fprintf(stderr,"Invaild longitude:%f\n",longitude);
            return 2;
        }
        
        printf("latitude:%f,longitude:%f,info:'%s'",latitude,longitude,info);
    }
    
    printf("\n]");
    return 0;
}

 log:

 bogon:0807-2 huaqiang$ ./4_3 < gpsdataerror.csv > outputerror.json

 Invaild latitude:94.000000

 

 现在错误信息显示在了标准错误中,而非标准输出中;解析出来的json文件对应的到了错误数据行就停止了;

 

 创建标准错误的初衷是为了区分普通输出和错误消息;但是别忘了,stderr和stdout只是两个输出流罢了,完全可以用它来做其他事;

 

 要点:

 -printf()函数把数据发送到标准输出;

 -默认,标准输出会发送到显示器;

 -可以在命令行中用>将标准输出重定向到文件;

 -scanf()从标准输入读取数据;

 -默认,标准输入会从键盘读取数据;

 -可以在命令行中用<将标准输入从定向到文件;

 -标准错误专门用来输出错误信息;

 -可以用2>重定向标准错误;(./4_3 < gpsdataerror.csv > outputerror.json 2> error.json,对应的error文件中会显示:Invaild latitude:94.000000)


这里放一张图,以便说明文件间关系:

 


 操作系统把程序变成进程,而不是直接把程序扔到存储器中;O/S为程序分配存储器,把程序和标准数据流连到一起,这样程序才能使用显示器和键盘;

 类Unix操作系统未完陈工作会大量使用工具,操作系统负责把这些工具串联起来;

 

 灵活的小工具:

 小工具的特点就是灵活;如果一个工具很好的完成了一件事,就可以在很多场合用到它;

 

 场景:

 对于之前的示例,我们要求显示符合条件的数据,比如latitude大于15 小于20;该怎么做?

 

 切莫修改之前的工具:

 前面的工具会显示所有的数据,他已经是一个好用的工具了,因为它“做一件事并把它做好”了;

 

 小工具设计Tips:

 像设计之前这样的小工具时,应该遵循一下原则:

 -从标准输入读取数据;

 -从标准输出显示数据;

 -处理文本数据,而不是难以阅读的二进制格式;

 -只做一件简单的事;

 


 今天先到这里,下一节我们继续。。。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值