IO详解一 linux 操作系统的文件命令与管道 Shepard-Wang

一 操作系统级 IO

1、虚拟文件系统

df 命令

检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

❯ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            894M     0  894M   0% /dev
tmpfs           188M  1.1M  187M   1% /run
/dev/vda1        59G  9.6G   47G  18% /
tmpfs           940M     0  940M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           940M     0  940M   0% /sys/fs/cgroup
tmpfs           188M  8.0K  188M   1% /run/user/116
tmpfs           188M     0  188M   0% /run/user/0

df filename 可以看到文件所在的块设备

du 命令

du 命令是对文件和目录磁盘使用的空间的查看

du 递归查看当前目录下的所有目录的空间占用

du -a 查看目录以及文件

fdisk

fdisk 是 Linux 的磁盘分区表操作工具。

  • -l :输出后面接的装置所有的分区内容。若仅有 fdisk -l 时, 则系统将会把整个系统内能够搜寻到的装置的分区均列出来。

mount 与 unmount

Linux 的磁盘挂载使用 mount 命令,卸载使用 umount 命令。

用默认的方式,将刚刚创建的 /dev/hdc6 挂载到 /mnt/hdc6 上面!

[root@www ~]# mkdir /mnt/hdc6
[root@www ~]# mount /dev/hdc6 /mnt/hdc6
[root@www ~]# df
Filesystem           1K-blocks      Used Available Use% Mounted on
.....中间省略.....
/dev/hdc6              1976312     42072   1833836   3% /mnt/hdc6

磁盘卸载命令 umount 语法:

umount [-fn] 装置文件名或挂载点

选项与参数:

  • -f :强制卸除!可用在类似网络文件系统 (NFS) 无法读取到的情况下;
  • -n :不升级 /etc/mtab 情况下卸除。

卸载/dev/hdc6

[root@www ~]# umount /dev/hdc6  

io1.png

软连接与硬链接

硬链接:

ln 1.txt 2.txt 为文件 1.txt 创建硬链接 2.txt

❯ touch main.c
❯ ls
dou  main.c  tool  work
❯ ln main.c hard_main.c
❯ ls
dou  hard_main.c  main.c  tool  work
❯ ls -l
total 12
drwxr-xr-x 4 root root 4096 Jan  4 16:51 dou
-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 2 root root    0 May 15 19:53 main.c
drwxr-xr-x 3 root root 4096 Jan  1 03:10 tool
drwxr-xr-x 2 root root 4096 Dec  9 16:00 work
-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 2 root root    0 May 15 19:53 main.c

main.chard_main.c 的引用变为了 2

❯ stat main.c
  File: main.c
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: fc01h/64513d	Inode: 655656      Links: 2
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2022-05-15 19:53:32.870154505 +0800
Modify: 2022-05-15 19:53:32.870154505 +0800
Change: 2022-05-15 19:53:45.574415596 +0800
 Birth: -
 
❯ stat hard_main.c
  File: hard_main.c
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: fc01h/64513d	Inode: 655656      Links: 2
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2022-05-15 19:53:32.870154505 +0800
Modify: 2022-05-15 19:53:32.870154505 +0800
Change: 2022-05-15 19:53:45.574415596 +0800
 Birth: -

两个文件 inode 一样

main.chard_main.c 在磁盘上只有一份,但是在操作系统的虚拟文件系统中有不同的 path

修改 main.chard_main.c 也会改变。删除 main.chard_main.c 不受影响。

软连接:

ln -s 1.txt 2.txt 为文件 1.txt 创建软链接 2.txt

❯ ln -s main.c soft_main.c
❯ ls -l
total 12
drwxr-xr-x 4 root root 4096 Jan  4 16:51 dou
-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 2 root root    0 May 15 19:53 main.c
lrwxrwxrwx 1 root root    6 May 15 20:00 soft_main.c -> main.c
drwxr-xr-x 3 root root 4096 Jan  1 03:10 tool
drwxr-xr-x 2 root root 4096 Dec  9 16:00 work

-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 2 root root    0 May 15 19:53 main.c
lrwxrwxrwx 1 root root    6 May 15 20:00 soft_main.c -> main.c

main.chard_main.c 的引用还是 2soft_main.c 的引用为 1

❯ stat soft_main.c
  File: soft_main.c -> main.c
  Size: 6         	Blocks: 0          IO Block: 4096   symbolic link
