众所周知,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/ -tluffy/ 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 %a644 $ 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 %y2021-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; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; blksize_t st_blksize; blkcnt_t st_blocks; 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 () { struct stat myst ; 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 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 () { struct stat myst ; 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 () { struct stat myst ; 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 ; }