文件管理和IO重定向
7种文件类型
- - 普通文件
- d 目录文件 directory
- b 块设备block
- c 字符设备 character
- l 符号链接文件 link
- p 管道文件 pipe
- s 套接字文件 socket
普通文件
普通文件分为三类:
- 纯文本文件 ASCII
使用cat
可以查看纯文本文件 - 二进制文件 binary
使用od
或者hexdump
命令以8进制或者16进制查看 - 数据格式文件 data
有些程序运行的过程中会读取某些特定格式的文件, 那些特定格式的文件可以被成为数据文件(data file). 举例来说, 我们的Linux在使用登录的时候, 都会将登录的数据记录在 /var/log/wtmp 文件内, 该文件就是一个data file, 它可以被last
和who
命令读取内容.
目录文件
块设备 和 字符设备
块设备是硬件设备, 以块(block, 在EXT4文件系统中, 一个block通常为4K)为单位, 应用程序通过随机访问的形式读取数据.
最常见的块设备是硬盘, 还有软盘等. 注意这些都是挂载文件系统的设备, 文件系统就像块设备的通用语言.
字符设备文件以字节流的方式进行访问,由字符设备驱动程序来实现这种特性。字符终端、串口和键盘等就是字符设备。可以顺序读取,但通常不支持随机存取。
区分块设备和字符设备最简单的方法就是看数据访问的方式, 能随机访问获取数据的是块设备, 必须按字节顺序访问的是字符设备.
符号链接文件
符号链接文件又叫软连接,软连接相当于一个快捷方式
软(symbolic 或 soft)链接
文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的”软链接”(soft link)或者”符号链接(symbolic link)。
这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:”No such file or directory”。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode”链接数”不会因此发生变化。
1 | ln -s filename [linkname] |
- 软链接的内容是它引用文件的路径
- 可以对目录创建软链接
- 可以跨分区创建软链接
硬(hard)链接
硬链接本质上就给一个文件起一个新的名称,不是符号链接,写在这里主要是为了阅读方便
1 | ln filename [linkname] |
inode信息中有一项叫做”链接数”,记录指向该inode的文件名总数
- 创建硬链接后,和原文件的inode还是一样的
- 新建硬链接, inode中的”链接数” + 1
- rm删除文件, inode中的”链接数” - 1,当”链接数”为0,才是真正删除此文件
- 不能跨域驱动器或分区创建硬链接
- 不支持对目录创建硬链接
管道文件 (FIFO文件)
管道文件主要用于进程间通信
管道都是一端写入、另一端读取,它们是单方向数据传输的,它们的数据都是直接在内存中传输的,管道是进程间通信的一种方式,例如父进程写,子进程读。
套接字文件
套接字用来实现两端通信,可以实现双向管道的进程间通信功能。不仅如此,套接字还能通过网络实现跨主机的进程间通信功能。
套接字需要成对才有意义,也就是分为两端,每一端都有用于读、写的文件描述符(或文件句柄),相当于两根双向通信的管道。
套接字根据协议族的方式分为两大类:网络套接字(AF_INET类型,根据ipv4和ipv6分为inet4和inet6)和Unix Domain套接字(AF_UNIX类型)。当然,从协议族往下,套接字可细分为很多种类型,例如INET套接字可以分为TCP套接字、UDP套接字、链路层套接字、Raw套接字等等。其中网络套接字是网络编程的基础和核心。
对于单机的进程间通信,使用Unix Domain套接字比Inet套接字更好,因为Unix Domain套接字没有网络通信组件,也就是少了很多网络功能,它更加轻量级。实际上,某些语言在某些操作系统平台上实现的管道功能就是通过Unix Domain来实现的,可想而知其高效率。例如:http://blog.lujinkai.cn/archives/9.html#H2_9,nginx和PHP-FPM的进程间通信有两种方式,一种是TCP,一种是UNIX Domain Socket.其中TCP是IP加端口,可以跨服务器.而UNIX Domain Socket不经过网络,只能用于Nginx跟PHP-FPM都在同一服务器的场景,效率更高。
文件元数据
每个文件的属性信息,比如:文件的大小、时间、类型等,称为文件的元数据(meta data)
atime、mtime、ctime
每个文件都有三个时间,atime、mtime、ctime,使用stat命令可以查看。
atime:access time 访问时间,当使用cat命令查看文件内容时atime不改变。使用wq退出vim时修改atime,使用q!退出vim时不改变atime。使用echo往文件写内容不改变atime。使用sed -i修改文件改变atime
mtime:modify time 修改时间,当文件内容被修改时,mtime发生改变。
ctime:change time 变化时间,文件的inode被修改时,ctime发生改变。注意:只要mtime发生改变,ctime肯定也同步发生改变,如果文件较大,ctime可能会延迟几毫秒。
文件节点表结构
广义上,一个文件由三部分组成:目录项、索引节点(inode)、数据块
- 目录项:dirent,包含文件名和索引节点号,索引节点号指向索引节点表(inode table)中对应的索引节点
- 索引节点:inode,包含文件的元数据以及数据块指针
- 数据块:包含文件的具体内容
inode的大小
硬盘格式化的时候,操作系统自动将硬盘分为两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
inode有上图中的左边区域组成,数据块由右边区域构成。不同的文件大小,通过多层级的间接指针协同完成。
直接块指针有12个,一个块大小为4KB,所以直接指针可以保存48KB的文件
间接块指针:每个指针占用4个字节,一个块是4KB,所以可以将一个块拆分成1024个指针,那么它的存储数据1024*4KB=4MB
双重间接块指针:同理可得它可以存储的数据为1024*4MB=4GB
三级指针可以储存文件数据大小为1024*4GB=4TB
一个字节占用几个字节?
https://blog.csdn.net/IOSSHAN/article/details/88944637
目录也是文件,打开目录,实际上就是打开目录文件。
目录文件也是由目录项、inode、数据块组成。
目录的数据块中存储的是一系列目录项的列表,是其下的文件的所有目录项。每个目录项,由两部分组成:包含文件名,以及该文件名对应的inode号码。
打开一个文件分三步走
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。
打开一个文件分三步走:名称与inode编号关联,首先,系统找到这个文件名对应的inode号码;其次, 通过inode号找到对应的inode,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
cp和inode
- 分配一个空闲的inode号,在inode table中生成新条目
- 在目录中创建一个目录项,将名称与inode编号关联
- 拷贝数据生成 新的文件
rm和inode
- 链接数递减,当减到0,就释放inode号
- 把数据块放在空闲列表中
- 删除目录项
- 数据实际上不会马上被删除,但是当另一个文件使用数据块时将被覆盖
根据3, 可以得出, 删除文件实际上是修改目录的内容, 所以删除文件必须要有目录的写权限, 所以删除文件和文件的权限没有关系, 和文件的父目录的权限有关, 同理:创建文件也是这样
mv和inode
如果mv命令的目标和源在相同的文件系统
- 创建新的目录项,新名称对应到inode号
- 删除旧的目录项
- 修改对应inode上的元数据(时间戳),不移动数据
如果mv命令的目标和源在不同的文件系统:mv相当于 cp + rm
1 | # 删除大文件 |
IO重定向和管道
IO的I是input,IO的O是ouput
Linux给程序提供了三种IO设备:
- 标准输入(STDIN)0 默认接受来自终端窗口的输入
- 标准输出(STDOUT)1 默认输出到终端窗口
- 标准错误(STDERR)2 默认输出到终端窗口
STDOUT和STDERR可以被重定向到指定文件,而非默认的当前终端。
1 | 命令 操作符 文件名 |
2>&1
这里的&类似php中表示变量引用的用法, 放在>后面的&, 表示重定向的目标不是一个文件, 而是一个文件描述符
标准错误拷贝了标准输出的行为, 注意是行为,不仅仅是路径。如果标准输出是 > a.log,那么标准错误输出也是 > a.log,如果标准输出是 >> a.log,那么标准错误输出也是 >> a.log
而&>file是一种特殊的用法, 也可以写成>&file, 二者的意思完全相同, 都等价于1>file 2>&1
, 此处的&>或者>&视作整体, 不能分开
标准输入重定向 单行重定向 <
利用<可以将标准输入重定向
1 | [root@4710419222 test]# cat a.log |
多行重定向 <<终止词
这个很好理解, 类似php的长字符串<<<EOT
1 | [root@4710419222 test]# cat > a.log <<EOT |
管道
1 | 命令1 | 命令2 | 命令3 | ... |
- 将命令1的STDOUT发送给命令2的STDIN, 命令2的STDOUT发送到命令3的STDIN
- 所有命令都在当前shell进程的子进程中执行
注意:管道符后面只能跟一个命令,如果跟多个命令,命令之间用分号间隔,那么,由于管道的右边会打开一个子进程,所以,后面的命令实际上是属于父进程的,解决办法就是用{}或者()把多个命令包裹起来,确保多个这多个命令是在同一个进程下执行1
2
3
4
5
6[root@centos8 scripts]#echo 1 2 | read x y ; echo x=$x y=$y
x= y=
[root@centos8 ~]#echo 1 2 | ( read x y ; echo x=$x y=$y )
x=1 y=2
[root@centos8 ~]#echo 1 2 | { read x y ; echo x=$x y=$y; }
x=1 y=2 - 组合多种工具的功能
注意: STDERR默认通过管道转发, 可利用2>&1
或|&
实现
1 | [root@4710419222 test]# ll c.log |
重定向中的 - 符号
1 | # 将下载文件内容输出到标准输出, 而不保存文件 |
文件描述符(fd)
Linux中一切都是文件。文件描述符fd是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数,在文件open时产生。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件。
文件操作相关命令
pwd
1 | # -P 显示真实的物理路径 |
basename 和 dirname
1 | [root@4710419222 vhost]# pwd |
cd
1 | # 切换至物理路径, 而非软链接目录 -P |
ls
1 | # 显示隐藏文件,但是不显示.和.. |
说明: ls 查看不同后缀文件时的颜色由 /etc/DIR_COLORS 和@LS_COLORS变量定义
stat
文件有三个时间
1 | [root@4710419222 ~]# stat test.php |
file
判断文件的类型不能依靠后缀, 可以使用file命令判断文件的类型
hexdump
hexdump命令一般用来查看“二进制”文件的十六进制编码,但实际上它能查看任何文件,而不只限于二进制文件。
-C 输出规范的十六进制和ASCII码。
1 | [root@4710419222 ~]# hexdump -C test.txt |
文件通配符 glob
文件通配符可以用来匹配符合条件的多个文件,方便批量管理文件
1 | # * 匹配零个或多个字符, 但不匹配隐藏文件 |
此外, 还有预定义的字符类: man 7 glob
1 | [:digit:]:任意数字,相当于0-9 |
ps: 批量创建文件是用{}, 批量显示文件使用[]
文件通配符(glob)和正则表达式(regex)
文件通配符就是* ? [] 这三个,正则表达式则功能强大。
对于:文件通配符中,\匹配0个或多个字符,可以单独使用。而在正则表达式中,*是匹配前面的0次或多次,前面必须由内容,不能单独使用。
对于?:和*一样,文件通配符中,?可以单独使用,正则表达式中不可以。
对于[]:文件通配符[]中的内容是按照ASCII统计的,例如[a-z]会匹配a、A、b、B…y、Y、z,而正则表达式[]中的内容是按照人类的方式统计,例如[a-z]会匹配a、b、c、d、e…x、y、z
对于.:文件通配符中.就是.,没有别的意思。正则表达式中.匹配单个字符
touch
创建空文件和刷新文件时间
1 | # -a 仅改变 atime和ctime |
cp
- -a 归档,相当于-dR –preserv=all,常用于备份功能
- -u –update 只复制源比目标先更新文件或目标不存在的文件
- -b 目标存在, 覆盖前先备份, 默认形式为
filename~
, 只保留最近一个备份 - –backup=numbered 目标存在, 覆盖前先备份加数字后缀, 形式为
filename.~#~
, 可以保留多个版本
注意: 不同类型的文件不能覆盖, 例如普通文件可以覆盖普通文件, 但是不能覆盖目录
1 | [root@4710419222 test]# ll |
mv
1 | mv [OPTION]... [-T] SOURCE DEST # 移动并重命名 |
mv命令可以移动和重命名文件, 同一分区移动会很快
-b 目标存在, 覆盖前先备份
但是mv命令只能一次重命名一个文件, 使用rename
命令可以批量重命名文件
rename
1 | # rename [options] <expression> <replacement> <file>... |
这个命令好像可以使用正则, 但是我试了一下, 不行, 可能是版本的问题
rm
此命令非常危险, 建议使用mv替代rm , 当删除的时候, 实际上是移动到一个特定的目录
1 | alias rm='DIR=/data/backup`date +%F%T`;mkdir $DIR;mv -t $DIR' |
rm虽然删除了文件, 但是在安全场景要求较高的情况下, 可以使用shred安全删除文件
1 | [root@centos8 ~]#shred -zvun 5 passwords.txt |
tree
显示目录树
df
1 | df [选项列表]... [文件列表]... |
- -a –all
- -h –human-readable
- -i –inodes 显示 inode 信息而非块使用量
关于 df 和 lsblk
lsblk 查看的是block device,也就是逻辑磁盘大小。df查看的是file system, 也就是文件系统层的磁盘大小。
显示磁盘占用情况和inode使用情况使用df。
mkdir
- -p 目录如果不存在, 创建, 如果存在, 不报错
- -m 创建目录时直接指定权限
rmdir
- -p 递归删除父空目录
挂载点不能删除
tee
从标准输入读入并写往标准输出和文件
1 | tee [选项]... [文件列表]... |
把标准输入的数据复制到文件列表中的每一个文件,同时送往标准输出。
- -a –append 追加到给出的文件, 而不是覆盖
功能: 保存不同阶段的输出; 复杂管道的故障排除; 同时查看和记录输出
1 | [root@centos8 ~]#cat <<EOF | tee /etc/motd |
lsof
列出打开的文件,Linux下万物皆文件,网络也是文件。
lsof的参数巨多,下面列举常用的参数
1 | lsof file # 查看文件被哪些进程占用 |
1 | lsof -i # 列出所有的网络连接 |
seq
打印数列
1 | [root@centos8 ~]# seq 0 9 |
练习题
1、将/etc/issue文件中的内容转换为大写后保存至/tmp/issue.out文件中
1 | [root@centos7 test]# cat /etc/issue | tr 'a-z' 'A-Z' > /tmp/issue.out |
2、将当前系统登录用户的信息转换为大写后保存至/tmp/who.out文件中
1 | [root@centos7 test]# whoami | tr 'a-z' 'A-Z' > /tmp/who.out |
3、一个linux用户给root发邮件,要求邮件标题为”help”,邮件正文如下:
Hello, I am 用户名,The system version is here,please help me to check it ,thanks!
操作系统版本信息
1 | [root@centos8 ~]# echo -e "Hello, I am `whoami`,The system version is here,please help me to check it ,thanks! \n`cat /etc/os-release`" | mail -s hello root |
4、将/root/下文件列表,显示成一行,并文件名之间用空格隔开
1 | [root@4710419222 /]# ls -A /root | tr '\n' ' ' |
5、计算1+2+3+…+99+100的总和
1 | [root@centos8 test]# seq -s + 1 100 | bc |
6、删除Windows文本文件中的回车字符 ,即“\r”
1 | cat a.log | tr '\r' ' ' |
7、处理字符串“xt.,l 1 jr#!$mn 2 c*/fe 3 uz 4”,只保留其中的数字和空格
1 | [root@centos8 test]# echo 'xt.,l 1 jr#!$mn 2 c*/fe 3 uz 4' | tr -dc '0-9 ' |
8、将PATH变量每个目录显示在独立的一行
1 | [root@centos8 test]# echo $PATH | tr ':' '\n' |
9、将指定文件中0-9分别替代成a-j
1 | [root@centos8 test]# cat a.log | tr '0-9' 'a-j' |
10、将文件/etc/centos-release中每个单词(由字母组成)显示在独立一行,并无空行
1 | [root@centos8 test]# cat /etc/centos-release | tr -c "[:alpha:]" " " | tr -s " " "\n" |
11、ls 输出的内容明明是分行的, 为什么显示出来就不分行了?
不单 ls 会这样,不少其他命令也会这样。它们会使用 isatty 函数查询输出是否指向终端,对输出到终端和非终端的处理,可能不一样
常见的比如:
1、输出到终端时,使用 color,非终端则不用;
2、输出到终端时,使用 text 方式,非终端则用 binary。
如果需要一致的输出,应当明确使用相关参数
這取決於 stdout 是不是終端。如果是終端就可以讀取終端的寬度,根據寬度排版。