Device: fc01h/64513d	Inode: 656326      Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2022-05-15 20:00:59.203327075 +0800
Modify: 2022-05-15 20:00:57.451291069 +0800
Change: 2022-05-15 20:00:57.451291069 +0800
 Birth: -


❯ stat main.c
  File: main.c
  Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
Device: fc01h/64513d	Inode: 655656      Links: 2
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2022-05-15 19:53:32.870154505 +0800
Modify: 2022-05-15 19:53:32.870154505 +0800
Change: 2022-05-15 19:53:45.574415596 +0800
 Birth: -

main.csoft_main.c 具有不同的 inode

软连接,也称符号链接。类似 windows 中的快捷方式

修改 soft_main.c ,实际上就是修改main.c 。删除 main.csoft_main.c 指向丢失,无法打开。

dd 命令

dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。

  • if=文件名:输入文件名,默认为标准输入。即指定源文件。
  • of=文件名:输出文件名,默认为标准输出。即指定目的文件。
  • ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。 obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。 bs=bytes:同时设置读入/输出的块大小为bytes个字节。
  • cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
  • skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
  • seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
  • count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
  • conv=<关键字>,关键字可以有以下11种:
    • conversion:用指定的参数转换文件。
    • ascii:转换ebcdic为ascii
    • ebcdic:转换ascii为ebcdic
    • ibm:转换ascii为alternate ebcdic
    • block:把每一行转换为长度为cbs,不足部分用空格填充
    • unblock:使每一行的长度都为cbs,不足部分用空格填充
    • lcase:把大写字符转换为小写字符
    • ucase:把小写字符转换为大写字符
    • swap:交换输入的每对字节
    • noerror:出错时不停止
    • notrunc:不截短输出文件
    • sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。

将文件 lowercase.txt 中的所有小写字母编程大写字母并输出到文件 uppercase.txt

❯ dd if=lowercase.txt of=uppercase.txt conv=ucase
0+1 records in
0+1 records out
52 bytes copied, 0.000335669 s, 155 kB/s
❯ ls
dou  hard_main.c  lowercase.txt  main.c  soft_main.c  tool  uppercase.txt  work
❯ cat lowercase.txt
Hello! This is an example for commamd dd.
Goodbye~~
❯ cat uppercase.txt
HELLO! THIS IS AN EXAMPLE FOR COMMAMD DD.
GOODBYE~~

losetup 命令

循环设备可把文件虚拟成区块设备,籍以模拟整个文件系统,让用户得以将其视为硬盘驱动器,光驱或软驱等设备,并挂入当作目录来使用。

(1)创建空的磁盘镜像文件,这里创建一个1.44M的软盘

$ dd if=/dev/zero of=floppy.img bs=512 count=2880

(2)使用 losetup将磁盘镜像文件虚拟成块设备

$ losetup /dev/loop1 floppy.img

(3)挂载块设备

$ mount /dev/loop0 /tmp

经过上面的三步之后,我们就可以通过/tmp目录,像访问真实块设备一样来访问磁盘镜像文件floppy.img。

mke2fs 命令

Linux mke2fs 命令用于建立 ext2 文件系统。

使用文件模拟磁盘并挂载

/dev/zero在被读取时会提供无限的空字符(ASCII NUL, 0x00)

制作一个大小为 100M (100 个大小为 1M 的块)的文件

#制作文件
❯ dd if=/dev/zero of=boot.img bs=1048574 count=100
100+0 records in
100+0 records out
104857400 bytes (105 MB, 100 MiB) copied, 0.177023 s, 592 MB/s
    
❯ ls
boot.img  dou  hard_main.c  lowercase.txt  main.c  soft_main.c  tool  uppercase.txt  work

❯ ls -l boot.img
-rw-r--r-- 1 root root 104857400 May 16 00:30 boot.img

#将文件虚拟化为块设备
❯ losetup /dev/loop0 boot.img
losetup: boot.img: Warning: file does not fit into a 512-byte sector; the end of the file will be ignored.

#使用 ext2 格式化
❯ mke2fs /dev/loop0
mke2fs 1.45.5 (07-Jan-2020)
Discarding device blocks: done                            
Creating filesystem with 25599 4k blocks and 25600 inodes

Allocating group tables: done                            
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done

❯ mkdir /mnt/mydisk

❯ ls -l /mnt/mydisk
total 0

# 将 /dev/loop0 挂载到 /mnt/mydisk
❯ mount /dev/loop0 /mnt/mydisk

