Linux-基础IO+minishell重定向

文件的输入输出在c语言之前了解过。比如标准库的IO接口

回顾C语言的库函数

fopen,fread,fwrite,fseek,fclose这几个函数。关键在于参数的使用。

例如fopen,FILE *fopen(const char *path, const char *mode),在不同的模式下打开文件所能作的操作也不同。

文件使用方式含义如果指定文件不存在
r–只读为了输入数据,打开一个已经存在的文本文件出错
w–只写为了输出数据,打开一个文本文件建立一个新文件
a–追加向文本文件尾添加数据出错
rb–只读为了输入数据,打开一个二进制文件出错
wb–只写为了输出数据,打开一个二进制文件建立一个新文件
ab–追加向一个二进制文件添加数据出错
r±-读写为了读和写,打开一个文本文件出错
w±-读写为了读和写,建立一个新的文件建立一个新文件
a±-读写打开一个文件,在文件尾进行读写建立一个新的文件
rb±-读写为了读和写打开一个二进制文件出错
wb±-读写为了读和写,新建一个新的二进制文件建立一个新的文件
ab±-读写打开一个二进制文件,在文件尾进行读和写建立一个新的文件

先回顾一下C语言中的文件操作

#include <stdio.h>    
#include <unistd.h>    
#include <string.h>    
  int main(){    
    int ret;    
    FILE *fp = NULL;    
    fp = fopen("./tmp.txt","r+");    
    if(fp == NULL){    
      perror("打开失败");                                                                                               
      return -1;    
    }    
  fseek(fp, 5, SEEK_END);    
      
  char *ptr = "nihao---\n";    
      
  ret = fwrite(ptr, 1, strlen(ptr), fp);    
  printf("write item:%d\n",ret);    
      
  fseek(fp, 0, SEEK_SET);    
  char buf[1024] = {0};    
      
  ret = fread(buf, 1, 1023, fp);    
  perror("读取失败");    
  printf("read buf[%s]-[%d]",buf,ret);    
      
  fclose(fp);    
  return 0;    
  }

先打开当前路径下的tmp.txt文件以读写的方式打开,之后利用fseek函数跳转读写位置从当前文件位置的末尾开始向后五个位置跳转。然后写入数据,一个字节写入,写入长度为*ptr的字符串长度,然后再跳转到文件的开头开始读取数据读取到buf中。fread返回值是读取的数据长度。最后关闭文件。

这是C语言库函数中的使用方法。

系统调用IO接口
文件描述符和文件流指针

标准库接口使用文件流指针 *FILE

系统调用接口使用文件描述符 比如 int fd

进程中使用open函数打开某个文件,前提是需要我们将进程与文件联系起来。所以进程PCB中就有一个 *FILE指针,这个*FILE指针指向一个数组files_struct,这个数组内每个元素都对应了一个文件指针,文件指针指向各个FILE结构体。

文件流指针这个结构体中就包含了文件描述符,当使用标准库接口进行io,则最终是通过文件流指针找到文件描述符进而对文件进行操作

文件描述符是一个正整型数字。文件描述符实际上就是一个数组下标,当进程每打开一个文件,都会使用struct file描述这个文件,并且将描述信息添加到struct file这个结构中的file结构体数组中,并且向用户返回数组下标作为文件描述符,用户通过文件描述符对文件进行操作,再内核实际上是通过文件描述符找到文件描述信息,进而操作文件。

​ 标准输入 标准输出 标准错误

​ stdin stdout stderr

​ 0 1 2

在这里插入图片描述

文件描述符分配规则:最小未使用

系统调用函数

open write read close lseek

int open(const char *pathname, int flags, mode_t mode);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
off_t lseek(int fd, off_t offset, int whence);
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路径名
flags:选项标志
必选项:
	O_RDONLY 只读
	O_WRONLY 只写
	O_REWR 可读可写
可选项:
	O_CREAT 文化不存在则创建,存在则打开
	O_EXCL 与O_CREAT同用时,若文件存在则报错
	O_APPEND 写追加模式
mode:创建文件时给定权限 (八进制数字)
	mode &~umask)
