[Linux] File operation (1)

Table of contents

Preliminary knowledge

Review the C language file interface

fopen()

Write class: fwrite(), fprintf(), fputs()

Read class: fgets()

system interface

open()

How can one parameter pass multiple options?

close()

write()

read()


 

Preliminary knowledge

Before formally explaining the document, we need to have some preliminary knowledge:


1. File = file content + file attributes.


2. Operations on files: 1. Operations on content 2. Operations on attributes


3. The file is on the disk (hardware), what is our process of accessing the file?

First write the code to access the file -> compile -> exe executable program -> run -> access to the file.

The essence of accessing files is that processes are accessing files . Processes need interfaces to access files. The interfaces we have learned so far are all language interfaces, not system interfaces.

The disk is a kind of hardware, and only the operating system has the permission to write in the hardware. If ordinary users also want to write, the operating system must provide an interface for file system calls.

And the related interfaces of the file operation of C language that we usually use are the encapsulation of the system interface , which will make the interface more convenient and easy for users to use.

But in this way, the file operation interfaces of each language such as (C++ and Iava) will be different, but the bottom layer is the system interface , because there is only one set of such interfaces, but the encapsulation methods are different.

The advantage of encapsulation is that the code can be run across platforms (such as using conditional compilation and dynamic tailoring). If the user directly uses the system interface, the code cannot be run on another platform.


4. The display is a kind of hardware, which is essentially a file under linux ! Printf printing to the display is essentially a kind of writing; it is the same as writing to a file on a disk!


5. Under Linux, everything is a file. We can only have a rational understanding of it now.

The files we used to understand: read to read, write to write.

Display: printf/cout -> a write

keyboard: scanf/cin -> a read

The above are all from the perspective of the program we write. In the future, our program will be loaded into the memory, which is equivalent to the perspective of the memory. The keyboard is equivalent to transferring our data to the memory (input), and the memory will The read data is refreshed to a file or display (output).

 This is equivalent to an IO operation.

 We are looking back, what is a file?

From the perspective of the system, a device that can be read by input or written by output is called a file.

Files in the narrow sense : ordinary disk files

Files in a broad sense : monitors, network cards, sound cards, graphics cards, disks, etc., almost all peripherals can be called files.    

Review the C language file interface

fopen()

Let's first use man fopen to see the usage of the function

 This function is used to open the file,

        1.FIFE* We call it a file pointer.

        2. The first parameter is the path of the function to be opened

        3. The second parameter is the opening mode , that is, how to open it. There are 6 types in total:

r (read): read only the file


r+ (read and write): read and write files.


w (read): Clear the content of the file first, and then start writing from the file. If the file does not exist, create a new file.


w+ (read and write): Clear the content of the file first, and then read and write the file. If the file does not exist, create a new file.


a (write): start writing to the end of the file, which is equivalent to an append operation. If the file does not exist, a new file will be created.

a+ (read and write): start writing from the end of the file, start reading from the beginning of the file, if the file does not exist, a new file will be created.

r is to read files, this will be more convenient after I explain w (write) first.

Let's take a look at w : first write the following code in vim

#include<stdio.h>    
    
int main()    
{    
  FILE* fp = fopen("log.txt","w");    
  if(fp == NULL)    
  {    
    perror("fopen");    
    return 1;    
  }    
  //进行文件操作                                                                                                                                       
  fclose(fp);    
                                                                                                                                             
  return 0;                                                                                                                                  
}     

 We use the w mode to open this, and there is no "log.txt" file in our current path.

We exit make to compile, then run

 In this way, because the original file does not exist, it will automatically create a new file for us.

Then the question is: I only wrote a log.txt in my code, so how do I know where it is or where it was created?

When a process is running, each process will record its current working path ! This path is called the current path, and the search and creation of log.txt are performed in the current path.


Then if we write something to the log.txt file at this time, and then run the program in w mode, what will happen? 

open the file in w mode 

 Then enter "hello, world" into the file and run. 

 We found that there is still content in log.txt at the beginning, and then after running the program in w mode, the content inside is cleared.

This verifies that the w mode we just said will clear the file content before writing.