❯ df
Filesystem     1K-blocks     Used Available Use% Mounted on
udev              915128        0    915128   0% /dev
tmpfs             192504     1084    191420   1% /run
/dev/vda1       61795304 10048704  49004104  18% /
...
/dev/loop0         99180       48     94016   1% /mnt/mydisk #新增了记录

❯ cd /mnt/mydisk
❯ ls -al
total 24
drwxr-xr-x 3 root root  4096 May 16 01:11 .
drwxr-xr-x 3 root root  4096 May 16 09:47 ..
drwx------ 2 root root 16384 May 16 01:11 lost+found

#增加zsh可执行程序
❯ whereis zsh
zsh: /usr/bin/zsh /usr/lib/x86_64-linux-gnu/zsh /etc/zsh /usr/share/zsh /usr/share/man/man1/zsh.1.gz
❯ mkdir -p usr/bin
❯ cp /usr/bin/zsh usr/bin/
❯ ls usr/bin
zsh

#增加依赖的库
❯ ldd bash
ldd: ./bash: No such file or directory
❯ ldd /usr/bin/zsh
	linux-vdso.so.1 (0x00007fff20cfd000)
	libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007fc92cc4f000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc92cc49000)
	libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007fc92cc19000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc92caca000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc92c8d8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc92cd59000)

❯ mkdir -p lib/x86_64-linux-gnu
❯ mkdir lib64
❯ cp /lib/x86_64-linux-gnu/{libcap.so.2,libdl.so.2,libtinfo.so.6,libc.so.6,libm.so.6} lib/x86_64-linux-gnu/
❯ cp /lib64/ld-linux-x86-64.so.2 lib64

