Linux系统文件编程详解
Linux文件操作命令
ls命令
-
功能:列出指定目录下的内容及其相关属性信息
-
语法:ls [参数选项] [文件]
-
常见参数:
-a 显示所有文件以及目录,包括 '以"."开头的隐藏文件'
-l 使用长格式列出文件及目录信息 -r 将文件以相反次序显示(默认依英文字母次序) -t 根据最后的修改时间排序 -A 显示所有文件以及目录,但不列出 "."(当前目录)
及".."(父目录)
-S 根据文件大小排序 -R 递归列出所有子目录 -
示例:
# 列出文件详情 coder@coder-vm:/$ ls -l # 列出当前工作目录下所有名称是"s"开头的文件,在ubuntu中还会将文件夹以及其中的文件信息 coder@coder-vm:/$ ls -ltr s* # 列出指定目录下的详细信息包含子目录 coder@coder-vm:/$ ls -lR /sys
cp命令
-
功能:复制文件或目录
-
语法:cp [参数选项] [文件1、文件2、…|文件夹] [目标地址]
-
常见参数:
-f 若目标文件已存在,则会 直接覆盖
原文件-i 若目标文件已存在,则会 询问
是否覆盖-p 保留源文件或目录的所有属性 -r 递归复制文件和目录 -d 当复制符号连接时,把目标文件或目录也建立为符号连接,并指向与源文件或目录连接的原始文件或目录 -l 对源文件建立硬连接,而非复制文件 -s 对源文件建立硬连接,而非复制文件 -b 覆盖已存在的文件目标前将目标文件备份 -v 详细显示cp命令执行的操作过程 -a 等价于 dpr
选项 -
示例:
# 复制目录 -R 等价于 -r coder@coder-vm:~$ cp -R Code/ /Code_1/ # 复制多个文件,Temp 要存在 coder@coder-vm:~/Code$ cp -r CMakeLists.txt main.cpp ../Temp
mkdir命令
-
功能:创建目录
-
语法:mkdir [参数选项] [文件夹1、文件夹2、…]
-
常见参数:
-p 递归创建多级目录 -m 建立目录的同时设置目录的权限 -z 设置安全上下文 -v 显示目录的创建过程 -
示例:
# 在指定位置创建文件夹 coder@coder-vm:~$ mkdir dir # 递归创建文件夹 coder@coder-vm:~$ mkdir -p dir/code/
mv命令
-
功能:可以移动文件或对其改名
-
语法:mv [参数选项] [源文件|源目录] [目标文件|目标目录]
-
常见参数:
-i 若存在同名文件,则向用户询问是否覆盖 -f 覆盖已有文件时,不进行任何提示 -b 当文件存在时,覆盖前为其创建一个备份 -u 当源文件比目标文件新,或者目标文件不存在时,才执行移动此操作 -
示例:
# 文件重命名 coder@coder-vm:~$ mv main.cpp hello.cpp # 剪切文件夹到指定目录下,指定目录提前创建 coder@coder-vm:~$ mv Code/ Code_mv/
cat命令
-
功能:查看文件内容
-
语法:cat [参数选项] [文件]
-
常见参数:
-n 显示行数, 空行也编号
-s 显示行数, 多个空行算一个编号
-b 显示行数, 空行不编号
-E 每行结束处显示 $
符号-T 将TAB字符显示为 ^|
符号-v 使用 ^
和M-
引用,除了LFD
和TAB
之外-e 等价于 -vE
组合-t 等价于 -vT
组合-A 等价于 -vET
组合–help 显示帮助信息 –version 显示版本信息 -
示例:
# 查看文件内容和行号 coder@coder-vm:~$ cat -n Code/CMakeLists.txt
rm命令
-
功能:删除一个目录中的一个或多个文件或目录
-
语法:rm [参数选项] [文件|文件夹]
-
常见参数:
-f 忽略不存在的文件,不会出现警告信息 -i 删除前会询问用户是否操作 -r/-R 递归删除 -v 显示指令的详细执行过程 -
示例:
# 删除前逐一询问确认 coder@coder-vm:~$ rm -i Code/CMakeLists.txt # 递归删除目录及目录下所有文件 coder@coder-vm:~$ rm -rf Code/
grep命令
-
功能:基于正则表达式的文本搜索
-
语法:grep [参数选项]
-
常见参数:
-i 搜索时,忽略大小写 -c 只输出匹配行的数量 -l 只列出符合匹配的文件名,不列出具体的匹配行 -n 列出所有的匹配行,显示行号 -s 不显示不存在、没有匹配文本的错误信息 -v 显示不包含匹配文本的所有行 -w 匹配整词 -x 匹配整行 -r 递归搜索 -q 禁止输出任何结果,已退出状态表示搜索是否成功 -b 打印匹配行距文件头部的偏移量,以字节为单位 -o 打印匹配行距文件头部的偏移量,以字节为单位 -h 查询多文件时不显示文件名 -
示例:
# 多文件查询并支持使用通配符 coder@coder-vm:~$ grep zwx file_* /etc/hosts file_1:zwx file_1:zwx file_1:zwxddkjflkdjfdlkfjlsdkj file_2:zwx file_4:dkfjlzwxejfkje file_4:zwx djfkdjf file_4:zwxedkfgj
more命令
-
功能:用于将内容较长的文本文件内容进行分屏显示,并且支持在显示时定位关键字
-
语法:grep [参数选项] [文件]
-
常见参数:
-num 指定每屏显示的行数 -l more在通常情况下把 ^L
当作特殊字符, 遇到这个字符就会暂停,-l选项可以阻止这种特性-f 计算实际的行数,而非自动换行的行数 -p 先清除屏幕再显示文本文件的剩余内容 -c 与 -p
相似,不滚屏,先显示内容再清除旧内容-s 多个空行压缩成一行显示 -u 禁止下划线 +/pattern 在每个文档显示前搜寻该字 pattern
,然后从该字串之后开始显示+num 从第 num
行开始显示 -
内部命令
Space键 显示文本的下一屏内容 B键 显示文本的上一屏内容 斜线符\ 向下n行,需要定义,默认为1行 H键 显示帮助屏 Enter键 向下n行,需要定义,默认为1行 Q键 退出more命令 = 输出当前的行号 :f 输出文件名和当前的行号 V 调用vi编辑器 ! 调用Shell,并执行命令 -
示例
# 显示文件file的内容,显示之前先清屏,附已显示的百分比 coder@coder-vm:~$ more -dc Code/CMakeLists.txt # 显示文件file的内容,每10行显示一次,而且在显示之前先清屏 coder@coder-vm:~$ more -c -10 Code/CMakeLists.txt # 从第 20 行开始显示 file 之文档内容 coder@coder-vm:~$ more +20 Code/CMakeLists.txt
Linux文件系统
在Linux中可以使用ls -l
命令看到当前文件夹下全部文件或文件夹的属性信息:
coder@coder-vm:~/Code$ ls -l
-rw-rw-r-- 1 coder coder 110 1月 16 09:50 CMakeLists_bk.txt
在每一个文件或者文件夹名称的前边详细描述了文件的信息,信息解析情况如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FcQfiG4M-1643122019252)(\image\文件属性信息解析.png)]
在Linux系统中内核为每一个新创建的文件分配一个i(索引 index node)
节点。文件的属性信息就保存在索引节点里,访问文件时索引节点被复制
到内存中,从而实现文件的快速访问。索引节点是Linux虚拟文件系统(Virtual File System VFS)的基本概念之一。
i节点
不但包含某个文件属性信息,还包含指向存储文件数据的数据块的指针
。在Linux中一个文件除了纯数据本身之外,还必须包含对这些纯数据的管理信息,这些管理信息称为元数据(mata data)
保存在文件的i节点
之中。因此,可以简单的将i节点
看成一个结构体其中保存了一些文件信息:
- 节点编号,每个
i节点
都有一个唯一的编号。 - 文件类型。
- 权限信息。
- 文件的字节数。
- 文件的拥有者uid。
- 文件的所属组gid。
- 文件的时间戳,
i节点最近变动时间(ctime)
、内容最近变动时间(mtime)
和文件上次访问时间(atime)
。 - 硬盘链接数。
- 纯数据块的指针位置。
i节点
的总数在格式化时给定,每个i节点大小一般为128字节或256字节
,但实际是每1KB或2KB设置一个i节点
,用df
命令可以查看i节点总数
和已使用i节点数量
,每次创建文件可用i节点
就会减少1个,命令如下所示:
# 查看i节点信息
df -i
# 查看系统使用信息
df -h
在Linux中每个文件都有一个i节点号
与之对应,可用通过命令查看某个文件的i节点号
:
coder@coder-vm:~/Code$ ls -i CMakeLists.txt
1973171 CMakeLists.txt
这种i节点号
虽然对应着具体文件,但i节点号
不能被人所识别,人所能识别的就只有名称字符。因此,Linux系统中存在一个目录项
概念,目录中包含了i节点号
和文件名
对应关系,当用户打开文件时一共分为三个步骤:系统通过目录项找到这个文件名对应的i节点号
、通过i节点号获得节点信息
、根据节点信息找到文件块读取数据
。
Linux文件编程
在Linux中设备或者文件都是通过文件描述符进行,当打开或者创建一个文件的时候,内核像进程返回一个文件描述符(非负整数)
并基于该文件描述符进行操作。
打开文件
- 相关函数
#include <fcntl.h>
int open(const char * pathname, int flags);
int open(const char * pathname, int flags, mode_t mode);
- 参数说明
参数名称 | 说明 |
---|---|
pathname | 文件路径名称,相对路径 或绝对路径 |
flags | 文件打开方式 |
mode | 用来规定文件的所有者 、文件的用户组 及系统中其他用户的访问权限 |
- flags标志说明
标志位 | 说明 |
---|---|
O_RDONLY | 打开一个只供读取文件(单选) |
O_WRONLY | 打开一个只供写入文件(单选) |
O_RDWR | 打开一个可供读写的文件(单选) |
O_APPEND | 数据写入时追加到文件末尾 |
O_CREAT | 打开文件时如果不存在就创建文件 |
O_EXCL | 在使用了O_CREAT 字段情况下,文件存在文件打开失败 |
O_TRUNC | 打开文件时将文件清空 |
O_DSYNC | 每次写入时,等待数据写到磁盘上 |
O_RSYNC | 每次读取时,等待相同部分先写到磁盘 |
O_SYNC | 已同步方式写入文件,强制刷新缓冲到输出文件 |
- mode标志说明
标志位 | 说明 |
---|---|
S_IRUSR | 文件所有者的读取权限位 |
S_IWUSR | 文件所有者的写入权限位 |
S_IXUSR | 文件所有者的执行权限位 |
S_IRWXU | S_IRUSR|S_IWUSR|S_IXUSR |
S_IRGRP | 文件用户组的读取权限位 |
S_IWGRP | 文件用户组的写入权限位 |
S_IXGRP | 文件用户组的执行权限位 |
S_IRWXG | S_IRGRP|S_IWGRP|S_IXGRP |
S_IROTH | 文件其他用户的读取权限位 |
S_IWOTH | 文件其他用户的写入权限位 |
S_IXOTH | 文件其他用户的执行权限位 |
S_IRWXO | S_IROTH|S_IWOTH|S_IXOTH |
- 示例
#include <iostream>
#include <fcntl.h>
using namespace std;
int main() {
// 使用读写模式打开当前 exe 同级目录下的 test_data.txt
int fd = open("./test_data.txt", O_RDWR);
if (fd != -1) {
cout << "test file open success." << endl;
} else {
cout << "test file open fail." << endl;
}
return 0;
}
关闭文件
文件不在使用的时候就要将其关闭,关闭后文件描述不再指向任何文件,如果不关闭文件将会造成系统文件描述符资源耗尽
,此时,不可再次打开文件。因此,打开文件和关闭文件应成对使用,文件关闭时返回一个整数其中最常见的0表示关闭成功
和-1表示关闭失败
。
- 相关函数
#include <unistd.h>
int close(int fd);
- 参数说明
参数名称 | 说明 |
---|---|
fd | 文件创建或打开后或返回一个文件描述符 是一个整型变量 |
- 示例
#include <iostream>
#include <fcntl.h>
using namespace std;
int main() {
int fd = open("./test_data.txt", O_RDWR);
if (fd != -1) {
cout << "test file open success." << endl;
} else {
cout << "test file open fail." << endl;
return -1;
}
close(fd);
return 0;
}
读取文件
通过打开文件后返回文件描述符,可以读取文件中的数据,可以一次读取指定字节长度
的内容到指定的缓存中
,读取时返回值是实际读取的字节数,可通过目标读取字节
和实际读取字节
的比较判断是否读取到文件的结尾。
- 相关函数
#include <unistd.h>
ssize_t read(int fd, void * buf, size_t count);
- 参数说明
参数名称 | 说明 |
---|---|
fd | 已经打开的文件描述符 |
buf | 读取到的文件会存放到buf内存地址中 |
count | 每次读取的文件数据的长度 |
- 常见返回异常值
参数名称 | 说明 |
---|---|
EINTR | 此调用被信号所中断 |
EAGAIN | 当使用不可阻断I/O时或无数据可读 |
EBADF | 参数fd无效或者文件已经关闭 |
- 示例
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main() {
int fd = open("./test_data.txt", O_RDWR);
// ........
size_t read_size = 1024;
char read_buffer[1024]; // 读取数据缓存区
while(true) {
size_t read_real_size = read(fd, read_buffer, read_size);
if (read_real_size > 0) {
cout << "read size " << read_real_size << endl;
} else {
break;
}
}
cout << "read over" << endl;
close(fd);
return 0;
}
写入文件
通过打开文件后返回文件描述符,可以向文件中的写入数据,可以将指定的缓存中
的内容写入到文件中。
- 相关函数
#include <unistd.h>
ssize_t write(int fd, const void * buf, size_t count);
- 参数说明
参数名称 | 说明 |
---|---|
fd | 已经打开的文件描述符 |
buf | 需要写入的数据的文件缓存 |
count | 每次写入文件数据的长度 |
- 常见返回异常值
参数名称 | 说明 |
---|---|
EINTR | 此调用被信号所中断 |
EADF | 参数fd无效或者文件已经关闭 |
- 示例
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main() {
int fd = open("./test_data.txt", O_RDWR);
// ........
char write_buffer[] = "hello";
ssize_t write_size = write(fd, write_buffer, strlen(write_buffer));
close(fd);
return 0;
}
文件偏移量
文件可以直接从指定位置开始读写数据,文件偏移量指的是当前文件操作位置相对于目标位置的偏移,当打开一个文件时在未指定O_APPEND
情况下默认是0
,如果指定O_APPEND
则文件偏移到文件的结尾
与文件的长度相等。
- 相关函数
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 参数说明
参数名称 | 说明 |
---|---|
fd | 已经打开的文件描述符 |
offset | 偏移的字节数该值与whence设置有关 |
whence | 偏移量的计算位置标记 |
- whence标志说明
标志位 | 说明 |
---|---|
SEEK_SET | offset为相对文件开始处的值 |
SEEK_CUR | offset为相对当前位置处的值 |
SEEK_END | offset为相对文件结尾处的值 |
- 示例
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main() {
int fd = open("test_data.txt", O_RDWR);
// 文件内容: hello seek file code
off_t result = lseek(fd, 5, SEEK_SET);
if (result == -1) {
cout << "seek fail." << endl;
return -1;
}
char buffer[] = "Insert";
ssize_t w_result = write(fd, &buffer, strlen(buffer));
close(fd);
return 0;
}
获取文件状态
文件的状态信息包含了例如,文件所有者、文件修改时间、文件大小等
- 相关函数
#include <sys/stat.h>
int stat(const char * path, struct stat * buf);
int fstat(int filedes, struct stat * buf);
int lstat(const char * path, struct stat * buf);
- 参数说明
参数名称 | 说明 |
---|---|
path | 文件完整路径 |
filedes | 文件描述符 |
buf | stat结构体的指针 |
在三个函数中fstat
需要一个已打开文件的描述符,即已经open过的文件
,另外两个则是文件全路径。stat
和lstat
区别在于,当文件是一个符号链接时,前者返回链接指向文件的信息
而后者返回链接文件的信息
。
- stat结构体说明
变量类型 | 变量名称 | 变量说明 |
---|---|---|
mode_t | st_mode | 文件对应的模式 |
ino_t | st_ino | inode节点号 |
dev_t | st_dev | 设备号码 |
dev_t | st_rdev | 特殊设备号码 |
nlink_t | st_nlink | 文件的链接数 |
uid_t | st_uid | 文件所有者 |
gid_t | st_gid | 文件所有者对应的组 |
off_t | st_size | 文件对应的字节数[普通文本] |
time_t | st_atime | 文件最后被访问的时间 |
time_t | st_mtime | 文件内容最后被修改的时间 |
time_t | st_ctime | 文件状态改变的时间 |
blksize_t | st_blksize | 文件内容对应的块大小 |
blkcnt_t | st_blocks | 文件内容对应的块数量 |
- 示例
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
using namespace std;
int main() {
struct stat file_stat{};
if (-1 == stat("test_data.txt", &file_stat)) {
cout << "get stat fail." << endl;
return -1;
}
// 文件长度
cout << "文件长度 = " << file_stat.st_size << endl;
// 文件节点号
cout << "文件索引号 = " << file_stat.st_ino << endl;
return 0;
}
文件锁定
可以通过给文件增加锁
标记来避免共享的文件资源产生竞争状态,文件锁分为两种建议性锁
和强制性锁
,前者只是给文件加上锁标识其他进程可以读取到该标识但不会真的阻止文件操作,后者则是在其他进程访问时组织相应的操作。
- 相关函数
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, struct flock * lock);
- 参数说明
参数名称 | 说明 |
---|---|
fd | 文件描述符 |
cmd | 操作的命令 |
lock | 文件锁数据结构 |
- cmd标志位说明
标志位 | 说明 |
---|---|
F_GETLK | 根据lock描述,决定是否上文件锁 |
F_SETLK | 设置lock描述的文件锁 |
- flock结构体说明
变量类型 | 变量名称 | 变量说明 |
---|---|---|
short int | l_type | 锁定的状态 |
short int | l_whence | 决定l_start的计算起始位置 |
off_t | l_start | 锁定区域的开头位置 |
off_t | l_len | 锁定区域的大小,0表示锁到结尾 |
pid_t | st_nlink | 锁定动作的进程 |
- whence标志位
标志位 | 说明 |
---|---|
SEEK_SET | l_start为相对文件开始处的值 |
SEEK_CUR | l_start为相对当前位置处的值 |
SEEK_END | l_start为相对文件结尾处的值 |
l_len表示加锁的长度,0为到文件末尾,l_pid表示操作文件的进程ID号。
- 示例
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
using namespace std;
int main() {
struct flock lock{};
int fd = open("test_data.txt", O_RDWR);
if (fd == -1) {
return fd;
}
lock.l_type = F_WRLCK; // 写入锁
lock.l_whence = SEEK_SET; // 文件的开始文件
lock.l_start = 0; // 相对于 l_whence 偏移量
lock.l_len = 20; // 锁20个字符
lock.l_pid = getpid();
/* 直接锁定文件 */
int res = fcntl(fd, F_SETLK, &lock);
cout << "文件上锁结果 : " << res << endl;
close(fd);
return 0;
}
内存映射
文件映射可以将普通文件映射到内存中,可以像访问内存一样对文件进行访问,不必再调用read或者write。
- 相关函数
#include <sys/mman.h>
void * mmap(void * start, size_t length, int port, int flags, int fd, off_t offset);
- 参数说明
参数名称 | 说明 |
---|---|
start | 映射区起始地址,通常为0,表示为系统默认 |
length | 映射数据长度 |
port | 表示映射区保护方式 |
flags | 指定映射对象类型,组合值 |
fd | 文件描述符 |
offset | 被映射数据在文件中的起点 |
- port类型说明
标志位 | 说明 |
---|---|
PROT_EXEC | 映射区可被执行 |
PROT_READ | 映射区可读取 |
PROT_WRITE | 映射区可写入 |
PROT_NONE | 映射区不可访问 |
- flags类型说明
标志位 | 说明 |
---|---|
MAP_FIXED | 固定映射地址,与start配套使用,一般不设置 |
MAP_SHARED | 共享映射区域,映射区域允许其他进程共享,对区域写入数据会写入文件中 |
MAP_RIVATE | 执行写入操作时会创建副本,不影响源文件 |
MAP_ANONYMOUS | 匿名映射,不与文件关联,不与进程共享 |
MAP_DENYWRITE | 禁止写入 |
MAP_LOCKED | 映射区锁定 |
- 示例
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
using namespace std;
int main() {
char * mapped_mem, *p;
// test_data.txt
// hello seek file code
// hello lock file code
int fd = open("test_data.txt", O_RDWR);
int length = lseek(fd, 1, SEEK_END);
lseek(fd, 0, SEEK_SET);
mapped_mem = (char*) mmap(nullptr, length, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0);
printf("%s\n", mapped_mem);
/* 替换所以 hello -> write */
char * hello_pos = nullptr;
while ((hello_pos = strstr(mapped_mem, "hello"))) {
memcpy(hello_pos, "write", 5);
hello_pos += 5;
}
printf("%s\n", mapped_mem);
close(fd);
munmap(mapped_mem, length);
return 0;
}