【Linux】基础IO——文件操作|文件描述符|重定向|缓冲区

简介: Linux下的文件操作、C语言下的文件操作、文件描述符、重定向的原理和缓冲区的理解。

一、文件操作

1. 文件预备知识

我们在学习下面文件的内容之前,先预备一些基础的文件知识:

  • 文件 = 内容 + 属性,对文件的操作就是对文件内容和文件属性的操作。
  • 当文件没有被操作的时候,文件一般都是在磁盘上存放。
  • 当我们对文件操作时,文件都会被提前加载到内存中,加载的内容至少得有属性。
  • 当文件被加载到内存中时,在Linux下并不一定只有你一个人在打开文件,内存中一定存在大量的不同文件的属性。
  • 因此,打开文件的本质就是将文件属性加载到内存中,OS中一定存在大量被打开的文件,操作系统对这些被打开的文件进行管理需要先描述、再组织,所以需要先构建在内存中的文件结构体(struct file)。
  • 文件可以被分为两部分:磁盘文件被打开的文件(内存文件)
  • 文件是被OS所打开的,是被用户所创建的进程让OS打开的。
  • 我们之前所有的文件操作,都是进程(struct task_struct) 和被 打开文件(struct file) 的关系。

2. 回顾C文件操作

我们曾经学过C语言的文件操作,那么是不是只有C语言有文件操作呢?答案显然是否定的,因为无论哪一门语言(Python、Java、php、go...)他们都有对应的文件操作。无论上层语言如何变化,该语言对应的库函数底层都必须调用文件级别的系统调用来完成对文件的操作。

下面我们来回顾一下C语言的文件操作:

image.png

这里我们需要强调一点:如果没有指明路径,则默认在当前路径下进行文件操作。

💕 w的方式打开,向文件中写入数据(==以写的方式打开文件,如果文件不存在则创建文件==)

image.png
image.png

💕 r的方式从文件中读取数据(==以读的方式打开文件,如果文件不存在则报错==)

image.png
image.png

💕 a的方式打开,向文件中追加数据(==以追加的方式打开文件,如果文件不存在则打开失败==)

image.png
image.png


3. 文件操作的系统调用

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问:

上面的fopen fclose fread fwrite都是C标准库当中的函数,我们称之为==库函数(libc)==。而, open close read write lseek都属于系统提供的接口,称之为==系统调用接口==。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
返回值:
成功:新打开的文件描述符
失败:-1

参数:

  • O_RDONLY: 只读打开
  • O_WRONLY: 只写打开
  • O_RDWR: 读,写打开
    这三个常量,必须指定一个且只能指定一个
  • O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  • O_APPEND: 追加写

image.png

标志位的传递

在C语言中我们经常用一个整形来传递选项,但是如果如果选项较多时,就会造成空间的浪费,这里我们可以通过使用一个比特位表示一个标志位,这样一个int就可以同时传递至少32个标志位,此时的flag就可以看待成位图的数据类型。

#include <stdio.h>
//每个宏只占用一个比特位,该比特位为1说明选项成立。
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10

// 0000 0000 0000 0000 0000 0000 0000 0000
//按位与的结果为1,说明flags对应的比特位为1
void Print(int flags)
{
   
   
    if(flags & ONE) printf("hello 1\n"); //充当不同的行为
    if(flags & TWO) printf("hello 2\n");
    if(flags & THREE) printf("hello 3\n");
    if(flags & FOUR) printf("hello 4\n");
    if(flags & FIVE) printf("hello 5\n");
}


int main()
{
   
   
    printf("--------------------------\n");
    Print(ONE);
    printf("--------------------------\n");
    Print(TWO);
    printf("--------------------------\n");
    Print(FOUR);
    printf("--------------------------\n");

    Print(ONE|TWO);
    printf("--------------------------\n");

    Print(ONE|TWO|THREE);
    printf("--------------------------\n");

    Print(ONE|TWO|THREE|FOUR|FIVE);
    printf("--------------------------\n");

    return 0;
}

image.png

open

open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>