返回值:文件描述符-正整数	错误:-1
ssize_t write(int fd, const void *buf, size_t count);
fd:打开文件所返回的文件描述符
buf:要向文件写入数据
count:要写入的数据长度
返回值:实际的写入字节数	错误:-1
ssize_t read(int fd, void *buf, size_t count);
fd:打开文件所返回的文件描述符
buf:对读取到的数据进行存储的位置
count:要读取的数据长度
返回值:实际的读取字节数	错误:-1
off_t lseek(int fd, off_t offset, int whence);
fd:打开文件所返回的文件描述符
offset:偏移量
whence:偏移位置
	SEEK_SET
	SEEK_CUR
	SEEK_END
返回值:返回当前位置到文件起始位置的偏移量

掌握知识的最好办法还是结合代码!!!

#include <stdio.h>
#inlcude <unistd.h>
#include <string.h> 
#include <fcntl.h>
#include <stdlib.h>
int main(){
    //mode_t umask
    //修改调用进程的文件创建权限掩码
    umask(0);
    int fd = open("./tmp.txt",O_RDWR | O_CREAT | O_APPEND,0777);
    if(fd < 0){
      perror("open error");
      return -1;
    }
    char buf[1024] = "nihaoa~~!!";
    int ret = write(fd, buf, strlen(buf));
    if(ret < 0){
      perror("write error");
      return -1;
    }
    lseek(fd, 0, SEEK_SET);
  
    memset(buf, 0x00, 1024);//对buf数组所在的内存空间全部初始化为0,初始化的长度为1024
    ret = read(fd, buf, 1023);
    if(ret < 0){
      perror("read error");
      return -1;
    }
    printf("read buf:[%s]\n", buf);
    close(fd);
    return 0;
  }

在这里插入图片描述

运行如图,系统调用函数还是比较简单的。

标准输入输出的重定向

这里要学习一个dup2函数。

函数dup和dup2提供了复制文件描述符的功能。他们通常用于stdin,stdout或进程的stderr的重定向。

int dup2(int oldfd, int newfd);

dup2用来复制参数oldfd所指的文件描述符,并将oldfd拷贝到参数newfd后一起返回。若参数newfd为一个打开的文件描述符,则newfd所指的文件会先被关闭,若newfd等于oldfd,则返回newfd,而不关闭newfd所指的文件。dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags。

返回值:如果成功则返回新的文件描述符,否则出错返回-1.

由dup2函数返回的新文件描述符一定是当前文件描述符可用的最小值

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <fcntl.h>    
int main(){    
  int fd = open("./tmp.txt",O_RDWR,0777);                                                                               
    
  dup2(fd, 1);    
    
  printf("%d\n",fd);    
    
  fflush(stdout);    
  close(fd);    
    
  return 0;    
} 

此时输出的结果为1,因为oldfd为打开文件的文件描述符,本应该为3,但是调用了dup2函数,此时stdout关闭了,将oldfd复制进去此时在返回新的文件描述符即为1。

基础IO3

模拟实现minishell重定向

1、接收标准输入数据

2、解析命令(判断是否包含重定向符号 > \ >>)

3、如果包含,则认为需要输出重定向,这时候获取重定向符号后边的文件名将重定向符号替换成’\0’

4、在子进程中打开文件,将标准输出重定向到这个文件,进行程序替换

#include <stdio.h>    
#include <stdlib.h>    
#include <fcntl.h>    
#include <string.h>    
#include <unistd.h>    
#include <ctype.h>    
#include <wait.h>                                                                                                       
//之前写过一个关于minishell的文件,先对之前的一些进行封装函数    
    
char buf[1024] = {0};    
char *argv[32];    
int argc = 0;    
    
    
void do_face(){    
  printf("[liuyucheng@localhost]$ ");    
  fflush(stdout);    
  memset(buf, 0x00, 1024);    
  if(scanf("%[^\n]",buf) != 1){    
    getchar();    
  }    
}    
    
void do_parse(){    
  char *ptr = buf;    
  argc = 0;    
  while(*ptr != '\0'){    
   if(!isspace(*ptr)){
     argv[argc++] = ptr; 
     while(!isspace(*ptr) && *ptr != '\0'){
       ptr++;
     }
    }else{
      *ptr = '\0';
      ptr++;
    }
  }
  argv[argc] = NULL;
  return;
}

