1. shell 编程
1
2
3
4
5
6
7
8
9
10
who | sort > prev // 同时执行who 与 sort 将who的输出直接送到sort的输入
while true ;do
sleep 30
who | sort > curr
echo "logged out :"
comm -23 prev curr // 删除第二列与第三列 仅留prev内容
echo "logged in:"
comm -13 prev curr
mv curr prev
done
comm 详解:
comm -23 prev curr // 删除第二列与第三列 仅留prev内容
2. I/O 与重定向的一些概念
所有的Unix I/O重定向都是基于标准数据流原理,下面展示sort
的荣作原理:
- 标准输入: 需要处理的数据流
- 标准输出: 结果数据流
- 标准错误输出: 错误消息流
2.1 概念1: 3个标准文件描述符
- 0: stdin 文件描述符0 占0号位置的文件
- 1: stdout
- 2: stderr
所有的Unix工具都使用文件描述符0,1,2。 Unix 假设文件描述符0,1,2已经被打开,可以分别进行读写写操作。
默认的链接:tty
程序都输出到stdout
大多数程序都不接受输出文件名,他们总是将结果写到文件描述符1中去,将错误消息写到文件描述符2. 如果希望进程的输出写到另外一个文件或者程序中去,就必须重定向相应的文件描述符
重定向I\O的是shell 而不是程序
1
2
3
4
5
6
7
8
int main(int ac,char* av[]){
int i;
printf("Number of args: %d,Args are :\n",ac);
for(i = 0; i < ac; ++i){
printf("args[%d] %s\n",i,av[i]);
}
fprintf(stderr,"This message is sen to stdree\n");
}
全部输出到屏幕(默认)
./a.out
重定向1
./a.out ont two > xyz
重定向2
./a.out ont two > xyz 2>oops // 将stderr消息重定向到opps文件中
shell 并不将重定向标记和文件名传递给程序,2>file
意思为重定向文件描述符2,将标准错误输出到给定的文件中.
一些理解
1
2
3
who > userlist // 将stdout链接到一个文件
sort < date // 将 stdin 链接到一个文件
who | sort // 将stdout 链接到 stdin
2.2 概念2:最低可用文件描述符原则
第一个问题:文件描述符是什么?文件描述符是一个数组的索引号,每个进程都打开了一组文件,第零个文件是stdin
第二个问题: 进程新打开的文件,怎么排序? 从小到大一次插入。 把最小可用位置留给它。
3. 重定向编程
3.1 stdin 定向到文件
如何将文件描述符0定义到一个文件
方法一 close then open
这个方法思路其实还是很简单的,close(0), open(new)占住0号位置,ok了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int ac,char* av[]){
int fd;
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
close(0);
fd = open("/etc/passwd",O_RDONLY);
if (fd != 0){
fprintf(stderr,"Could not open data as fd \n");
exit(1);
}
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
}
方法二 open close dup close
四个步骤:
open(file) 打开一个stdin 重定向文件,此时返回的文件描述符fd 可定不为0.
close(0) 关闭0.
dup(fd) 系统调用dup(fd) 将文件描述符fd做了一个复制。 此时复制使用最低可用文件描述符号,0号为空,因此获得0号文件描述符。 此时磁盘文件与文件描述符0链接在一起了。
close(fd) 关闭原始链接,只留下文件描述符0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int main(int ac,char* av[]){
int fd;
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fd = open("/home/xm/projects/linux/data.txt",O_RDONLY);
if (fd == -1){
perror("open error");
}
int newFd;
newFd = dup2(fd,0);
if (newFd != 0){
perror(" dup2 error");
exit(1);
}
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
fgets(line,100,stdin);
printf("%s",line);
}
多看看manpage
dup详解: The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
总结
其实标准输入stdin 重定向用的不是很多,如果程序要读入某一个文件,直接打开就可以啦。 以上程序只为了展示 0号文件描述符的作用。
3.2 为其他程序重定向
上面的一个例子是自己写的程序中定向stdin。 其他程序如何重定向呢? 例如who > userlist
. 关键点在fork() 之后,exce() 之前。,exce()将替换进程中的运行程序,但是不会改变进程属性与链接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main(int ac,char* av[]){
int pid;
int fd;
printf("About to run who into a file \n");
pid = fork() ;
if ( pid == -1){
perror("fork");
exit(1);
}
if (pid == 0){
close(1);
fd = creat("userlist",0644);
execlp("who","who",NULL);
perror("execlp error");
exit(1);
}
if (pid != 0){
wait(NULL);
printf("Done runing who,result in userlist\n");
}
}
3.3 定向到文件 小结
- 标准输入 标注输出 错误输出 分别对应文件描述符 0,1,2
- 内核总是使用最低可用文件描述符
- 文件描述符集合通过exec调用传递
shell 支持一下两种形式命令: - who > userlog
- sort < data
4. 管道编程
pipe(int array[2]) // array[0] 为读描述符 ,array[1] 为写描述符
1
2
3
4
int apipe[2];
pipe(apipe[2]);
write to apipe[1];
read from apipe[0];
4.1 使用管道来共享进程 fork()
先放一个例子吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#define CHIHLD_MESS "I want a cookie\n"
#define PAR_MESS "testing ..\n"
#define oops(m,x)
int main(int ac,char* av[]){
int pipefd[2];
int len;
char buf[BUFSIZ];
int read_len;
if (pipe(pipefd) == -1)
perror("cannot get a pipe");
switch (fork()) {
case -1:
perror("cannot fork");
case 0:
len = strlen(CHIHLD_MESS);
while (1){
if (write(pipefd[1],CHIHLD_MESS,len) != len)
perror("write error");
sleep(5);
}
default:
len = strlen(PAR_MESS);
if (write(pipefd[1],PAR_MESS,len) == -1)
perror("write error");
sleep(1);
read_len = read(pipefd[0],buf,BUFSIZ);
if (read_len <= 0)
break;
write(1,buf,read_len);
}
}
当父进程创建一个管道后,该进程就有了管道两端的链接。fork()后,子进程也得到了管道两端的链接。怎么说呢,子进程与父进程这两个管道是互相独立的。下面的例子可以看出。
4.2 编写 who > sort
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int main(int ac,char *av[]){
int thepipe[2],newfd,pid;
if (ac != 3){
fprintf(stderr,"paramers error");
exit(1);
}
if (pipe(thepipe) == -1){
perror("pipe error");
}
pid = fork();
if (pid == -1)
perror("fork error");
// father process
if (pid > 0){
close(thepipe[1]); // close write
if (dup2(thepipe[0],0) == -1)
perror("dup2 error");
close(thepipe[0]);
execlp(av[2],av[2]);
perror("error ending");
}
close(thepipe[0]);
if (dup2(thepipe[1],1) == -1){
perror("son dup2 error");
}
close(thepipe[1]);
execlp(av[1],av[1],NULL);
perror("bad ending");
}
4.3 管道小结
读的问题
管道读取阻塞 当进程试图从管道中读取数据的时候,进程被挂起直到数据被写入管道中
管道的读取结束标志 当所有的写者都关闭了管道的写数据端时,试图从管道读数据时,返回0;
多个读者的麻烦 管道是一个队列。 当一个进程从管道中读取数据后,数据已经不存在了, 进程需要某种方法协调他们对管道的访问。
向管道中写数据
写入数据阻塞直到管道上有空间去容纳新的数据 管道的容量是有限的。
无读者的情况下,写操作失败。