#define LOG "log.txt"

int main()
{
   
   
    int fd = open(LOG, O_WRONLY|O_CREAT);
    if(fd < 0)
    {
   
   
        perror("open");
        return 1;
    }

    close(fd);  // close关闭文件,参数是对应的文件描述符

    return 0;
}

image.png

这里我们发现创建出来的文件权限是乱的,这里我们就需要传入open的第三个参数了,我们可以先使用umask系统调用设置为0,通过手动设置当前进程的权限掩码,从而不使用父进程继承过来的umask。

image.png

image.png


write

write表示向文件中写入数据,下面我们来举例看一下:

image.png

ssize_t write(int fd, const void* buf, size_t count);
# 头文件:<unistd.h>
# fd:目标文件的文件描述符
# buf:要写入数据的来源
# count:要写入数据的字节数
# ssize_t:函数返回值,写入成功返回成功写入的字节数,写入失败返回-1

image.png
image.png

这里我们需要注意几点:

  • 向文件中写入数据时,==如果不指定O_TRUNC选项,新的数据就会覆盖原来的数据==。
  • C语言中字符串是以’\0‘结尾的,==但是文件中的字符串并不以'\0'结尾==,所以当我们向文件中写字符串时,==strlen(str)后面不需要+1==,如果要是加上的话就会出现乱码。

如果我们想要每次都向文件中追加内容怎么办呢?

image.png
image.png


read

read表示从文件中读取数据

image.png

ssize_t read(int fd, void* buf, size_t count);
# 头文件:<unistd.h>
# fd:目标文件的文件描述符
# buf:读取数据存放的位置
# count:要读取数据的字节数
# ssize_t:函数返回值,读取成功返回读取写入的字节数,读到文件末尾返回0,读取失败返回-1

image.png
image.png

由于C语言字符串以'\0'结尾,但是文件字符串数据并不包含'\0',所以这里我们需要预留一个位置,这样的话无论如何buf数组的末尾都能够存放'\0'。


二、文件描述符

1. 文件描述符的理解

进程可以打开多个文件,这也就意味着系统中一定会存在大量的被打开的文件,然而被打开的文件则需要被操作系统管理,我们知道,管理的本质就是先描述在组织,所以操作系统为了管理对应的打开文件,操作系统必定要为文件创建对应的内核数据结构来标识文件,这个内核数据结构就是struct file{}结构体(与C语言的FILE没有关系哦);包含了文件的大部分属性。

而进程和被打开的文件如何关联,也就是说进程和被打开文件的关系是如何维护的?

通过文件打开(open)的返回值和文件描述符进行联系。下面我们通过代码来看一看返回值究竟是多少。

image.png
image.png

这里我们可以看到文件描述符是从3开始的,C 语言程序会默认打开三个流:stdin(标准输入流:键盘)、stdout(标准输出流:显示器)和 stderr(标准错误流:显示器)。这三个流的类型都是FILE,而FILE是结构体。C 语言进行文件操作是使用的是`FILE`,而操作系统使用的是文件描述符fd,==那么结构体FILE中肯定包含文件描述符fd==。所以 0、1、2 就被这三个流使用了。

image.png

进程的tast_struct里面有一个struct files_struct files 指针变量,它指向一个该进程的数据结构对象struct files_struct,该对象中包含了一个指针数组 struct file fd_array[],即进程的文件描述符,数组里面的每一个元素都是一个指针,指向一个struct file对象,这个数组的下标就是我们所得到的用户描述符fd。所以,文件描述符是从0开始的小整数,本质上是文件描述符表中的数组下标。

进程会通过进程控制块(task_struct)中的files变量找到 files_struct结构体,==再通过files_struct 中的文件描述符找到具体文件的内核数据结构file,从而实现数据的读取和写入。==

当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

💕 如何理解Linux下一切皆文件?

image.png

当我们打开一个文件时,操作系统会将该文件加载到内存,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。file结构体中不仅有文件的各种属性(文件的权限、文件的大小...),还有自己的缓冲区,当然了,最后面还有两个函数指针,那么这两个函数指针又是做什么的呢?