int main(){
  while(1){
  do_face();
  int redirect = 0;
  char *file = NULL;
  char *ptr = buf;
  while(*ptr != '\0'){
    if(*ptr == '>'){                                                                                                    
      redirect = 1;//清空重定向
      *ptr++ = '\0';
      if(*ptr == '>'){
        redirect = 2;
        *ptr++ = '\0';
      }
      while(isspace(*ptr) && *ptr != '\0'){
        ptr++;
      }
      file = ptr;
      while(!isspace(*ptr) && *ptr != '\0'){
        ptr++;
      }
      *ptr = '\0';
    }
    ptr++;
  }
  do_parse();
  int pid = fork();
  if(pid < 0){
    exit(-1);
  }else if(pid == 0){
    if(redirect == 1){
      int fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0664);
      dup2(fd, 1);
    }else if(redirect == 2){
      int fd = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0664);                                                          
      dup2(fd, 1);
    }
    execvp(argv[0], argv);
    exit(0);
  }
  wait(NULL);
  }
  return 0;
}
Linux ext2文件系统

注:图中应为inode。

基础IO4

上图是一个磁盘文件系统的内置。

每个分区都有一个文件系统,不同的分区拥有不同的文件系统。

什么是inode?inode里面包含了大小,权限,用户,时间,块位置

利用指令ls -i可以查看inode节点号

存储文件的流程

通过inode bitmap在inode table(表结构)找到空闲的inode节点,通过data bitmap在数据块区域找到空闲数据块,将数据块位置信息,记录到inode节点中,将文件数据写入到数据块中;将文件名和inode节点名写入父目录中。

目录文件中:存放了一张目录下有什么文件的表,表中记录了文件名。inode节点号—>目录项

当我们 cat ./a.txt输出文件内容时,在当前目录文件中查找文件名信息,通过文件名获取inode节点号,通过inode节点号,找到inode节点,进而访问数据块,读取数据进行打印。

软硬链接

创建硬链接:ln a.txt b.txt

创建软链接:ln -s a.txt a.soft

硬链接是一个文件的另一个名字,跟源文件并没有什么区别,----inode节点号相同

软链接是一个独立的文件,像是一个文件的快捷方式,----inode节点号不同

删除源文件,软链接失效---->通过记录的源文件名路径查找源文件数据;

硬链接无影响---->通过inode节点找文件只是链接数-1

软链接可以针对目录进行创建,硬链接不可以

软链接可以跨分区建立,硬链接不可以

静态库与动态库

静态库:名字一般为libxxx.a,编译时会整合到可执行程序中。程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库 。优点是运行时不需要外部函数库支持,缺点是编译后程序较大,一旦静态库改变,程序需要重新编译。

动态库:名字一般为libxxx.M.N.so,M为主版本号,N为副版本号 ,程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。优点是运行时,有需要时才动态调用外部库中的函数,节省空间,缺点是运行环境中必须提供相应的库,动态库更新升级方便。

建立静态库

gcc -fPIC -c b.c -o b.o产生位置无关代码

​ 生成静态库gcc -c b.c -o b.o

ar -cr libmytest.a b.o生成静态库

gcc选项:

​ -fPIC:产生位置无关代码

​ --share:生成一个共享库而不是可执行程序

​ ar:静态库打包所有命令

​ -c 创建

​ -r 替换

建立动态库

生成动态库gcc -fPIC -c b.c -o b.o

gcc --share b.o -o libmytest.so生成动态库

库的使用

动态库—>libmytest.so 静态库—>libmytest.a

同名动态和静态库,先链接动态库

链接库的时候:gcc a.c -o main -lmytest。如果报错,找不到库(链接库的查找路径----库的查找路径)

库链接的时候和运行加载的时候都需要在指定目录下

库文件的默认查找路径/lib64 /usr/lib64

设置环境变量:LIBRARY_PATH=.(库的链接路径)------>使用选项 ’-L‘

因为gcc默认是动态链接—因此优先使用动态库生成可执行程序

注意!

通常我们自己链接静态库生成可执行程序的时候,并不是使用-static静态链接,而是将静态库放到指定路径下,然后直接使用gcc -L选项指定库的链接路径链接静态库生成可执行程序

-static:作用是可执行程序使用静态链接生成,不依赖任何动态库

关于基础IO的总结

最重要的还是区分系统调用和库函数调用的区别和用法。在不同的语言中有着不同的用法,Linux下的使用可能还要考虑到进程的创建,程序替换等操作。

还有就是文件系统的理解与inode节点的作用,磁盘的分配有着独特的规则,通过inode来调用分配空间的使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值