该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
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:
像设计之前这样的小工具时,应该遵循一下原则:
-从标准输入读取数据;
-从标准输出显示数据;
-处理文本数据,而不是难以阅读的二进制格式;
-只做一件简单的事;
今天先到这里,下一节我们继续。。。