其实由于Linux操作系统出现的比C++要早,所以当时没有面向对象的语言程序设计,所以这里使用函数指针来保存函数的地址从而实现面向对象的功能。每一个外设都有对应的IO读写方法,在我们的操作系统的硬件之上有一个该外设对应的驱动程序,该驱动程序层中保存了对应了各个IO设备的读写方法。file结构体中的函数指针就保存了该方法的地址。如果我们需要写入或者读取文件中的信息,只需要通过struct file结构体中的函数指针找到方法的地址调用对应的方法即可。


2. 文件描述符的分配规则

上面我们已经了解了文件描述符,那么他又是如何给我们的文件进行分配的呢?

image.png
image.png

为什么文件描述符是从数组下标为3的地方开始的呢?数组下标0,1,2又去哪儿了呢?其实,这三个下标是被默认打开的三个标准流所占用了——标准输入流stdin、标准输出流stdout 和 标准错误流stderr。他们分别对应键盘文件、显示器文件和显示器文件。因此我们默认打开其他文件时候是从3号开始分配的。

int main()                                                                                                                                             
{
   
                                                   
  printf("stdin->fd:%d\n",stdin->_fileno);       
  printf("stdtout->fd:%d\n",stdout->_fileno);    
  printf("stderr->fd:%d\n",stderr->_fileno);    
  return 0;    
}

image.png

我们知道,C语言中的fopen接口的返回值是FILE* ,其中FILE是一个结构体类型,因为fopen底层是调用了open接口的,所以FILE中一定封装了一个变量来表示fd,这个变量是_fileno

image.png

这里,当我们将0和2号文件描述符关闭后,系统将会自动将他们分配给我们新打开的文件。==所以,文件描述符的分配规则是,从小到大依次搜寻,寻找未被使用的最小的fd作为新打开文件的fd。==


三、重定向

1. 重定向的本质

int main()    
{
   
       
  close(1);    
  int fd = open(LOG,O_WRONLY|O_CREAT,O_TRUNC,0666);                                                                                                      
  assert(fd != -1);    
  printf("hello world\n");    
  fprintf(stdout,"hello chenjiale\n");    

  fflush(stdout);    
  close(fd);    

  return 0;    
}

image.png

1号文件描述符对应的是标准输出(显示器)文件。printf默认是向显示器打印的,当我们将1号文件描述符关闭后,1号文件描述符将会分配给我们新打开的文件,所以数据就会被打印到log.txt文件里。

这种现象就是 重定向,常见的重定向有输出重定向>,输入重定向<和追加重定向>>。重定向的本质就是:上层使用的fd不变,在内核中更改fd对于struct file*的地址(同一个fd指向不同的file对象)。


2. dup2系统调用

Linux操作系统中为我们提供了一个系统调用接口dup2来让我们直接进行冲定向。

image.png

int dup2(int oldfd, int newfd);
# 头文件:<unistd.h>
# oldfd:旧的文件描述符
# newfd:新的文件描述符
# int:函数返回值,成功返回 newfd,失败返回-1

💕 输出重定向

int main()    
{
   
       
  int fd = open(LOG,O_WRONLY|O_CREAT,O_TRUNC,0666);    
  assert(fd != -1);    

  int ret = dup2(fd,1);    
  if(ret == -1){
   
       
    return -1;    
  }    

  printf("hello,fd:%d\n",fd);    
  fprintf(stdout,"hello,fd:%d\n",fd);    

  fflush(stdout);    
  close(fd);    

  return 0;    
}

image.png

image.png

这里我们需要注意的是,dup2的系统调用让newfd成为old的一份拷贝,本质就是将oldfd下标里面存放的file对象的地址拷贝到newfd下标的空间中,拷贝的是fd对应空间中的数据,而并不是两个fd数字之间进行拷贝。

💕 追加重定向

追加重定向和输出重定向的不同点在于在打开文件的时候将O_TRUNC选项去掉,然后换成O_APPEND选项。

