众所周知,Linux是一个基于文件的操作系统,因此作为文件本身也就有很多属性,如果想要查看某一个文件的属性有两种方式:命令函数。虽然有两种方式但是它们对应的名字是相同的,叫做stat。另外使用file命令也可以查看文件的一些属性信息。

1. file 命令

该命令用来识别文件类型,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的。

命令语法如下:

1
2
# 参数在命令中的位置没有限制
$ file 文件名 [参数]

file 命令的参数是可选项, 可以不加, 常用的参数如下表:

参数 功能
-b 只显示文件类型和文件编码, 不显示文件名
-i 显示文件的 MIME 类型
-F 设置输出字符串的分隔符
-L 查看软连接文件自身文件属性

1.1 查看文件类型和编码格式

使用不带任何选项的 file 命令,即可查看指定文件的类型和文件编码信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 空文件
$ file 11.txt
11.txt: empty

# 源文件, 编码格式为: ASCII
$ file b.cpp
b.cpp: C source, ASCII text

# 源文件, 编码格式为: UTF-8
robin@OS:~$ file test.cpp
test.cpp: C source, UTF-8 Unicode (with BOM) text, with CRLF line terminators

# 可执行程序, Linux中的可执行程序为 ELF 格式
robin@OS:~$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=5317ae9fba592bf583c4f680d8cc48a8b58c96a5, not stripped

1.2 只显示文件格式以及编码

使用-b参数,可以使 file 命令的输出不出现文件名,只显示文件格式以及编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 空文件
$ file 11.txt -b
empty

# 源文件, 编码格式为: ASCII
$ file b.cpp -b
C source, ASCII text

# 源文件, 编码格式为: UTF-8
robin@OS:~$ file test.cpp -b
C source, UTF-8 Unicode (with BOM) text, with CRLF line terminators

# 可执行程序, Linux中的可执行程序为 ELF 格式
robin@OS:~$ file a.out -b
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=5317ae9fba592bf583c4f680d8cc48a8b58c96a5, not stripped

1.3 显示文件的 MIME 类型

给file命令添加-i参数,可以输出文件对应的 MIME 类型的字符串。

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# charset 为该文件的字符编码

# 源文件, MIME类型: text/x-c, 字符编码: utf-8
$ file occi.cpp -i
occi.cpp: text/x-c; charset=utf-8

# 压缩文件, MIME类型: application/gzip, 字符编码: binary
$ file fcgi.tar.gz -i
fcgi.tar.gz: application/gzip; charset=binary

# 文本文件, MIME类型: text/plain, 字符编码: utf-8
$ file english.txt -i
english.txt: text/plain; charset=utf-8

# html文件, MIME类型: text/html, 字符编码: us-ascii
$ file demo.html -i
demo.html: text/html; charset=us-ascii

1.4 设置输出分隔符

在 file 命令中,文件名和后边的属性信息默认使用冒号(:)分隔,我们可以通过 -F 参数修改分隔符,分隔符可以是单字符也可以是一个字符串,如果分隔符是字符串需要将这个参数值写到引号中(单/双引号都可以)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 默认格式输出
$ file english.txt
english.txt: UTF-8 Unicode text, with very long lines, with CRLF line terminators

# 修改分隔符为字符串 “==>"
$ file english.txt -F "==>"
english.txt==> UTF-8 Unicode text, with very long lines, with CRLF line terminators

$ file english.txt -F '==>'
english.txt==> UTF-8 Unicode text, with very long lines, with CRLF line terminators

# 修改分隔符为单字符 '='
$ file english.txt -F =
english.txt= UTF-8 Unicode text, with very long lines, with CRLF line terminators

1.5 查看软连接文件

软连接文件是一个特殊格式的文件, 查看这种格式的文件可以得到两种结果: 第一种是软连接文件本身的属性信息, 另一种是链接文件指向的那个文件的属性信息。

直接通过 file 查看文件属性得到的是链接文件指向的文件的信息,如果添加参数 -L得到的链接文件自身的属性信息。

1
2
3
4
5
6
7
8
9
10
11
# 使用 ls 查看链接文件属性信息
$ ll link.lnk
lrwxrwxrwx 1 root root 24 Jan 25 17:27 link.lnk -> /root/luffy/onepiece.txt

