学习目标
通过分析 ls 指令,来学习 Linux 目录和文件属性相关的知识。
代码实验环境
操作系统:Ubuntu 18.04 LTS
编译器 gcc 版本:gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
ls 指令介绍
ls 指令可以列出目录中所有文件的名字,以及这些文件的其他信息。
在命令行输入 ls:
$ ls
bin etc lib media root srv usr
boot home lib32 mnt run swapfile var
cdrom initrd.img lib64 opt sbin sys vmlinuz
dev initrd.img.old lost+found proc snap tmp vmlinuz.old
ls 的默认动作是找出当前目录中所有文件的名字,并按字典序排列后输出。
ls 还能显示其他信息,如果加上 -l 选项,ls 会列出每个文件的详细信息,也叫 ls 的长格式:
$ ls -l
total 2097260
drwxr-xr-x 2 root root 4096 9月 26 16:19 bin
drwxr-xr-x 3 root root 4096 11月 10 09:11 boot
drwxrwxr-x 2 root root 4096 4月 18 2021 cdrom
drwxr-xr-x 18 root root 4180 11月 16 22:57 dev
drwxr-xr-x 147 root root 12288 11月 16 22:58 etc
drwxr-xr-x 3 root root 4096 6月 22 14:43 home
lrwxrwxrwx 1 root root 34 11月 9 10:21 initrd.img -> boot/initrd.img-4.15.0-162-generic
lrwxrwxrwx 1 root root 34 11月 9 10:21 initrd.img.old -> boot/initrd.img-4.15.0-161-generic
drwxr-xr-x 22 root root 4096 4月 18 2021 lib
drwxr-xr-x 2 root root 4096 4月 18 2021 lib32
drwxr-xr-x 2 root root 4096 4月 18 2021 lib64
drwx------ 2 root root 16384 4月 18 2021 lost+found
drwxr-xr-x 4 root root 4096 4月 21 2021 media
drwxr-xr-x 2 root root 4096 4月 27 2018 mnt
drwxr-xr-x 3 root root 4096 10月 29 13:08 opt
dr-xr-xr-x 240 root root 0 11月 16 22:57 proc
drwx------ 7 root root 4096 5月 21 09:39 root
drwxr-xr-x 31 root root 960 11月 16 22:58 run
drwxr-xr-x 2 root root 12288 11月 16 22:58 sbin
drwxr-xr-x 15 root root 4096 9月 29 08:53 snap
drwxr-xr-x 2 root root 4096 4月 27 2018 srv
-rw------- 1 root root 2147483648 4月 18 2021 swapfile
dr-xr-xr-x 13 root root 0 11月 16 23:02 sys
drwxrwxrwt 14 root root 4096 11月 16 23:03 tmp
drwxr-xr-x 15 root root 4096 4月 18 2021 usr
drwxr-xr-x 15 root root 4096 8月 13 17:37 var
lrwxrwxrwx 1 root root 31 11月 9 10:21 vmlinuz -> boot/vmlinuz-4.15.0-162-generic
lrwxrwxrwx 1 root root 31 11月 9 10:21 vmlinuz.old -> boot/vmlinuz-4.15.0-161-generic
每一行代表一个文件和它的多个属性。
Linux 系统中会有很多的目录,每个目录中又会有很多文件。如果要列出一个非当前目录的内容或者是特定文件的信息,则需要在参数中给出目录名或文件名。
例子 | 说明 |
---|---|
ls /tmp | 列出 /tmp 目录中各文件名 |
ls -l docs | 列出 docs 目录中各文件的属性 |
ls -l …/Makefile | 显示文件 …/ Makefile 的属性 |
ls *.c | 显示与 *.c 匹配的文件 |
如果参数是目录,ls 列出目录的内容;如果参数是文件,ls 列出文件名和属性。
ls 经常用到的命令行选项
命令 | 说明 |
---|---|
ls -a | 列出包含以 “.” 开头的文件 |
ls -lu | 显示最后访问时间 |
ls -s | 显示以块为单位的文件大小 |
ls -t | 按时间排序输出 |
ls -F | 显示文件类型 |
在 Linux 系统中,ls 一般不会列出以 “.” 开头的文件,可以把这样的文件看作是隐藏文件。 ls 加上 -a 选项后,遇到这样的文件也必须把它们列出来。对操作系统来说,文件名前面的 “.” 没有任何特殊含义,它只对 ls 的使用者有意义。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iDV2U70N-1639474793394)(M:\document\03.Linux\02.Linux编程入门\05.Linux编程入门(5)]-读取目录(ls 初步实现)_20211117\image-20211117003403543.png)
某些应用程序的配置文件以 “.” 开头,这是由习惯形成的,因为在大多数情况下可以将他们隐藏。需要时可以直接打开编辑,不需任何特殊的操作。
总结,ls 做了以下两件事:
-
出目录的内容
-
显示文件的信息
注意,ls 对文件和目录所作的操作是不同的。ls 能根据参数判断出是文件还是目录。这是如何做到的呢?有以下三点内容需要搞清楚:
- 如何列出目录的内容
- 如何读取并显示文件的属性
- 如何根据名字判断出,它是目录还是文件
ls 工作原理
ls 指令会显示一个文件名的列表,大致的工作流程如下:
打开目录 --> 读取目录 --> 显示文件信息 --> 关闭目录
目录是什么?
目录是一种特殊的文件,它的内容是文件和目录的名字。它包含很多记录,每条记录格式由统一的标准定义,其内容代表一个文件或者目录。
目录文件永远不会空,每个目录至少包含两个特殊的项—— “.” 和 “…”。其中,“.” 代表当前目录,“…” 代表上一级目录。
如何读取目录内容
从目录中读取数据与从文件读数据是类似的。首先打开一个目录,然后读取目录项,最后关闭目录。
opendir()系统函数可以打开一个目录,并返回指向该目录的句柄,供后续调用使用。函数原型如下:
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数 name,为目录名字。
打开成功则返回指向 DIR 类型结构的指针,该结构也就是所谓的目录流,调用者可将该指针传递给其他相关函数。若打开失败,则返回 NULL。
从 opendir() 返回的指针,指向目录列表的首条记录。
readdir() 系统函数读取目录项,函数返回一个指向目录当前记录的指针。函数原型如下:
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数 dirp,为 opendir() 调用返回的指针。
调用成功,则返回一个指向 struct dirent 类型的指针。调用失败或者读取到目录末尾,则返回 NULL。
调用readdir() 一次,就会从 dirp 所指代的目录流中读取下一条目录项。struct dirent 结构由静态分配而得到,每读取一次目录项,都会覆盖该结构。
struct dirent 结构定义如下:
struct dirent
{
ino_t d_ino; /* i节点号 */
off_t d_off;
unsigned short d_reclen; /* 记录信息长度 */
unsigned char d_type; /* 文件类型; 并非所有文件系统类型支持此字段 */
char d_name[256]; /* 文件名称 */
};
readdir() 返回时,并未对文件进行排序,而是按照文件在目录中出现的天然次序。这取决于文件系统向目录添加文件时所遵循的次序,以及其在删除文件后对目录列表中空隙的填充方式。
close() 系统函数将关闭打开的目录,同时释放目录流所使用的资源。其函数原型如下:
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
参数 dirp,为 opendir() 调用返回的指针。
调用成功,则返回 0。否则返回 -1。
ls 初步实现
经过上面的分析,我们编写代码读取目录内容,实现简单的 ls 命令。
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
void do_ls(char *dirname);
int main(int ac, char *av[])
{
if(ac == 1)
{
/* 未指定目录参数,默认动作是找出当前目录中所有文件的名字 */
do_ls(".");
}
else
{
/* 逐个显示指定目录的内容 */
while(--ac)
{
printf("%s:\n", *++av);
do_ls(*av);
}
}
return 0;
}
/* 读取并显示目录中的文件名 */
void do_ls(char *dirname)
{
DIR *dir_ptr = NULL;
struct dirent *direntp = NULL;
/* 打开目录,并判断返回值 */
if((dir_ptr = opendir(dirname)) == NULL)
{
/* 打开失败,显示失败信息 */
fprintf(stderr, "ls1: cannot open %s\n", dirname);
}
else
{
/* 打开成功,读取目录项目,并显示 */
while((direntp = readdir(dir_ptr)) != NULL)
{
printf("%s\n", direntp->d_name);
}
/* 关闭打开的目录 */
closedir(dir_ptr);
}
}
将上面代码编译运行,结果如下
$ gcc -o ls1 ls1.c /* 编译 */
$ ./ls1 /* 未携带参数 */
.
ls1
ls1.c
..
$ ./ls1 / /* 显示根目录内容 */
/:
cdrom
dev
sbin
.
tmp
sys
proc
lib
vmlinuz
root
etc
lost+found
lib64
initrd.img.old
var
lib32
home
mnt
run
opt
boot
usr
snap
..
swapfile
media
srv
bin
initrd.img
vmlinuz.old
再看看标准 ls 指令执行结果
$ ls
ls1 ls1.c
$ ls /
bin etc lib media root srv usr
boot home lib32 mnt run swapfile var
cdrom initrd.img lib64 opt sbin sys vmlinuz
dev initrd.img.old lost+found proc snap tmp vmlinuz.old
对比分析可知。我们编写的程序可以正常显示目录内容,但还有需要改进的地方:
-
排序
ls1 的输出没有经过排序。可以将所有的文件名读入到一个数组中,并用 qsort 函数把数组进行排序。
-
分栏
标准的 ls 输出是分栏排列的,有些是以行排序输出,有些是以列排序输出。可以先把文件名读入数组,然后计算出列的宽度和行数。
- “.” 和 “…” 文件
ls1 列出了 “.” 和 “…” 文件,而标准 ls 只有在给出 -a 选项时才会列出这些文件。可以使 ls1 能够接收选项 -a,并在没有 -a 的时候不显示隐藏文件。
- 选项 -l
ls1 不会处理 -l 选项。标准 ls 会处理选项 -l ,并显示出文件的详细信息。要解决这个问题不太容易,因为 dirent 结构中没有提供所需要的信息,如文件大小、文件所有者等。
小结
经过上述内容,我们学习了 ls 指令能够做什么,以及其是如何工作的。并通过编写代码,实现简单的 ls 功能 显示目录内容。
学习的系统函数有:
- opendir()
- readdir()
- closedir()
本篇文章只是读取了目录内的文件名,那如何获取文件的详细信息呢?下篇继续分析。
————————————————————————
关注公众号【一起学习嵌入式】,后台回复 “ linux ”,获取经典Linux书籍资料