int main()    
{
   
       
  int fd = open(LOG,O_WRONLY|O_CREAT,O_APPEND,0666);    
  assert(fd != -1);    

  int ret = dup2(fd,1);    
  if(ret == -1){
   
       
    return -1;    
  }    

  const char* msg = "hello I want to IDM\n";    
  write(1,msg,strlen(msg));    

  fflush(stdout);    
  close(fd);    

  return 0;    
}

image.png

💕 输入重定向

int main()    
{
   
       
  int fd = open(LOG,O_RDONLY);    
  assert(fd != -1);    

  int ret = dup2(fd,0);    
  if(ret == -1){
   
       
    return -1;    
  }    

  char buf[64];    
  while(fgets(buf,sizeof(buf) - 1,stdin)!=NULL)    
  {
   
       
    buf[strlen(buf)] = '\0';    
    printf("%s",buf);                                                                                                                                    
  }    

  close(fd);    

  return 0;    
}

image.png


四、缓冲区

进程向磁盘文件中写数据时,由于磁盘属于外设,进程直接向磁盘文件中写数据的效率非常低,所以有了缓冲区,进程可以将自己的数据拷贝到缓冲区中,再由缓冲区将数据写入到磁盘文件中去。在计算机中,缓冲区的意义是节省进程进行数据 IO 的时间。虽然我们认为 fwrite 是将数据写入到文件的函数,但 fwrite 本质上是进行数据拷贝的函数,因为 fwrite 函数只是将数据从进程拷贝到缓冲区中,并没有真正将数据写入到磁盘文件中。

1. 缓冲区的刷新策略

💕 三种刷新策略:

  • 立即刷新 (无缓冲): 缓冲区中一出现数据就立马刷新,这种很少出现;
  • 行刷新 (行缓冲): 每拷贝一行数据就刷新一次,显示器采用的就是这种刷新策略,因为显示器是给人看了,而按行刷新符合人的阅读习惯,同时刷新效率也不会太低;
  • 缓冲区满 (全缓冲): 待数据把缓冲区填满后再刷新,这种刷新方式效率最高,一般应用于磁盘文件。

💕 两种特殊情况:

  • 用户使用 fflush 等函数强制进行缓冲区刷新
  • 进程退出时一般都要进行缓冲区刷新
int main() {
   
       

    printf("hello printf\n");    
    fprintf(stdout, "hello fprintf\n");    
    const char* fputsString = "hello fputs\n";    
    fputs(fputsString, stdout);    

    const char* msg = "hello write\n";    
    write(1, msg, strlen(msg));    

    return 0;    
}

image.png
image.png

当我们在程序结尾使用fork函数创建一个子进程后:

int main() {
   
       

    printf("hello printf\n");    
    fprintf(stdout, "hello fprintf\n");    
    const char* fputsString = "hello fputs\n";    
    fputs(fputsString, stdout);    

    const char* msg = "hello write\n";    
    write(1, msg, strlen(msg));    

    fork();    

    return 0;    
}

image.png

这是什么原因呢?为什么 write 函数重定向了一次,但是其他三个函数却重定向了两次呢?下面我们会细细解释一下。


2. 缓冲区的位置

我们谈论的所有缓冲区都不在操作系统内核中,而是位于用户级语言层面;实际上,对于C语言来说,缓冲区位于 FILE 结构体中。

对于C语言的printf、fwrite、fputs 等库函数会自带缓冲区,而 write 系统调用没有带缓冲区;同时,我们这里所说的缓冲区,都是用户级缓冲区。那这个缓冲区谁提供呢? printf、fwrite、fputs 是库函数, write 是系统调用,库函数在系统调用的 “上层”, 是对系统调用的 “封装”,但是 write 没有缓冲区,而 printf、fwrite、fputs 有,足以说明该缓冲区是二次加上的,又因为是C库函数,所以是由C标准库提供的。