So what if we don't want the file to be emptied, but write to the content behind the file? At this time, you need to open it in a/a+ mode.

Let’s talk about the writing interface fwrite right away. It’s good to know that this is writing to a file.

Then we exit, compile and run: 

It is found that each run does not clear the file, but appends it to continue writing.

Write class: fwrite(), fprintf(), fputs()

We just opened the file, and we need to operate on the file accordingly. This is the usage of fwrite:

         1. The first parameter ptr is a pointer to the array of elements to be written.

         2. The second parameter size is the size of each element in bytes.

         3. The third parameter is the number of elements , and the size of each element is size bytes

4. The fourth parameter is the FILE pointer, which is the file pointer          returned after fopen() opens the file .

In code, we write:

 

 Because s1 is a string, strlen directly calculates the size of the entire string, so the third parameter only needs to pass in 1, and the size is the size of the entire string.

At this point, we output log.txt and successfully write it to the file

 


Then introduce the usage of fprintf(), the function prototype is:

​int fprintf(FILE *stream, const char *format, ...);

        1. The first parameter is still a file pointer.

        2. The second parameter is our normal formatted output.

When writing in code, write like this

 Then it was successfully written to the file:


fputs is also written to the file, and the function prototype is

int fputs(const char *s, FILE *stream);

        1. The first parameter is the string content we want to write.

        2. The second parameter is still a file pointer. That is, the fp at the beginning.

Let's use it, it's also relatively simple

 

 

 This will successfully write to the file.


Read class: fgets()

Similarly, let's take a look at the function prototype first:

char *fgets(char *s, int size, FILE *stream);

        1. The first parameter s is an output parameter , and the read result will be stored in the character array pointed to by s.

        2. The second parameter size is the maximum number of bytes to read. If the file content itself is smaller than size, then only the file content will be read. If it is larger, then size bytes will be read.

        3. The third is also a file pointer, which is obtained through fopen().

Take a look at its return value:

 fgets() returns s if it succeeds, or NULL if it fails.


Specifically how to use it?

 Then we already have content in the log.txt file at this time, we can see that the content is output when we run the program

 In the code above, we mentioned stdout, which is called the standard output stream .

When executing a program, our C language will open three standard input and output streams by default:

        1.stdin standard input, corresponding to the keyboard

        2.stdout standard output, display

        3.stderr standard error, monitor

Let's take a look at it with man stdin.

 It is found that the type is a file pointer. In fact, this also explains a truth in disguise: everything under linux is a file!

But how to understand these is still a problem. After we explain the system interface, I believe it will be much more thorough when we come back.

system interface

There are 4 system interfaces here: open, close, read, write

open()

Its function is to open and possibly create a file or device. It can be understood as opening a file for the time being.

        1. The first parameter pathname is the same as the interface just now, it is the file path to be opened

        2. The second parameter, flags, is some options . There are many options, here are some commonly used explanations.

        3. The third parameter will be discussed later.

For the second parameter:

Flags must contain one of the above three, representing O_RDONLY (read only read-only), O_WRONLY (write only write-only), and O_RDWR (read write read and write).

 Then you can add other options later. Then there is only one flags parameter, how to pass in many options?

How can one parameter pass multiple options?

Bit operations are used here: that is, a state can be identified by using a non-repeated bit in int.

For example, 0000 0001 can represent one state, 0000 0010 represents the second state, and so on.

When we need to check whether there is the first state, we can let the flag be &0x1 (0000 0001) at this time. At this time, if the result is 1, it means that the last bit of the flag has 1, which means it has this option. Execute the corresponding operation.

Similarly, to detect whether there is a second state, just continue to judge flag&0x2(0000 0010)==1. If it is equal, it means that the penultimate bit of the flag has 1, otherwise there is no.

So how to let the flag have these two states at the same time (that is, how to pass in two options at the same time)?

We just need to let the two states | (bitwise OR)  it. Suppose

#define ONE 0X1 // 0000 0001
#define TWO 0X2 // 0000 0010

Then flag = ONE | TWO, at this time the bit of the flag is 0000 0011

