文件管理和IO重定向

7种文件类型

  1. - 普通文件
  2. d 目录文件 directory
  3. b 块设备block
  4. c 字符设备 character
  5. l 符号链接文件 link
  6. p 管道文件 pipe
  7. s 套接字文件 socket

普通文件

普通文件分为三类:

  1. 纯文本文件 ASCII
    使用cat可以查看纯文本文件
  2. 二进制文件 binary
    使用od或者hexdump命令以8进制或者16进制查看
  3. 数据格式文件 data
    有些程序运行的过程中会读取某些特定格式的文件, 那些特定格式的文件可以被成为数据文件(data file). 举例来说, 我们的Linux在使用登录的时候, 都会将登录的数据记录在 /var/log/wtmp 文件内, 该文件就是一个data file, 它可以被lastwho命令读取内容.

目录文件

块设备 和 字符设备

块设备是硬件设备, 以块(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

  1. 分配一个空闲的inode号,在inode table中生成新条目
  2. 在目录中创建一个目录项,将名称与inode编号关联
  3. 拷贝数据生成 新的文件

rm和inode

  1. 链接数递减,当减到0,就释放inode号
  2. 把数据块放在空闲列表中
  3. 删除目录项
  4. 数据实际上不会马上被删除,但是当另一个文件使用数据块时将被覆盖

根据3, 可以得出, 删除文件实际上是修改目录的内容, 所以删除文件必须要有目录的写权限, 所以删除文件和文件的权限没有关系, 和文件的父目录的权限有关, 同理:创建文件也是这样

mv和inode

如果mv命令的目标和源在相同的文件系统

  1. 创建新的目录项,新名称对应到inode号
  2. 删除旧的目录项
  3. 修改对应inode上的元数据(时间戳),不移动数据

如果mv命令的目标和源在不同的文件系统:mv相当于 cp + rm

1
2
# 删除大文件
[root@centos8 ~]#cat /dev/null > /var/log/huge.log

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
2
3
4
5
6
7
8
9
10
[root@4710419222 test]# cat a.log 
a
# cat 如果没有指定文件, 或者指定文件为"-", 则从标准输入读取
[root@4710419222 test]# cat > b.log <a.log
[root@4710419222 test]# cat b.log
a
[root@4710419222 test]# echo b > b.log
[root@4710419222 test]# cat < a.log > b.log
[root@4710419222 test]# cat b.log
a

多行重定向 <<终止词

这个很好理解, 类似php的长字符串<<<EOT

1
2
3
4
5
6
7
8
9
[root@4710419222 test]# cat > a.log <<EOT
> 123
> 456
> 789
> EOT
[root@4710419222 test]# cat a.log
123
456
789

管道

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
2
3
4
5
6
7
8
[root@4710419222 test]# ll c.log
ls: cannot access c.log: No such file or directory
# 命令1 2>&1 | 命令2
[root@4710419222 test]# ll c.log 2>&1 | tr a-z A-Z
LS: CANNOT ACCESS C.LOG: NO SUCH FILE OR DIRECTORY
# 命令1 |& 命令2
[root@4710419222 test]# ll c.log |& tr a-z A-Z
LS: CANNOT ACCESS C.LOG: NO SUCH FILE OR DIRECTORY

重定向中的 - 符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 将下载文件内容输出到标准输出, 而不保存文件
[root@centos8 ~]#wget -qO - http://www.wangxiaochun.com/testdir/hello.sh
#!/bin/bash
# ------------------------------------------
# Filename: hello.sh
# Version: 1.0
# Date: 2017/06/01
# Author: wang
# Email: 29308620@qq.com
# Website: www.wangxiaochun.com
# Description: This is the first script
# Copyright: 2017 wang
# License: GPL
# ------------------------------------------
#经典写法
echo "hello, world"
#流行写法
echo 'Hello, world!'

# 将 /home 里面的文件打包,但打包的数据不是记录到文件,而是传送到 stdout,经过管道后,将 tar - cvf - /home 传送给后面的 tar -xvf - , 后面的这个 - 则是取前一个命令的 stdout, 因此,就不需要使用临时file了
tar -cvf - /home | tar -xvf -

文件描述符(fd)

Linux中一切都是文件。文件描述符fd是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数,在文件open时产生。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。

系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件

文件操作相关命令

pwd

1
2
3
4
5
# -P 显示真实的物理路径
[root@centos7 bin]# pwd
/bin
[root@centos7 bin]# pwd -P
/usr/bin

basename 和 dirname

1
2
3
4
5
6
7
8
9
10
[root@4710419222 vhost]# pwd
/usr/local/nginx/conf/vhost
# 显示文件名
[root@4710419222 vhost]# basename `pwd`
vhost
[root@4710419222 vhost]# basename `pwd`\/to2b.cn.conf
to2b.cn.conf
# 显示路径
[root@4710419222 vhost]# dirname $(pwd)
/usr/local/nginx/conf

cd

1
2
3
4
5
# 切换至物理路径, 而非软链接目录 -P
[root@4710419222 ~]# cd -P /bin/
[root@4710419222 bin]# pwd
/usr/bin
# 切换到来时的目录 cd -

ls

1
2
3
4
5
6
7
8
9
10
11
12
# 显示隐藏文件,但是不显示.和..
[root@47105171233 ~]# ll -A
# 按照mtime从新到旧排序
[root@4710419222 ~]# ll -t
# 按照mtime从旧到新排序
[root@4710419222 ~]# ll -tr
# 按照atime从新到旧排序
[root@4710419222 ~]# ll -ut /
# 按照从大到小排序
[root@4710419222 /]# ll -S
# 递归遍历目录下所有文件
[root@4710419222 ~]# ll -R

说明: ls 查看不同后缀文件时的颜色由 /etc/DIR_COLORS 和@LS_COLORS变量定义

stat

文件有三个时间

1
2
3
4
5
6
7
8
9
[root@4710419222 ~]# stat test.php
File: ‘test.php’
Size: 96 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 138598 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2020-07-26 18:48:51.780437746 +0800 # 文件最近一次被访问的时间
Modify: 2020-07-25 17:21:15.970280822 +0800 # 文件内容最近一次被修改的时间
Change: 2020-07-25 17:21:15.973280860 +0800 # 文件属性最近一次被修改的时间
Birth: -

file

判断文件的类型不能依靠后缀, 可以使用file命令判断文件的类型

hexdump

hexdump命令一般用来查看“二进制”文件的十六进制编码,但实际上它能查看任何文件,而不只限于二进制文件。

-C 输出规范的十六进制和ASCII码。

1
2
3
[root@4710419222 ~]# hexdump -C test.txt       
00000000 0a |.|
00000001

文件通配符 glob

文件通配符可以用来匹配符合条件的多个文件,方便批量管理文件

1
2
3
4
5
6
7
8
9
10
11
12
 # * 匹配零个或多个字符, 但不匹配隐藏文件
[root@4710419222 ~]# ll -a *
-rw-r--r-- 1 root root 863 Oct 17 2019 clear_history.php
-rw-r--r-- 1 root root 244205697 Oct 24 2018 oneinstack-full.tar.gz
lrwxrwxrwx 1 root root 27 Jul 18 2019 python-learn -> /data/wwwroot/python-learn/
-rw-r--r-- 1 root root 1073741824 Oct 31 2018 swapfile
-rw-r--r-- 1 root root 11195 Sep 27 2019 testparm-v.txt
# ? 匹配任何单个字符
# [0-9]
# [a-z] a、A、b、B...x、X、y、Y、z、Z
# [lujinkai]
# [^lujinkai]

此外, 还有预定义的字符类: man 7 glob

1
2
3
4
5
6
7
8
9
10
11
12
[:digit:]:任意数字,相当于0-9
[:lower:]:任意小写字母,表示 a-z
[:upper:]: 任意大写字母,表示 A-Z
[:alpha:]: 任意大小写字母
[:alnum:]:任意数字或字母
[:blank:]:水平空白字符
[:space:]:水平或垂直空白字符
[:punct:]:标点符号
[:print:]:可打印字符
[:cntrl:]:控制(非打印)字符
[:graph:]:图形字符
[:xdigit:]:十六进制字符

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
2
3
4
# -a 仅改变 atime和ctime
# -m 仅改变 mtime和ctime
# -t [[CC]YY]MMDDhhmm[.ss] 指定atime和mtime的时间戳
# -c 如果文件不存在,则不予创建

cp

  • -a 归档,相当于-dR –preserv=all,常用于备份功能
  • -u –update 只复制源比目标先更新文件或目标不存在的文件
  • -b 目标存在, 覆盖前先备份, 默认形式为filename~, 只保留最近一个备份
  • –backup=numbered 目标存在, 覆盖前先备份加数字后缀, 形式为filename.~#~, 可以保留多个版本

注意: 不同类型的文件不能覆盖, 例如普通文件可以覆盖普通文件, 但是不能覆盖目录

1
2
3
4
5
6
7
8
9
10
[root@4710419222 test]# ll
drwxr-xr-x 2 root root 12288 Jul 29 15:32 test
[root@4710419222 test]# \cp -a --backup=numbered /test ./xxx
[root@4710419222 test]# \cp -a --backup=numbered /test ./xxx
[root@4710419222 test]# \cp -a --backup=numbered /test ./xxx
[root@4710419222 test]# ll
drwxr-xr-x 2 root root 12288 Jul 29 15:32 test
-rw-r--r-- 1 root root 11 Jul 29 15:30 xxx
-rw-r--r-- 1 root root 11 Jul 29 15:30 xxx.~1~
-rw-r--r-- 1 root root 11 Jul 29 15:30 xxx.~2~

mv

1
2
3
mv [OPTION]... [-T] SOURCE DEST	# 移动并重命名
mv [OPTION]... SOURCE... DIRECTORY # 移动到目录
mv [OPTION]... -t DIRECTORY SOURCE... # 目的目录参数在前, 要移动的文件参数在后

mv命令可以移动和重命名文件, 同一分区移动会很快

-b 目标存在, 覆盖前先备份

但是mv命令只能一次重命名一个文件, 使用rename命令可以批量重命名文件

rename

1
2
3
4
5
6
7
8
9
10
11
# rename [options] <expression> <replacement> <file>...
# 修改后缀
[root@4710419222 test]# ll
total 4
-rw-r--r-- 1 root root 18 Jul 29 19:36 a.txt
-rw-r--r-- 1 root root 0 Jul 29 19:14 b.txt
[root@4710419222 test]# rename '.txt' '.log' ./*
[root@4710419222 test]# ll
total 4
-rw-r--r-- 1 root root 18 Jul 29 19:36 a.log
-rw-r--r-- 1 root root 0 Jul 29 19:14 b.log

这个命令好像可以使用正则, 但是我试了一下, 不行, 可能是版本的问题

rm

此命令非常危险, 建议使用mv替代rm , 当删除的时候, 实际上是移动到一个特定的目录

1
alias rm='DIR=/data/backup`date +%F%T`;mkdir $DIR;mv -t $DIR'

rm虽然删除了文件, 但是在安全场景要求较高的情况下, 可以使用shred安全删除文件

1
2
3
4
5
[root@centos8 ~]#shred -zvun 5 passwords.txt
# -z 最后一次覆盖添加0,以隐藏覆盖操作
# -v 能够显示操作进度
# -u 覆盖后截断并删除文件
# -n 指定覆盖文件内容的次数(默认值是3次)

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
2
3
4
5
6
[root@centos8 ~]#cat <<EOF | tee /etc/motd
> welcome to magedu
> happy new year
> EOF
welcome to magedu
happy new year

lsof

列出打开的文件,Linux下万物皆文件,网络也是文件。
lsof的参数巨多,下面列举常用的参数

1
lsof file	# 查看文件被哪些进程占用
1
2
lsof -i		# 列出所有的网络连接
lsof -i tcp # 列出所有的tcp网络连接

seq

打印数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@centos8 ~]# seq 0 9
0
1
2
3
4
5
6
7
8
9
[root@centos8 ~]# seq -s ' ' 0 9
0 1 2 3 4 5 6 7 8 9
[root@centos8 ~]# seq -s ' ' 0 2 9
0 2 4 6 8
[root@centos8 ~]# seq -s + 1 100 | bc
5050
[root@centos8 ~]# seq -s + 1 2 100 | bc
2500

练习题

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
2
[root@centos8 test]# seq -s + 1 100 | bc
5050

6、删除Windows文本文件中的回车字符 ,即“\r”

1
cat a.log | tr '\r' ' '

7、处理字符串“xt.,l 1 jr#!$mn 2 c*/fe 3 uz 4”,只保留其中的数字和空格

1
2
[root@centos8 test]# echo 'xt.,l 1 jr#!$mn 2 c*/fe 3 uz 4' | tr -dc '0-9 '
1 2 3 4[root@centos8 test]#

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
2
3
4
5
[root@centos8 test]# cat /etc/centos-release | tr -c "[:alpha:]" " " | tr -s " " "\n"
CentOS
Linux
release
Core

11、ls 输出的内容明明是分行的, 为什么显示出来就不分行了?

不单 ls 会这样,不少其他命令也会这样。它们会使用 isatty 函数查询输出是否指向终端,对输出到终端和非终端的处理,可能不一样

常见的比如:
1、输出到终端时,使用 color,非终端则不用;
2、输出到终端时,使用 text 方式,非终端则用 binary。

如果需要一致的输出,应当明确使用相关参数

這取決於 stdout 是不是終端。如果是終端就可以讀取終端的寬度,根據寬度排版。