
Linux系统编程-文件IO
1- 系统调用关系
C标准函数和系统函数调用关系:一个helloworld打印到屏幕的过程
2- 文件描述符
2.1 PCB进程控制块
用来描述进程的标志,本质上是一个结构体,其中有一根指针指向文件描述符表。
2.2 文件描述符表
文件描述符表里面的成员都是一个指针,本质是key-value键值对,value就是指针。
文件描述符是指向一个文件结构体的指针!当使用open等函数打开的文件,就是返回的fd
操作系统并不会暴露给你文件描述符实现细节,所以只暴露了下标。
文件描述符从3开始,0是标准输入,1是标准输出,2是标准错误,能打开的文件最大1024个.
STDIN_FILENO 0 // 标准输入
STDOUT_FILENO 1 // 标准输出
STDERR_FILENO 2 // 标准错误
2.3 最大打开文件数
一个进程默认打开文件的个数
1024
。命令查看
ulimit -a
查看open files
对应值。默认为1024
可以使用
ulimit -n 4096
修改当然也可以通过修改系统配置文件永久修改该值,但是不建议这样操作。
cat /proc/sys/fs/file-max
可以查看该电脑最大可以打开的文件个数。受内存大小影响。
3- open/close函数
3.1 函数原型
#include <unistd.h> // 包含open/close函数
#include <fcntl.h> // 包含O_RDONLY等宏定义
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
可以使用man 2 open
和man 2 close
查看相关函数的man手册。
pathname
:要打开的文件的路径名。flags
:打开文件的标志,以下是一些常用的flags标志量:O_RDONLY
:以只读模式打开文件。O_WRONLY
:以只写模式打开文件。O_RDWR
:以读写模式打开文件。O_CREAT
:如果文件不存在,则创建文件。O_TRUNC
:如果文件存在并且以写入模式打开,则将文件截断为零长度。O_APPEND
:如果文件以写入模式打开,则在文件末尾追加数据。O_NONBLOCK
: 用于设置文件描述符为非阻塞模式。O_EXCL
:与O_CREAT
一起使用时,如果文件已经存在,则打开失败。O_BINARY
:以二进制模式打开文件(在某些系统上可用)。O_TEXT
:以文本模式打开文件(在某些系统上可用)。
这些标志可以通过按位或运算符
|
进行组合,以满足特定的需求。例如,要以读写模式打开文件并在文件末尾追加数据,可以使用flags
参数为O_RDWR | O_APPEND
。mode
:创建新文件时的权限(仅在flags
包含O_CREAT
时有效)。0775,0511等创建文件时,指定文件访问权限。权限同时受
umask
影响。结论为: 文件权限 =mode & ~umask
fd
:要关闭的文件描述符。
3.2 常见错误
打开文件时可能出现的错误:
文件不存在(如果未指定
O_CREAT
标志)。权限不足。
文件路径错误或无效。
文件已经被其他进程以独占方式打开。
关闭文件时可能出现的错误:
文件描述符无效或不可关闭。
关闭文件时发生了错误(例如,文件已经关闭或文件描述符无效)。
可以使用 errno
和 strerror
来获取错误信息,并根据需要采取适当的措施来处理错误。
3.3 示例
/** 实现了打开文件文件的程序,如果文件不存在则按照权限创建,如果存在则文件清0 **/
#include <unistd.h> // 包含open/close函数
#include <stdio.h>
#include <fcntl.h> // 包含O_RDONLY等宏定义
#include <errno.h> // 包含了errno参数
#include <string.h> // str化errno参数
int main(int argc, char *argv[])
{
int fd;
fd = open("./test.txt",O_RDONLY | O_CREAT | O_TRUNC, 0777);
printf("fd = %d\nerror = %d:%s\n",fd,errno,strerror(errno));
// errno是一个全局变量,当函数发生错误时,设置一个errno的值指示具体的错误
// strerror 是一个函数,用于将错误代码转换为对应的错误消息字符串。它接受一个整数参数,即错误代码,然后返回相应的错误消息字符串。
close(fd);
return 0;
}
4- read/write函数
4.1 函数原型
#include <unistd.h> // 包含read/write函数
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
可以使用man 2 read
和man 2 write
查看相关函数的man手册。
fd
:文件描述符,表示要进行读写操作的文件或套接字。文件描述符是一个非负整数,可以通过打开文件或创建套接字来获得。buf
:用于读取或写入数据的缓冲区的指针。对于read
,它是用于存储读取数据的缓冲区;对于write
,它是包含要写入的数据的缓冲区。count
:要读取或写入的字节数。它指定了缓冲区的大小。
4.2 常见错误
这些函数返回一个 ssize_t
类型的值,表示实际读取或写入的字节数。如果返回值为负数,则表示出现了错误。如果返回值为0,表示读到了文件末尾。一些常见的错误代码包括:
EINTR
:操作被信号中断。EAGAIN
或EWOULDBLOCK
:非阻塞文件描述符上没有可用的数据或无法立即写入数据。EBADF
:无效的文件描述符。EFAULT
:缓冲区指针无效。EINVAL
:无效的参数,如读取或写入的字节数为负数。EIO
:I/O 错误。
可以使用 errno
和 strerror
来获取错误信息,并根据需要采取适当的措施来处理错误。
4.3 预读入缓输出机制
buf
的大小决定了一次write
函数从用户空间向内核空间传输的字节数,kernel
空间的缓冲区最大大小为4KB,即4096。因为write
函数从用户空间访问内核空间比较耗时,所以buf
越大,可以减少访问kernel
的次数,这样可以提高效率。
4.4 示例
/***** 实现了拷贝文件的程序 *****/
#include <unistd.h> // 包含open/close/read/write函数
#include <stdio.h> // 包含标准输入输出函数的头文件,printf、scanf、fopen、fclose 等函数
#include <stdlib.h> // 包含标准库函数的头文件。malloc、free、atoi、rand、qsort 等函数。
#include <fcntl.h> // 包含O_RDONLY等宏定义
#include <errno.h> // 包含了errno参数
#include <string.h> // str化errno参数
#include <pthread.h> // 包含 POSIX 线程库的头文件。线程的创建、销毁、同步、互斥等操作的函数和数据类型。
int main(int argc, char *argv[])
{
// 定义大小
int n = 0;
char buf[1024]; // buf大小设定为1024,即一次向内核空间传输1KB大小的数据
int fd1 = open(argv[1],O_RDONLY); // read
if(fd1 == -1)
{
perror("open argv1 error!"); // 打印错误输出信息
exit(1);
}
int fd2 = open(argv[2],O_RDWR | O_CREAT | O_TRUNC, 0664);// write
if(fd2 == -1)
{
perror("open argv2 error!");
exit(1);
}
while((n=read(fd1,buf,1024))!=0)
{
if(n<0)
{
perror("read error!");
break;
}
write(fd2,buf,n); // 第三个参数必须为n
}
close(fd1);
close(fd2);
return 0;
}
5- 错误处理函数
5.1 perror
函数
void perror(const char *s);
示例:
#include <stdio.h>
#include <errno.h>
int main() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
perror("Error");
return 1;
}
// 其他文件操作
fclose(file);
return 0;
}
如果fopen
打开文件失败,则perror
函数将打印类似以下的错误消息到标准错误流:
Error: No such file or directory
.
5.2 strerror
函数
char *strerror(int errnum);
示例:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main() {
int errnum = errno; // 获取当前的错误代码
const char *errmsg = strerror(errnum); // 获取错误消息字符串
printf("Error message: %s\n", errmsg);
return 0;
}
使用 errno
宏获取当前的错误代码,并将其作为参数传递给 strerror
函数。然后,我们将返回的错误消息字符串打印出来。
需要注意的是,strerror
函数返回的错误消息字符串是一个静态的全局数组,因此在多 线程环境下使用时需要注意线程安全性。
6- 阻塞与非阻塞(等待)
产生阻塞的场景:读设备文件、网络文件。(读常规文件无阻塞概念)
/dev/tty
-- 终端文件。
open("/dev/tty", O_RDWR | O_NONBLOCK)
--- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)
/***** 阻塞 *****/
/***** 该程序读取设备文件 *****/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
char buf[10];
int n;
n = read(STDIN_FILENO,buf,10);
if(n<0)
{
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO,buf,n);
return 0;
}
/***** 非阻塞 *****/
/***** 更改非阻塞读取终端——超时设置 *****/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#define MSG_TRY "Try Again\n"
#define MSG_TIMEOUT "TIME OUT\n"
int main(void)
{
char buf[10];
int fd,n,i;
fd = open("/dev/tty", O_RDONLY | O_NONBLOCK); // 设置为非阻塞状态,默认为阻塞状态
if(fd <0)
{
perror("open /dev/tty");
exit(1);
}
printf("open /dev/tty ok... %d\n",fd);
for(i = 0; i < 5; i++)
{
n = read(fd,buf,10);
if(n > 0)
{
break;
}
if(errno != EAGAIN) // EWOULDBLOCK
{
perror("READ /DEV/TTY");
exit(1);
}
else
{
write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));
sleep(2);
}
}
if (i == 5)
{
write(STDOUT_FILENO,MSG_TIMEOUT,strlen(MSG_TIMEOUT));
}
else
{
write(STDOUT_FILENO,buf,n);
}
close(fd);
return 0;
}
7- fcntl函数
改变一个已经打开的文件的访问控制属性。
7.1 函数原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */);
7.2 参数
fd
: 文件描述符cmd
: 命令,决定了后续参数个数F_GETFL
: 获取文件状态F_SETFL
: 设置文件状态F_DUPFD
: 复制文件描述符并返回一个新的文件描述符
... /* arg */
: 可变参数,根据cmd
的不同有不同的形式
7.3 返回值
int
类型的返回值,根据cmd
的不同有不同的返回值flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
7.4 示例
终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_TRY "try again\n"
int main(void)
{
// 将标准输入更改为非阻塞
char buf[10];
int flags, n;
flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
if(flags == -1){
perror("fcntl error");
exit(1);
}
flags |= O_NONBLOCK;
int ret = fcntl(STDIN_FILENO, F_SETFL, flags);
if(ret == -1){
perror("fcntl error");
exit(1);
}
// 测试
tryagain:
n = read(STDIN_FILENO, buf, 10);
if(n < 0){
if(errno != EAGAIN){
perror("read /dev/tty");
exit(1);
}
sleep(3);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
write(STDOUT_FILENO, buf, n);
return 0;
}
8- lseek函数
Linux 中可使用系统函数 lseek 来修改文件偏移量(读写位置)
8.1 函数原型
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
8.2 参数
fd
:文件描述符offset
: 偏移量,就是将读写指针从whence
指定位置向后偏移offset
个单位,矢量值whence
:起始偏移位置
8.3 返回值
成功: 较起始位置偏移量
失败: -1 errno
8.4 使用场景
文件的读、写使用同一偏移位置
使用lseek获取文件大小
使用lseek拓展文件大小,要想使文件大小真正拓展,必须引起IO操作。
8.5 示例
写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(void)
{
int fd, n;
char msg[] = "It's a test for lseek\n";
char ch;
fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);
if(fd < 0){
perror("open lseek.txt error");
exit(1);
}
write(fd, msg, strlen(msg)); //使用fd对打开的文件进行写操作,此时读写位置位于文件结尾处。
lseek(fd, 0, SEEK_SET); //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢?
while((n = read(fd, &ch, 1))){
if(n < 0){
perror("read error");
exit(1);
}
write(STDOUT_FILENO, &ch, n); //将文件内容按字节读出,写出到屏幕
}
close(fd);
return 0;
}
8.6 拓展文件大小
8.7 总结
对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。
lseek
读取的文件大小总是相对文件头部而言。用
lseek
读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek
返回799
,ls
查看为800的原因是,lseek
读取到偏移差的时候,还没有写入最后的‘$’符号,末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。
truncate函数
可以直接拓展文件。
int ret = truncate("dict.cp", 250); // 该文件必须存在,扩展出来250大小,成功返回0
9- 传入传出参数
传入参数:
1. 指针作为函数参数。
2. 同常有const关键字修饰。
3. 指针指向有效区域, 在函数内部做读操作。
传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。
3. 在函数内部,做写操作。
4。函数调用结束后,充当函数返回值。
传入传出参数:
1. 指针作为函数参数。
2. 在函数调用之前,指针指向的空间有实际意义。
3. 在函数内部,先做读操作,后做写操作。
4. 函数调用结束后,充当函数返回值
10- 目录项和inode
一个文件主要由两部分组成,dentry(目录项)和inode
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
11- stat函数
获取文件属性,(从inode结构体中获取)
11.1 函数原型
stat/lstat
函数
int stat(const char *path, struct stat *buf);
11.2 参数
path
: 文件路径buf
:(传出参数) 存放文件属性,inode结构体指针。获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode
11.3 返回值
成功: 0
失败: -1 errno
11.4 示例
获取文件大小的正规军解法,用stat
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror("stat error");
exit(1);
}
printf("file size: %ld\n", sbuf.st_size);
return 0;
}
12- lstat和stat
lstat
查看文件类型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror("stat error");
exit(1);
}
if (S_ISREG(sbuf.st_mode))
{
printf("It is a regular\n");
}
else if (S_ISDIR(sbuf.st_mode))
{
printf("It is a dir\n");
}
else if (S_ISFIFO(sbuf.st_mode))
{
printf("It is a pipe\n");
}
// stat存在符号穿透, 显示的是符号链接的文件
else if (S_ISLNK(sbuf.st_mode))
{
printf("It is a sym link\n"); // 无法显示符号连接
}
return 0;
}
stat
存在符号穿透, 显示的是符号链接的文件
类似穿透现象还有cat vim
(实现基于系统调用)
不想穿透符号就用lstat
13- link和unlink和隐式回收
硬链接数就是
dentry
数目link
就是用来创建硬链接的link
可以用来实现mv
命令
13.1 link
函数
int link(const char *oldpath, const char *newpath);
用这个来实现mv,用oldpath来创建newpath,然后删除oldpath就行。
13.2 unlink
函数
删除一个链接
int unlink(const char *pathname);
unlink
是删除一个文件的目录项dentry
,使【硬链接数-1】unlink
函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry
对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
13.3 示例:使用unlink
实现mv
功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// 实现mv (rename) 功能
int main(int argc, char *argv[])
{
link(argv[1], argv[2]);
unlink(argv[1]);
}
13.4 示例:验证unlink
是删除dentry
/*
*unlink函数是删除一个dentry
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void)
{
int fd, ret;
char *p = "test of unlink\n";
char *p2 = "after write something.\n";
fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open temp error");
exit(1);
}
ret = write(fd, p, strlen(p));
if (ret == -1) {
perror("-----write error");
}
printf("hi! I'm printf\n");
ret = write(fd, p2, strlen(p2));
if (ret == -1) {
perror("-----write error");
}
printf("Enter anykey continue\n");
getchar();
ret = unlink("temp.txt"); //具备了被释放的条件
if(ret < 0){
perror("unlink error");
exit(1);
}
close(fd);
return 0;
}
编译程序并运行,程序阻塞,文件还未被删除,因为当前程序未结束。输入字符使当前进程结束后,temp.txt
就不见了。
下面开始搞事,在程序中加入段错误成分,段错误在unlink
之前,由于发生段错误,程序后续删除temp.txt
的dentry
部分就不会再执行,temp.txt
就保留了下来,这是不科学的。
解决办法是检测fd
有效性后,立即释放temp.txt
,由于进程未结束,虽然temp.txt
的硬链接数已经为0,但还不会立即释放,仍然存在,要等到程序执行完才会释放。这样就能避免程序出错导致临时文件保留下来。
因为文件创建后,硬链接数立马减为0,即使程序异常退出,这个文件也会被清理掉。这时候的内容是写在内核空间的缓冲区。
以下是修改后的程序
/*
*unlink函数是删除一个dentry
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void) {
int fd, ret;
char *p = "test of unlink\n";
char *p2 = "after write something.\n";
fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
if(fd < 0){
perror("open temp error");
exit(1);
}
ret = unlink("temp.txt"); //具备了被释放的条件
if(ret < 0){
perror("unlink error");
exit(1);
}
ret = write(fd, p, strlen(p));
if (ret == -1) {
perror("-----write error");
}
printf("hi! I'm printf\n");
ret = write(fd, p2, strlen(p2));
if (ret == -1) {
perror("-----write error");
}
printf("Enter anykey continue\n");
getchar();
close(fd);
return 0;
}
隐式回收
当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。
比如上面那个程序,要是没有在程序中关闭文件描述符,没有隐式回收的话,这个文件描述符会保留,多次出现这种情况会导致系统文件描述符耗尽。所以隐式回收会在程序结束时收回它打开的文件使用的文件描述符。
14- readlink函数
读取符号链接文件本身内容,得到链接所指向的文件名。
ssize_t readlink(const char *path, char *buf, size_t bufsize);
// 成功: 0;
// 失败: -1; 设置errno为相应值
15- 文件目录rwx权限差异
如果使用vi
编辑器打开目录,则会得到目录项的列表
16- 目录操作函数
16.1 opendir函数
根据传入的目录名打开一个目录(库函数) DIR*
类似于FILE*
#include <dirent.h>
DIR * opendir(char *name);
// 成功: 返回指向该目录的结构体指针
// 失败: 返回NULL
参数支持相对路径和绝对路径两种方式
例如:
打开当前目录:
1. getcwd(), opendir()
2. opendir(".")
16.2 closedir函数
关闭打开的目录
#include <dirent.h>
int closedir(DIR *dirp)
// 成功: 返回0
// 失败: 返回-1,设置errno为相应值
16.3 readdir函数
#include <dirent.h>
struct dirent *readdir(DIR * dirp);
以下为struct dirent
结构体
struct dirent {
ino_t d_ino; // inode number
off_t d_off; // offset to the next dirent
unsigned short d_reclen; // length of this record
unsigned char d_type; // type of file
char d_name[256]; // filename
};
示例:
打印指定目录的目录项
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char *argv[]) {
DIR *dir;
struct dirent *entry;
dir = opendir(argv[1]); // 打开目录
if (dir == NULL) {
perror("opendir");
return 1;
}
while ((entry = readdir(dir)) != NULL) { // 读取目录中的条目
// 用来隐藏'.'和'..'
if(strcmp((entry->d_name),".") == 0)
continue;
if(strcmp((entry->d_name),"..") == 0)
continue;
printf("%s\t", entry->d_name); // 打印文件名
}
printf("\n");
closedir(dir); // 关闭目录
return 0;
}
17- 递归遍历目录
17.1 递归遍历目录思路
任务需求: 使用opendir
closedir
readdir
stat
实现一个递归遍历目录的程序
输入一个指定目录,默认为当前目录。递归列出目录中的文件,同时显示文件大小。
思路分析:递归遍历目录:ls -R .
判断命令行参数,获取用户要查询的目录名。
int argc, char *argv[1]
判断用户指定的是否是目录。
stat S_ISDIR(); ---> // 将这个代码封装在isFile()函数中
读目录
opendir (dir) while (readdir ()) { 判断isFile() - ---true---普通文件,直接打印 - ---false--是目录---sprintf --拼接目录访问绝对路径---> sprintf(path, "%s/%s", dir, d_name) --继续判断是否为文件,是则打印,否则继续进入子目录 --进入目录,递归调用自己---> opendir(path) readdir closedir } closedir()
17.2 代码预览
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pthread.h>
void isFile(char *name);
// 打开目录读取,处理目录
void read_dir(char *dir, void (*func)(char *))
{
DIR *dp;
char path[256];
struct dirent *sdp;
// 打开dir
dp = opendir(dir);
if (dp == NULL)
{
perror("opendir error");
return;
}
// 读dir
while((sdp = readdir(dp)) != NULL)
{
// 跳过"."和"..",防止无限递归
if (strcmp(sdp->d_name,".") == 0 || strcmp(sdp->d_name,"..") == 0)
{
continue;
}
// 拼接路径(目录项本身不可访问)
sprintf(path, "%s/%s", dir, sdp->d_name);
// 递归
// isFile(path);
// 回调函数
(*func)(path);
}
// 关闭dir
closedir(dp);
return;
}
void isFile(char *name)
{
int ret = 0;
struct stat sb;
ret = stat(name, &sb);
if (ret == -1)
{
perror("stat error");
return;
}
if (S_ISDIR(sb.st_mode))
{
read_dir(name, isFile);
}
printf("%10s\t\t%ld\n",name, sb.st_size);
return;
}
int main(int argc, char *argv[])
{
// 判断命令行参数
if (argc == 1)
{
isFile("."); // 如果没有输入命令行参数,则执行当前目录
}
else
{
isFile(argv[1]);
}
return 0;
}
18- 重定向
用来做重定向,本质就是复制文件描述符
18.1 dup函数
函数原型
int dup(int oldfd); // 文件描述符复制
参数
oldfd
: 已有文件描述符
返回值
成功:
int
类型的新文件描述符,这个描述符和oldfd
指向相同内容。失败: 返回-1,设置errno为相应值。
示例
给一个旧的文件描述符,返回一个新文件描述符
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int fd = open(argv[1], O_RDONLY);
int newfd = dup(fd);
printf("newfd = %d\n", newfd);
return 0;
}
18.2 dup2函数
函数原型
int dup2(int oldfd, int newfd); // 文件描述符复制,oldfd拷贝给newfd
参数
oldfd
: 已有文件描述符newfd
: 新文件描述符
返回值
成功:
int
类型的新文件描述符,这个描述符和oldfd
指向相同内容。失败: 返回-1,设置errno为相应值
示例
将一个已有文件描述符fd1复制给另一个文件描述符fd2,然后用fd2修改fd1指向的文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int fd1 = open(argv[1], O_RDWR);
int fd2 = open(argv[2], O_RDWR);
int fdret = dup2(fd1, fd2);
printf("newfd = %d\n", fdret);
int ret = write(fd2, "i love you\n", 12); // 这样写会覆盖原有的内容
printf("ret = %d\n", ret);
return 0;
}
也可以进行标准输出的重定向,将输出到STDOUT的内容重定向到文件里
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int fd1 = open(argv[1], O_RDWR);
int fd2 = open(argv[2], O_RDWR);
int fdret = dup2(fd1, fd2);
printf("newfd = %d\n", fdret);
int ret = write(fd2, "1234567", 7); // 这样写会覆盖文件原有的内容
printf("ret = %d\n", ret);
dup2(fd1, STDOUT_FILENO);
printf("---------- I Love You ! \n"); // 这样写会覆盖文件原有的内容
return 0;
}
该程序的重定向状态如下:
18.3 fcntl实现dup描述符
函数原型
#include <unistd.h>
#include <fcntl.h>
int newfd = fcntl(oldfd, F_DUPFD, minfd); // 用于描述符复制的函数形式
参数
oldfd
: 旧文件描述符F_DUPFD
: 复制文件描述符并返回一个新的文件描述符的命令选项minfd
: 新文件描述符的最小值
返回值
int
类型的新文件描述符。新的文件描述符是系统中当前可用的最小的未使用的文件描述符,并且大于或等于minfd
。
示例
用fcntl
来实现描述符的复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int fd1 = open(argv[1], O_RDWR);
printf("fd1 = %d\n", fd1);
// 使用方法:int newfd = fcntl(oldfd, F_DUPFD, minfd);
int newfd = fcntl(fd1, F_DUPFD,0); // 0被占用,fcntl使用文件描述符表中最小可用的文件描述符返回
printf("newfd = %d\n", newfd);
int newfd2 = fcntl(fd1, F_DUPFD,7); // 7未被占用,返回 >=7 的文件描述符
printf("newfd2 = %d\n", newfd2);
return 0;
}
- 感谢你赐予我前进的力量