Then we judge the flag again, at this time flag & 0x1 = 1, indicating that there is the first option; flag & 0x2 = 1, also indicating that there is the second option, so we are equivalent to passing in multiple options.


Let's use simple code to do this.

At this time, the result we expect should be output 1,3 and 1,2,3.

At this point, we get the result we want, and by the way, we can pass multiple options in one parameter.


Similarly, back to open, although many options in its flags are uppercase, they are also defined macros, which are also numbers in essence , and are identified by different bits.

Then let's look at the return value of open:

 You can see that if open returns a new file descriptor if it succeeds , it returns -1 if it fails.

We will explain this file descriptor in detail in the next chapter. Now you only need to know that the file descriptor is an integer.


Knowing the above, let's start using the open system interface. First, open it in a write-only mode.

int main()    
{    
  int fd = open("log.txt",O_WRONLY);    
  if(fd < 0)    
  {    
    perror("open");    
    return 1;    
  }    
  //open successful    
  printf("open success,fd: %d\n",fd);    
}            

We do not have a log.txt file in the directory at this time, the fopen interface of the c language, if there is no file, a new file will be created for us if there is no file, what about the system interface?

Let's compile and run:

We found that the system could not find this file, which means that the system only writes this interface and will not directly create a new file for us . The reason why the C language is possible is because the system interface is encapsulated, which simplifies our usage of.

In fact , a very simple action you see at the application layer may require a lot of work at the system interface or OS level !

So what if the file doesn't exist and you want to create it? At this time, you need to pass in another option O_CREATE.

Then we pass it two options as mentioned above, then put the two options |

int fd = open("log.txt",O_WRONLY | O_CREAT);

 

At this point we have successfully opened it.


However, let's take a look at the permissions of the file we created:

How could this be so? We don't want this, what if we want to specify permissions?

This goes back to the third parameter mode when we first talked about open, which is the permission given to the file when creating a new file.

For example, the permission we give to the file is 0666 .

int fd = open("log.txt",O_CREAT | O_WRONLY,0666); 

In this way, this permission finally looks normal, but why is there one less?

Our ideal is -rw-rw-rw, how is this -rw-rw-r--, where is the missing w?

This is due to the existence of the mask umask, which I said in detail in the understanding of permissions in the previous article.

The default mask is 002, and each permission cannot be the same as the bit with a bit of 1 in the mask. The bit of 2 in 002 is 010, which cannot be the same as the second bit with a value of 1, so you can only set The w permission is removed, because w is the corresponding second bit.

We can set the umask of this process to 0 in the program, and then the problem is solved.

At this point, the correct result is obtained:

close()

This interface is relatively simple, just pass in the file descriptor directly, and then close the file.

write()

First look at the usage of write

 Function prototype:

ssize_t write(int fd, const void *buf, size_t count);

        1.fd is the file descriptor returned by open().

        2.buf is the content to be written

        3.count is how many bytes are written to the file.

Let's demonstrate directly with code:

 At this point we exit, compile and run

We found that it has been successfully written at this time. 

If we change the written content at this time:

Then run again:

We found that the original hello and write were not cleared, but abcd was written directly from scratch, and then overwrote part of the original content. If we want to clear the content, we need to use a new option: O_TRUNC

 

This option will clear the content of the original file. So we add this option to the open code:

int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666); 

Then exit make to compile and run again:

It is found that the content of the original file has been cleared at this time, and then written into abcd.


If we want to add content, that is, do not clear it, and continue writing directly behind the file, we can replace O_TRUNC with O_APPEND . This will not continue the demonstration.

read()

Let's check the usage first:

The usage of the function is very similar to that of write, except that the content of the file is read into buf, which means that buf becomes an output parameter, and the number of bytes read is count.

Finally, the read content will be written in the buffer, and then we can finally output it.

 We edit the following in the log.txt file:

 Then compile and run the program:

 

 It was read successfully.

This is the end of part of the content about file operations. In the next chapter, we will really enter the category of files and begin to explain a series of related knowledge such as file descriptors.

 

Guess you like

Origin blog.csdn.net/weixin_47257473/article/details/131857917