在Unix系统中,每个进程都有STDIN、STDOUT和STDERR这3种标准I/O,它们是程序最通用的输入输出方式。
在日常操作中,我们很习惯的用上了重定向和管道( 1|2 :1的输出作为2的输入)操作,那么究竟原理是怎样的呢?
我们知道每个进程都会维护一个文件描述符表(FILE DESCRIPTOR TABLE),用以定位被打开的文件。
实际上,表中的记录(文件描述符)是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。
在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR。
当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
接下去我们要从文件描述符展开介绍重定位和管道是怎么一回事,先看一下文件描述符表的示意图。
很清晰的可以看出来之前描述的情况,接着我们研究重定向,用 ls /bin > testfile.txt为例。
我们便理解了,所谓的I/O重定向也就是让已创建的FD指向其他文件(testfile.txt)
文件描述符 0/1/2代表STDIN/STDOUT/STDERR代表的意义本身没有变化,变化的是文件描述符表中FD 0/1/2对应的具体文件,
从这个描述符表中我们得到的信息表示 ls /bin 的标准输出STDOUT重定向到了textfile.txt
这里强调一下文件描述符是一个索引号,每个表项都指向已打开文件的指针。
默认的STDIN/STDOUT/STDERR指向的是 /dev/ttyX (X表示用哪个终端执行的程序的号码)
接着看一个管道的例子(摘录自某篇博客)
crazy@ubuntu14:~$ sleep 30 | sleep 40 &
[1] 5584
crazy
@ubuntu14:~$ pgrep -l sleep 5583 sleep 5584 sleeptotal 0lrwx------ 1 crazy crazycrazy
@ubuntu14:~$ ll /proc/5583/fd
64 Feb 27 13:41 0 -> /dev/pts/3l-wx------ 1 dagang dagang 64 Feb 27 13:41 1 -> pipe:[246469]lrwx------ 1 dagang dagang 64 Feb 27 13:41 2 -> /dev/pts/3total 0lr-x------ 1crazy
@ubuntu14:~$ ll /proc/5584/fdcrazy crazy
64 Feb 27 13:41 0 -> pipe:[246469]
lrwx------ 1 crazy crazy 64 Feb 27 13:41 1 -> /dev/pts/3lrwx------ 1 crazy crazy 64 Feb 27 13:41 2 -> /dev/pts/3
上面我们启动了两个进程5583和5584,通过查看/proc//fd,
我们看到进程5583的STDOUT和5584的STDIN被重定向到了pipe:[246469],这样就达到了连接两个进程标准I/O的目的。
接着我们讲一下实际运用重定位的操作
—————————————————————————————————————————————
cmd > file 把 stdout 重定向到 file 文件中
cmd >> file 把 stdout 重定向到 file 文件中(追加)
cmd 2> file 把 stderr 重定向到 file 文件中
cmd 2>> file 把 stderr 重定向到 file 文件中(追加)
cmd < file cmd 命令以 file 文件作为 stdin
cat <>file 以读写的方式打开 file
cmd < file >file2 cmd 命令以 file 文件作为 stdin,以 file2 文件作为 stdout
cmd > file 2>&1 把 stdout 和 stderr 一起重定向到 file 文件中 (2重定向到与描述符1指向文件)
cmd >> file 2>&1 把 stderr 和 stderr 一起重定向到 file 文件中 (追加方式)
cmd << delimiter Here document,从 stdin 中读入,直至遇到delimiter 分界符
————————————————————————————————————————————————
>&n 使用系统调用 dup (2) 复制文件描述符 n 并把结果用作标准输出—————————————————————————————————————————————————
上面介绍了文件描述符表和I/O重定向的原理,那么在Linux系统中如何通过C程序实现I/O重定向呢?
主要是用这个函数 int dup2(int oldfd, int newfd);
int main() { int pid = 0; // fork a worker process if (pid = fork()) { //father process // wait for completion of the child process int status; waitpid(pid, &status, 0); } else { //child process // open input and output files int fd_in = open("in.txt", O_RDONLY); int fd_out = open("out.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd_in > 0 && fd_out > 0) { // redirect STDIN/STDOUT for this process dup2(fd_in, 0); dup2(fd_out, 1); // call shell command system("sort"); close(fd_in); close(fd_out); } } return 0; }
上面的主要步骤包括:
1. 首先fork一个子进程,后续步骤都在子进程中完成,父进程通过waitpid()系统调用等待子进程结束;
2. 打开open()系统调用打开in.txt和out.txt,得到它们的描述符;
3. 通过dup2()系统调用把STDIN重定向到fd_in,把STDOUT重定向到fd_out(注意,重定向的影响范围是整个子进程);
4. 通过system()系统调用运行shell命令sort