显示器采用行缓冲,所以在 fork 之前 printf、fprintf、fputs 三条语句的数据均已刷新到显示器上了,而对于进程数据来说,如果数据位于缓冲区内,那么该数据属于进程,此时 fork 子进程也会指向该数据;但如果该数据已经写入到磁盘文件了,那么数据就不属于进程了,此时 fork 子进程也不在指向该数据了;所以,这里 fork 子进程不会做任何事情。

我们使用重定向指令将本该写入显示器文件的数据写入到磁盘文件中,而磁盘文件采用全缓冲,所以 fork 子进程时 printf、fprintf、fputs 的数据还存在于缓冲区中 (缓冲区没满,同时父进程还没有退出,所以缓冲区没有刷新),也就是说,此时数据还属于父进程,那么 fork 之后子进程也会指向该数据;而 fork 之后紧接着就是进程退出,父子进程某一方先退出时会刷新缓冲区,由于刷新缓冲区会清空缓冲区中的数据,为了保持进程独立性,先退出的一方会发生 写时拷贝,然后向磁盘文件中写入 printf、fprintf、fputs 三条数据;然后,后退出的一方也会进行缓冲区的刷新;所以,最终 printf、fprintf、fputs 的数据会写入两份 (父子进程各写入一份),且 write 由于属于系统调用没有缓冲区,所以只写入一份数据且最先写入。


3. 简单模拟实现缓冲区

mystdio.h

#pragma once

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define SIZE 1024   // 缓冲区大小
#define SYNC_NOW   1    // 无缓冲
#define SYNC_LINE  2    // 行缓冲
#define SYNC_FULL  4    // 全缓冲

typedef struct FILE_
{
   
   
    int flags;  // 缓冲区刷新策略
    int fileno; // 文件描述符
    int size;   // buffer当前的使用量
    int capacity;   // buffer的总容量
    char buffer[SIZE];  //缓冲区
}FILE_;


FILE_* fopen_(const char* pathname, const char* mode);
void fwrite_(const void* ptr, int num, FILE_* fp);
void fflush_(FILE_* fp);
void fclose_(FILE_* fp);

mystdio.c

#include "myStdio.h"

FILE_* fopen_(const char* pathname, const char* mode)
{
   
   
    int flags = 0;
    int defaultMode = 0666; // 默认创建权限

    if(strcmp(mode, "r") == 0)
    {
   
   
        flags |= O_RDONLY;
    }
    else if(strcmp(mode, "w") == 0)
    {
   
   
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
    }
    else if(strcmp(mode, "a") == 0)
    {
   
   
        flags |= (O_WRONLY | O_CREAT | O_APPEND);
    }
    else
    {
   
   
        // TODO:r+, w+...
    }

    int fd = 0;
    if(flags & O_RDONLY)  fd = open(pathname, flags);
    else  fd = open(pathname, flags, defaultMode);

    if(fd < 0)
    {
   
   
        const char* err = strerror(errno);
        write(2, err, strlen(err));
        return NULL;    // 打开文件失败返回NULL的原因
    }

    FILE_* fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp != NULL);

    fp->flags = SYNC_LINE; // 默认设置成行刷新
    fp->fileno = fd;
    fp->size = 0;
    fp->capacity = SIZE;
    memset(fp->buffer, 0, SIZE);

    return fp;  // 打开文件成功返回FILE*的原因
}

void fwrite_(const void* ptr, int num, FILE_* fp)
{
   
   
    // 数据写入到缓冲区
    memcpy(fp->buffer + fp->size, ptr, num); // 这里不考虑缓冲区溢出的问题
    fp->size += num;

    // 是否刷新缓冲区
    if(fp->flags & SYNC_NOW)
    {
   
   
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; // 清空缓冲区
    }
    else if(fp->flags & SYNC_LINE)
    {
   
   
        // 不考虑abcd\nef的情况
        if(fp->buffer[fp->size - 1] == '\n')
        {
   
   
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0; // 清空缓冲区
        }
    }
    else if(fp->flags & SYNC_FULL)
    {
   
   
        if(fp->size == fp->capacity)
        {
   
   
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0; // 清空缓冲区
        }
    }
    else
    {
   
   
        return;
    }
}