# 使用file直接查看链接文件信息: 得到的是链接文件指向的那个文件的名字
$ file link.lnk
link.lnk: symbolic link to `/root/luffy/onepiece.txt'

# 使用 file 查看链接文件自身属性信息, 添加参数 -L
$ file link.lnk -L
link.lnk: UTF-8 Unicode text

2 stat 命令

stat命令显示文件或目录的详细属性信息包括文件系统状态,比ls命令输出的信息更详细。语法格式如下:

1
2
# 参数在命令中的位置没有限制
$ stat [参数] 文件或者目录名

关于这个命令的可选参数如下表:

参数 功能
-f 不显示文件本身的信息,显示文件所在文件系统的信息
-L 查看软链接文件关联的文件的属性信息
-c 查看文件某个单个的属性信息
-t 简洁模式,只显示摘要信息, 不显示属性描述

2.1 显示所有属性

1
2
3
4
5
6
7
8
9
$ stat english.txt 
File: 'english.txt'
Size: 129567 Blocks: 256 IO Block: 4096 regular file
Device: 801h/2049d Inode: 526273 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1001/ robin) Gid: ( 1001/ robin)
Access: 2021-01-31 00:00:36.791490304 +0800
Modify: 2021-01-31 00:00:36.791490304 +0800
Change: 2021-01-31 00:00:36.791490304 +0800
Birth: -

在输出的信息中我们可以看到很多属性,

  • File: 文件名
  • Size: 文件大小, 单位是字节
  • Blocks: 文件使用的数据块总数
  • IO Block:IO块大小
  • regular file:文件的实际类型,文件类型不同,该关键字也会变化
  • Device:设备编号
  • Inode:Inode号,操作系统用inode编号来识别不同的文件,找到文件数据所在的block,读出数据。
  • Links:硬链接计数
  • Access:文件所有者+所属组用户+其他人对文件的访问权限
  • Uid: 文件所有者名字和所有者ID
  • Gid:文件所有数组名字已经组ID
  • Access Time:表示文件的访问时间。当文件内容被访问时,这个时间被更新
  • Modify Time:表示文件内容的修改时间,当文件的数据内容被修改时,这个时间被更新
  • Change Time:表示文件的状态时间,当文件的状态被修改时,这个时间被更新,例如:文件的硬链接链接数,大小,权限,Blocks数等。
  • Birth: 文件生成的日期

2.2 只显示系统信息

给 stat 命令添加 -f参数将只显示文件在文件系统中的相关属性信息, 文件自身属性不显示

1
2
3
4
5
6
$ stat luffy/ -f
File: "luffy/"
ID: 47d795d8889d00d3 Namelen: 255 Type: ext2/ext3
Block size: 4096 Fundamental block size: 4096
Blocks: Total: 10288179 Free: 8991208 Available: 8546752
Inodes: Total: 2621440 Free: 2515927

2.3 软连接文件

使用 stat 查看软链接类型的文件, 默认显示的是这个软链接文件的属性信息, 添加参数 -L就可以查看这个软连接文件关联的文件的属性信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 查看软件文件属性 -> 使用 ls -l
ls -l link.lnk
lrwxrwxrwx 1 root root 24 Jan 25 17:27 link.lnk -> /root/luffy/onepiece.txt

# 使用 stat 查看软连接文件属性信息
$ stat link.lnk
File: ‘link.lnk’ -> ‘/root/luffy/onepiece.txt’
Size: 24 Blocks: 0 IO Block: 4096 symbolic link
Device: fd01h/64769d Inode: 393832 Links: 1
Access: (0777/lrwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-01-30 23:46:29.922760178 +0800
Modify: 2021-01-25 17:27:12.057386837 +0800
Change: 2021-01-25 17:27:12.057386837 +0800
Birth: -

# 使用 stat 查看软连接文件关联的文件的属性信息
$ stat link.lnk -L
File: ‘link.lnk’
Size: 3700 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 660353 Links: 2
Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-01-30 23:46:53.696723182 +0800
Modify: 2021-01-25 17:54:47.000000000 +0800
Change: 2021-01-26 11:57:00.587658977 +0800
Birth: -

2.4 简洁输出

使用 stat 进行简洁信息输出的可读性不是太好, 所有的属性描述都别忽略了, 如果只想得到属性值, 可以给该命令添加-t参数:

1
2
$ stat luffy/ -t
luffy/ 4096 8 41fd 1001 1001 801 662325 8 0 0 1611659086 1580893020 1580893020 0 4096

2.5 单个属性输出

如果每次只想通过 stat 命令得到某一个文件属性, 可以给名添加 -c参数。 不同的文件属性分别对应一些定义好的特殊符号,想得到哪个属性值,将其指定到参数 -c后边即可。属性对应的字符如下表:

格式化字符 功能
%a 文件的八进制访问权限(#0是输出标准)
%A 人类可读形式的文件访问权限(rwx
%b 已分配的块数量
%B 报告的每个块的大小(以字节为单位)
%C SELinux 安全上下文字符串
%d 设备编号 (十进制)
%D 设备编号 (十六进制)
%F 文件类型
%g 文件所属组组ID
%G 文件所属组名字
%h 用连接计数
%i inode编号
%m 挂载点
%n 文件名
%N 用引号括起来的文件名,并且会显示软连接文件引用的文件路径
%o 最佳I/O传输大小提示
%s 文件总大小, 单位为字节
%t 十六进制的主要设备类型,用于字符/块设备特殊文件
%T 十六进制的次要设备类型,用于字符/块设备特殊文件
%u 文件所有者ID
%U 文件所有者名字
%w 文件生成的日期 ,人类可识别的时间字符串 – 获取不到信息不显示
%W 文件生成的日期 ,自纪元以来的秒数 (参考 %X )– 获取不到信息不显示
%x 最后访问文件的时间, 人类可识别的时间字符串
%X 最后访问文件的时间, 自纪元以来的秒数(从1970.1.1开始到最后一次文件访问的总秒数)
%y 最后修改文件内容的时间, 人类可识别的时间字符串
%Y 最后修改文件内容的时间, 自纪元以来的秒数(参考 %X )
%z 最后修改文件状态的时间, 人类可识别的时间字符串
%Z 最后修改文件状态的时间, 自纪元以来的秒数(参考 %X )

仔细阅读上表可以知道:文件的每一个属性都有一个或者多个与之对应的格式化字符,这样就可以精确定位所需要的属性信息了,下面举了几个例子,可以作为参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ stat occi.cpp -c %a
644

$ stat occi.cpp -c %A
-rw-r--r--

# 使用 ls -l 验证权限
$ ll occi.cpp
-rw-r--r-- 1 robin robin 1406 Jan 31 00:00 occi.cpp # 0664

$ stat link.lnk -c %N
'link.lnk' -> '/home/robin/english.txt'

$ stat link.lnk -c %y
2021-01-31 10:48:52.317846411 +0800

3. stat/lstat 函数

stat/lstat 函数的功能和 stat 命令的功能是一样的, 只不过是应用场景不同。这两个函数的区别在于处理软链接文件的方式上:

  • lstat(): 得到的是软连接文件本身的属性信息
  • stat(): 得到的是软链接文件关联的文件的属性信息

函数原型如下:

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
  • 参数:
    • pathname: 文件名, 要获取这个文件的属性信息
    • buf: 传出参数, 文件的信息被写入到了这块内存中
  • 返回值: 函数调用成功返回 0,调用失败返回 -1

这个函数的第二个参数是一个结构体类型, 这个结构体相对复杂, 通过这个结构体可以存储得到的文件的所有属性信息, 结构体原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // inode节点
mode_t st_mode; // 文件的类型和存取的权限, 16位整形数 -> 常用
nlink_t st_nlink; // 连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // (设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; // 文件字节数(文件大小) --> 常用
blksize_t st_blksize; // 块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; // block的块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间(文件内容)
time_t st_ctime; // 最后一次改变时间(指属性)
};

3.1 获取文件大小

下面调用 stat() 函数, 以代码的方式演示一下如何得到某个文件的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <sys/stat.h>

int main()
{
// 1. 定义结构体, 存储文件信息
struct stat myst;
// 2. 获取文件属性 english.txt
int ret = stat("./english.txt", &myst);
if(ret == -1)
{
perror("stat");
return -1;
}

printf("文件大小: %d\n", (int)myst.st_size);

return 0;
}

3.2 获取文件类型

文件的类型信息存储在 struct stat 结构体的st_mode成员中, 它是一个 mode_t类型, 本质上是一个16位的整数。Linux API中为我们提供了相关的宏函数,通过对应的宏函数可以直接判断出文件是不是某种类型,这些信息都可以通过 man 文档(man 2 stat)查询到。

相关的宏函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 类型是存储在结构体的这个成员中: mode_t  st_mode;  
// 这些宏函数中的m 对应的就是结构体成员 st_mode
// 宏函数返回值: 是对应的类型返回-> 1, 不是对应类型返回0

S_ISREG(m) is it a regular file?
- 普通文件
S_ISDIR(m) directory?
- 目录
S_ISCHR(m) character device?
- 字符设备
S_ISBLK(m) block device?
- 块设备
S_ISFIFO(m) FIFO (named pipe)?
- 管道
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
- 软连接
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
- 本地套接字文件

在程序中通过宏函数判断文件类型, 实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main()
{
// 1. 定义结构体, 存储文件信息
struct stat myst;
// 2. 获取文件属性 english.txt
int ret = stat("./hello", &myst);
if(ret == -1)
{
perror("stat");
return -1;
}

printf("文件大小: %d\n", (int)myst.st_size);

// 判断文件类型
if(S_ISREG(myst.st_mode))
{
printf("这个文件是一个普通文件...\n");
}

if(S_ISDIR(myst.st_mode))
{
printf("这个文件是一个目录...\n");
}
if(S_ISLNK(myst.st_mode))
{
printf("这个文件是一个软连接文件...\n");
}

return 0;
}

3.2 获取文件权限

用户对文件的操作权限也存储在 struct stat 结构体的st_mode成员中, 在这个16位的整数中不同用户的权限存储位置如下图,如果想知道有没有相关权限可以通过按位与(&)操作将这个标志位值取出判断即可。

Linux 中为我们提供了用于不同用户不同权限判定使用的宏,具体信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
关于变量 st_mode: 
- st_mode -- 16位整数
○ 0-2 bit -- 其他人权限
- S_IROTH 00004 读权限 100
- S_IWOTH 00002 写权限 010
- S_IXOTH 00001 执行权限 001
- S_IRWXO 00007 掩码, 过滤 st_mode中除其他人权限以外的信息
○ 3-5 bit -- 所属组权限
- S_IRGRP 00040 读权限
- S_IWGRP 00020 写权限
- S_IXGRP 00010 执行权限
- S_IRWXG 00070 掩码, 过滤 st_mode中除所属组权限以外的信息
○ 6-8 bit -- 文件所有者权限
- S_IRUSR 00400 读权限
- S_IWUSR 00200 写权限
- S_IXUSR 00100 执行权限
- S_IRWXU 00700 掩码, 过滤 st_mode中除文件所有者权限以外的信息
○ 12-15 bit -- 文件类型
- S_IFSOCK 0140000 套接字
- S_IFLNK 0120000 符号链接(软链接)
- S_IFREG 0100000 普通文件
- S_IFBLK 0060000 块设备
- S_IFDIR 0040000 目录
- S_IFCHR 0020000 字符设备
- S_IFIFO 0010000 管道
- S_IFMT 0170000 掩码,过滤 st_mode中除文件类型以外的信息

############### 按位与操作举例 ###############
1111 1111 1111 1011 # st_mode
0000 0000 0000 0100 # S_IROTH
&
----------------------------------------
0000 0000 0000 0000 # 没有任何权限

通过仔细阅读上边提供的宏信息, 我们可以知道处理使用它们得到用户对文件的操作权限, 还可以用于判断文件的类型(判断文件类型的第二种方式),具体操作方式可以参考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <sys/stat.h>

int main()
{
// 1. 定义结构体, 存储文件信息
struct stat myst;
// 2. 获取文件属性 english.txt
int ret = stat("./hello", &myst);
if(ret == -1)
{
perror("stat");
return -1;
}

printf("文件大小: %d\n", (int)myst.st_size);

// 判断文件类型
if(S_ISREG(myst.st_mode))
{
printf("这个文件是一个普通文件...\n");
}

if(S_ISDIR(myst.st_mode))
{
printf("这个文件是一个目录...\n");
}
if(S_ISLNK(myst.st_mode))
{
printf("这个文件是一个软连接文件...\n");
}

// 文件所有者对文件的操作权限
printf("文件所有者对文件的操作权限: ");
if(myst.st_mode & S_IRUSR)
{
printf("r");
}
if(myst.st_mode & S_IWUSR)
{
printf("w");
}
if(myst.st_mode & S_IXUSR)
{
printf("x");
}
printf("\n");
return 0;
}

4. 练习

掌握了如何通过 stat / lstat 函数获取文件相关属性之后, 我们就可以使用这两个函数来模拟执行命令 ls -l 的效果,具体代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>


int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("./a.out filename\n");
exit(1);
}

struct stat st;
int ret = stat(argv[1], &st);
if(ret == -1)
{
perror("stat");
exit(1);
}

// 存储文件类型和访问权限
char perms[11] = {0};
// 判断文件类型
switch(st.st_mode & S_IFMT)
{
case S_IFLNK:
perms[0] = 'l';
break;
case S_IFDIR:
perms[0] = 'd';
break;
case S_IFREG:
perms[0] = '-';
break;
case S_IFBLK:
perms[0] = 'b';
break;
case S_IFCHR:
perms[0] = 'c';
break;
case S_IFSOCK:
perms[0] = 's';
break;
case S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 判断文件的访问权限
// 文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所属组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';

// 硬链接计数
int linkNum = st.st_nlink;
// 文件所有者
char* fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所属组
char* fileGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
int fileSize = (int)st.st_size;
// 修改时间
char* time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time)-1);

char buf[1024];
sprintf(buf, "%s %d %s %s %d %s %s",
perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);

printf("%s\n", buf);

return 0;
}