# 切换根目录到 /mnt/mydisk
❯ chroot ./
zsh: failed to load module `zsh/zle': /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/zle.so: cannot open shared object file: No such file or directory
iZuf66sz7dk0i1hksp2tlaZ# ls 
zsh: command not found: ls
#在根目录下创建文件 test.txt   
iZuf66sz7dk0i1hksp2tlaZ# echo "test" > test.txt
iZuf66sz7dk0i1hksp2tlaZ# echo $$                
60130
iZuf66sz7dk0i1hksp2tlaZ# exit                                                                                                      
# 进程号不同
❯ echo $$
58762
# test.txt 出现在了 /mnt/mydisk 目录下而未出现在 / 目录下
❯ ls
lib  lib64  lost+found  test.txt  usr
❯ ls /
bin   dev  home  lib32  libx32      media  opt   root  sbin  srv       sys   test.c  usr
boot  etc  lib   lib64  lost+found  mnt    proc  run   snap  swapfile  test  tmp     var

lsof 命令

lsof

  • -p 查看进程打开文件
  • -o 查看文件的 offset

lsof 命令不仅可以查看进程打开的文件、目录,还可以查看进程监听的端口等 socket 相关的信息

FD 列中的常见内容有 cwd、rtd、txt、mem 和一些数字等等。其中 cwd 表示当前的工作目录;rtd 表示根目录;txt 表示程序的可执行文件;mem 表示内存映射文件。

u表示文件为读写,r为读取,w为写入

lsof -p process_id 查看进程打开的文件:

❯ lsof -p $$
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/116/gvfs
      Output information may be incomplete.
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     60895 root  cwd    DIR  252,1     4096  655362 /root
zsh     60895 root  rtd    DIR  252,1     4096       2 /
zsh     60895 root  txt    REG  252,1   878288 1205122 /usr/bin/zsh
zsh     60895 root  mem    REG  252,1   198112 1708416 /usr/share/zsh/functions/Zle.zwc
zsh     60895 root  mem    REG  252,1    28008 1708542 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/files.so
zsh     60895 root  mem    REG  252,1    14632 1708548 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/net/socket.so
zsh     60895 root  mem    REG  252,1    14760 1708564 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/zleparameter.so
zsh     60895 root  mem    REG  252,1    14696 1708560 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/termcap.so
zsh     60895 root  mem    REG  252,1    25768 1708545 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/mathfunc.so
zsh     60895 root  mem    REG  252,1   202728 1708234 /usr/share/zsh/functions/Misc.zwc
zsh     60895 root  mem    REG  252,1    14624 1708555 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/regex.so
zsh     60895 root  mem    REG  252,1    19000 1708537 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/datetime.so
zsh     60895 root  mem    REG  252,1   294424 1707343 /usr/share/zsh/functions/Completion/Base.zwc
zsh     60895 root  mem    REG  252,1    18912 1708558 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/stat.so
zsh     60895 root  mem    REG  252,1    72136 1708534 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/complist.so
zsh     60895 root  mem    REG  252,1    32680 1708559 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/system.so
zsh     60895 root  mem    REG  252,1    15304 1708543 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/langinfo.so
zsh     60895 root  mem    REG  252,1   189744 1708185 /usr/share/zsh/functions/Completion.zwc
zsh     60895 root  mem    REG  252,1    49264 1708553 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/parameter.so
zsh     60895 root  mem    REG  252,1    39400 1708568 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/zutil.so
zsh     60895 root  mem    REG  252,1   155704 1708533 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/complete.so
zsh     60895 root  mem    REG  252,1   335808 1708563 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/zle.so
zsh     60895 root  mem    REG  252,1    51832 1195271 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
zsh     60895 root  mem    REG  252,1  5699248 1180613 /usr/lib/locale/locale-archive
zsh     60895 root  mem    REG  252,1  2029224 1185459 /usr/lib/x86_64-linux-gnu/libc-2.31.so
zsh     60895 root  mem    REG  252,1  1369352 1185461 /usr/lib/x86_64-linux-gnu/libm-2.31.so
zsh     60895 root  mem    REG  252,1   192032 1182764 /usr/lib/x86_64-linux-gnu/libtinfo.so.6.2
zsh     60895 root  mem    REG  252,1    18816 1185460 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
zsh     60895 root  mem    REG  252,1    31120 1182572 /usr/lib/x86_64-linux-gnu/libcap.so.2.32
zsh     60895 root  mem    REG  252,1    14696 1708561 /usr/lib/x86_64-linux-gnu/zsh/5.8/zsh/terminfo.so
zsh     60895 root  mem    REG  252,1    27002 1195536 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
zsh     60895 root  mem    REG  252,1   191472 1182753 /usr/lib/x86_64-linux-gnu/ld-2.31.so
zsh     60895 root    0u   CHR  136,0      0t0       3 /dev/pts/0
zsh     60895 root    1u   CHR  136,0      0t0       3 /dev/pts/0
zsh     60895 root    2u   CHR  136,0      0t0       3 /dev/pts/0
zsh     60895 root   10u   CHR  136,0      0t0       3 /dev/pts/0
zsh     60895 root   11w  FIFO  252,1      0t0 1182530 /tmp/gitstatus.POWERLEVEL9K.0.60895.1652688319.1.fifo (deleted)
zsh     60895 root   12r   REG  252,1   189744 1708185 /usr/share/zsh/functions/Completion.zwc
zsh     60895 root   13w  FIFO  252,1      0t0 1182538 /tmp/p10k.worker.0.60895.1652688319.fifo (deleted)
zsh     60895 root   14r   REG  252,1   294424 1707343 /usr/share/zsh/functions/Completion/Base.zwc
zsh     60895 root   17r   REG  252,1   198112 1708416 /usr/share/zsh/functions/Zle.zwc
zsh     60895 root   18r  FIFO   0,13      0t0 8588796 pipe
zsh     60895 root   19r  FIFO   0,13      0t0 8589443 pipe
zsh     60895 root   21r   REG  252,1   202728 1708234 /usr/share/zsh/functions/Misc.zwc

其中查看到的文件描述符保存于目录 /proc/进程号/fd

❯ echo $$
60895
❯ ll /proc/60895/fd
total 0
lrwx------ 1 root root 64 May 16 16:05 0 -> /dev/pts/0
lrwx------ 1 root root 64 May 16 16:05 1 -> /dev/pts/0
lrwx------ 1 root root 64 May 16 16:05 10 -> /dev/pts/0
l-wx------ 1 root root 64 May 16 16:05 11 -> '/tmp/gitstatus.POWERLEVEL9K.0.60895.1652688319.1.fifo (deleted)'
lr-x------ 1 root root 64 May 16 16:05 12 -> /usr/share/zsh/functions/Completion.zwc
l-wx------ 1 root root 64 May 16 16:05 13 -> '/tmp/p10k.worker.0.60895.1652688319.fifo (deleted)'
lr-x------ 1 root root 64 May 16 16:05 14 -> /usr/share/zsh/functions/Completion/Base.zwc
lr-x------ 1 root root 64 May 16 16:05 17 -> /usr/share/zsh/functions/Zle.zwc
lr-x------ 1 root root 64 May 16 16:05 18 -> 'pipe:[8588796]'
lr-x------ 1 root root 64 May 16 16:05 19 -> 'pipe:[8589443]'
lrwx------ 1 root root 64 May 16 16:05 2 -> /dev/pts/0
lr-x------ 1 root root 64 May 16 16:05 21 -> /usr/share/zsh/functions/Misc.zwc
lr-x------ 1 root root 64 May 16 16:05 8 -> /root/file

exec 命令

# 将文件 file 与文件描述符 8 绑定
❯ exec 8< file
❯ lsof -p $$
...
zsh     60895 root    8r   REG  252,1       24  670217 /root/file
...

#从文件描述符 8 指向的文件中读取一行数据放入变量 a 中
❯ read a 0<&8
❯ echo $a
This
❯ read a 0<&8
❯ echo $a
is
❯ lsof -op $$ | grep /root/file
zsh     60895 root    8r   REG  252,1    0t8  670217 /root/file

子进程会继承父进程的文件描述符表

❯ echo $$
60895
# 创建子进程
❯ zsh
❯ echo $$
61260
❯ lsof -op $$ | grep /root/file
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/116/gvfs
      Output information may be incomplete.
# 8 文件描述符依然存在
zsh     61260 root    8r   REG  252,1    0t8  670217 /root/file

#新启一个终端
❯ echo $$
61387
❯ lsof -op $$ | grep /root/file
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/116/gvfs
      Output information may be incomplete.
# 8 找不到了

下面是通过 pstree 命令看到的进程关系图

├─sshd─┬─sshd───zsh───zsh───pstree 
│      └─sshd───zsh #新打开的终端

参考:linux exec与重定向

bash 中的 /dev/tcp

Linux中的一个特殊文件: /dev/tcp 打开这个文件就类似于发出了一个socket调用,建立一个socket连接,读写这个文件就相当于在这个socket连接中传输数据。

/dev/tcp/host/port 其实是一个 bash 的 feature,由于是 bash的 feature,因此在别的 shell下就不能生效,所以需要注意使用shell类型。

root@iZuf66sz7dk0i1hksp2tlaZ:/proc# exec 9<> /dev/tcp/www.baidu.com/80

root@iZuf66sz7dk0i1hksp2tlaZ:/proc# lsof -p $$
lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /run/user/116/gvfs
      Output information may be incomplete.
COMMAND   PID USER   FD   TYPE  DEVICE SIZE/OFF    NODE NAME
...
bash    61823 root    9u  IPv4 8617541      0t0     TCP iZuf66sz7dk0i1hksp2tlaZ:50196->112.80.248.75:http (ESTABLISHED)
...

2、管道 pipeline

重定向

例 1

❯ ll > output #等价于 ll 1> output
❯ cat output
total 9.1M
-rw-r--r-- 1 root root 100M May 16 17:03 boot.img
drwxr-xr-x 4 root root 4.0K Jan  4 16:51 dou
-rw-r--r-- 1 root root   24 May 16 16:25 file
-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 1 root root   52 May 16 00:26 lowercase.txt
-rw-r--r-- 2 root root    0 May 15 19:53 main.c
-rw-r--r-- 1 root root    0 May 16 17:26 output
lrwxrwxrwx 1 root root    6 May 15 20:00 soft_main.c -> main.c
drwxr-xr-x 3 root root 4.0K Jan  1 03:10 tool
-rw-r--r-- 1 root root   52 May 16 00:27 uppercase.txt
drwxr-xr-x 2 root root 4.0K Dec  9 16:00 work

❯ cat output > output2 #等价于 cat 0< output 1> output2
❯ cat output2
total 9.1M
-rw-r--r-- 1 root root 100M May 16 17:03 boot.img
drwxr-xr-x 4 root root 4.0K Jan  4 16:51 dou
-rw-r--r-- 1 root root   24 May 16 16:25 file
-rw-r--r-- 2 root root    0 May 15 19:53 hard_main.c
-rw-r--r-- 1 root root   52 May 16 00:26 lowercase.txt
-rw-r--r-- 2 root root    0 May 15 19:53 main.c
-rw-r--r-- 1 root root    0 May 16 17:26 output
lrwxrwxrwx 1 root root    6 May 15 20:00 soft_main.c -> main.c
drwxr-xr-x 3 root root 4.0K Jan  1 03:10 tool
-rw-r--r-- 1 root root   52 May 16 00:27 uppercase.txt
drwxr-xr-x 2 root root 4.0K Dec  9 16:00 work

例 2:

❯ ls . dir
ls: cannot access 'dir': No such file or directory
.:
boot.img  dou  file  hard_main.c  lowercase.txt  main.c  soft_main.c  tool  uppercase.txt  work

如果我们想让 ls 的标准输出和错误输出不显示到屏幕,而是显示到文件中

❯ ls . dir 1> stdout 2> stderr
❯ cat stdout
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
stderr
stdout
tool
uppercase.txt
work
❯ cat stderr
ls: cannot access 'dir': No such file or directory

当我们试图将标准输出和标准错误都重定向到同一个文件时:

❯ ls . dir 1> std 2> std
❯ cat std
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
std
stderr
stdout
tool
uppercase.txt
work

发现标准错误被覆盖了!这是因为文件 std 被打开了两次且每次都是覆盖写。

我们可以通过修改写文件的方式为追加解决这个问题:

❯ ls . dir 1>> std 2>> std
❯ cat std
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
std
stderr
stdout
tool
uppercase.txt
work
ls: cannot access 'dir': No such file or directory
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
std
stderr
stdout
tool
uppercase.txt
work

我们还可以将标准错误绑定到标准输出上,也可以解决覆盖问题,并且 std 文件只被打开了一次,效率更高。

❯ echo > std
❯ ls . dir 2>&1 1>std
ls: cannot access 'dir': No such file or directory
❯ cat std
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
std
stderr
stdout
tool
uppercase.txt
work

通过输出发现标准错误并没用重定向到 std 文件中,这是因为绑定操作存在顺序,标准错误2先绑定到文件描述符1也就是标准输出上,让后标准输出才重定向到 std 文件上。

正确的写法是:

❯ ls . dir 1>std 2>&1
❯ cat std
ls: cannot access 'dir': No such file or directory
.:
boot.img
dou
file
hard_main.c
lowercase.txt
main.c
soft_main.c
std
stderr
stdout
tool
uppercase.txt
work

{ } 命令 (命令块)

一次执行多条命令:

❯ { mkdir test; mv main.c test; cd test; }
❯ ls
main.c
❯ pwd
/root/test

管道

例 1

如何只显示文件中的第八行?

❯ cat file
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 8

❯ head -8 file | tail -1
line 8

例 2

创建一个变量设定一个初始值,然后通过带管道的命令行左侧表达式给 a 重新赋值,解释完该行命令后再次输出 a 的值,发现 a 并未改变。

❯ a=1
❯ echo $a
1
❯ { a=10; echo "hello"; } | cat
hello
❯ echo $a
1

管道 |左右两侧会各启动一个子进程分别解释命令,然后将管道左侧的标准输出重定向给管道,同时将右侧的标准输出重定向到管道,以此完成两个子进程间的数据传递。

因此,子进程中对变量 a 的修改无法改变父进程的变量的值。

在父进程中将 a 设为 export

❯ export a
❯ a=10 | a=9
❯ echo $a
9
$$ 与 $BASHPID

观察下面的运行结果:

❯ echo $$
64414
❯ echo $$ | cat
64414

按理第二次 echo 的输出应该为管道左侧子进程的 PID,这里却和父进程一样!

这是因为 $$ 的优先级高于管道创建子进程,这里将 $$ 改为 $BASHPID 就可以正确的输出

例 3

❯ echo $$
62765
❯ { read a; echo "read a finish"; } | { read b; echo "read b finish"; }
❯ 

管道左右侧子进程阻塞

使用 ps -ef 查看进程:

❯ ps -ef | grep "62765"
root       62765   62640  0 20:36 pts/0    00:00:04 -zsh
root       65352   62765  0 21:11 pts/0    00:00:00 -zsh

进程 6535262765(主调进程)的子进程

查看进程65352打开的文件描述符:

❯ ll /proc/65352/fd
total 0
lrwx------ 1 root root 64 May 16 21:11 0 -> /dev/pts/0
l-wx------ 1 root root 64 May 16 21:11 1 -> 'pipe:[8685064]'
lrwx------ 1 root root 64 May 16 21:11 2 -> /dev/pts/0

标准输出 1 重定向到了管道

> lsof -p 65352
zsh     65352 root    0u   CHR  136,0      0t0       3 /dev/pts/0
# FD 变为了只写 1w TYPE 为 FIFO 
zsh     65352 root    1w  FIFO   0,13      0t0 8685064 pipe

zsh     65352 root    2u   CHR  136,0      0t0       3 /dev/pts/0
zsh     65352 root   10u   CHR  136,0      0t0       3 /dev/pts/0