void fflush_(FILE_* fp)
{
   
   
    if(fp->size > 0)  write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno);    // 强制刷新内核缓冲区,将数据刷新到外设中
    fp->size = 0;    // 清空缓冲区
}

void fclose_(FILE_* fp)
{
   
   
    fflush_(fp);
    close(fp->fileno);
    free(fp);
}

main.c

#include "myStdio.h"
#include <stdio.h>

int main()
{
   
   
    FILE_* fp = fopen_("log.txt", "w");
    if(fp == NULL)
    {
   
   
        return 1;
    }

    int cnt = 10;
    const char* msg = "hello world\n";
    while(1)
    {
   
   
        --cnt;
        fwrite_(msg, strlen(msg), fp);
        sleep(1);
        printf("count:%d\n", cnt);
        if(cnt == 0)   break;
    }
    fclose_(fp);

    return 0;
}
监控脚本
while :; do cat log.txt ; sleep 1; echo "------------------"; done

我们向外设中写入数据其实一共分为三个步骤 – 先通过 fwrite 等语言层面的文件操作接口将进程数据拷贝到语言层面的缓冲区中,然后再根据缓冲区的刷新策略 (无、行、全) 通过 write 系统调用将数据拷贝到 file 结构体中的内核缓冲区中,最后再由操作系统自主将数据真正的写入到外设中。(所以 fwrite 和 write 其实叫做拷贝函数更合适)。==这里操作系统的刷新策略比我们应用层 FILE 中的缓冲区的刷新策略要复杂的多,因为操作系统要根据不同的整体内存使用情况来选择不同的刷新策略,而不仅仅是死板的分为分行缓冲、全缓冲、无缓冲这么简单。==

操作系统提供了一个系统调用函数 fsync,其作用就是将内核缓冲区中的数据立刻直接同步到外设中,而不再采用操作系统的刷新策略。

image.png


相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
2月前
|
存储 缓存 固态存储
|
3月前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
609 2
|
4月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
165 3
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第17天】重定向在Linux中改变命令I/O流向,默认有&quot;&gt;&quot;覆盖输出至文件及&quot;&gt;&gt;&quot;追加输出至文件末尾,便于保存结果;使用&quot;&lt;&quot;从文件读取输入而非键盘,高效处理数据。文件描述符如0(stdin)、1(stdout)、2(stderr)标识I/O资源,支持读写操作。管道以&quot;|&quot;连接命令,使前一命令输出成为后一命令输入,如排序用户或找出CPU占用最高的进程,构建复杂数据处理流程。
50 9
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第14天】输出重定向可将命令结果存入文件,如`&gt;`覆盖写入或`&gt;&gt;`追加写入。输入重定向从文件读取数据,如`&lt;`代替键盘输入。这些操作利用文件描述符(如0:stdin, 1:stdout, 2:stderr)管理I/O。管道`|`连接命令,使前一命令输出作为后一命令输入,便于数据处理,如排序用户`sort -t: -k3 -n /etc/passwd | head -3`或查找CPU占用高的进程`ps aux --sort=-%cpu | head -6`。
42 4
|
4月前
|
Unix Linux Shell
Linux I/O 重定向简介
Linux I/O 重定向简介
40 2
|
3月前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
4月前
|
运维 Rust 监控
Linux高效运维必备:fd命令深度解析,文件描述符管理从此得心应手!
【8月更文挑战第23天】本文介绍了一款名为fd的命令行工具,该工具基于Rust语言开发,旨在以更直观的语法和更快的速度替代传统的`find`命令。通过本文,您可以了解到如何安装fd以及一些基本用法示例,比如使用正则表达式匹配文件名、排除特定目录等。此外,文章还展示了如何结合`ps`和`lsof`命令来查找特定文件并显示其文件描述符,从而帮助您更好地管理和监控Linux系统中的文件与进程。
142 0
|
4月前
|
存储 Linux 数据处理
在Linux中,管道(pipe)和重定向(redirection)的是什么?
在Linux中,管道(pipe)和重定向(redirection)的是什么?
|
4月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
34 0