Linux 系统将每个对象当作文件处理。Linux 用文件描述符(file descriptor)来标识每个文件对象。它是一个非负整数。

每个进程一次最多可以有九个文件描述符。bash shell 保留了个前三个(0、1、2)。

0 STDIN,标准输入;

1 STDOUT,标准输出;

2 STDERR,标准错误;

当在命令行上只输入 cat 命令时,它会从 STDIN 接受输入。输入一行,cat 命令就会显示出一行。

可以通过 STDIN 重定向符号强制 cat 命令接受来自另一个非 STDIN 文件的输入。
cat < testfile

  1. # 只重定向错误
  2. $ ls -la badfile 2> errorfile
  3. # 重定向错误和数据
  4. $ ls -la test test2 badfile 2> errorfile 1> outfile
  5. # 重定向错误和数据到一个文件
  6. $ ls -la test test2 badfile &> outfile
  7. # 注意,为了避免错误信息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级。
  8. # 所以输出文件中,错误信息集中显示在最开头

在脚本中重定向输出、输入

在重定向到文件描述符时,必需在文件描述符数字之前加一个 &

  1. echo "This is an error msg" >&2
  2. echo "This is a normal msg"

如果脚本中有大量数据需要重定向,重定向每个 echo 语句就很烦琐。可以使用 exec告诉 shell 在脚本执行期间重定向某个特定文件描述符。exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到文件。

  1. exec 1>testout
  2. echo "This is a test of redirecting all outoupt"
  3. echo " from a script to another file"
  4. echo "This is an other msg" >&2
  5. exec 2>testerror
  6. echo "This is an other normal msg"
  7. echo "This is an other msg2" >&2

重定向输入:

  1. exec 0< states
  2. count=1
  3. while read line
  4. do
  5. echo "State #$count: $line"
  6. count=$((count+1))
  7. done

创建自己的重定向

其它6个3~8的文件描述符可用作输入或输出重定向。

  1. exec 3>test28out
  2. # 当然也可以追加
  3. # exec 3>>test28out
  4. echo "1. This should display on the monitor"
  5. echo "and this should be stored in the file" >&3

一旦重定向了 STDOUT 或 STDERR,怎么再切回来呢?

  1. # 在脚本中临时重定向输出,然后恢复默认输出设置
  2. # 3->monitor
  3. exec 3>&1
  4. echo "2. This should display on the monitor"
  5. # 1->file
  6. exec 1>test28out2
  7. echo "This should be stored in the file"
  8. echo "3. This should display on the monitor" >&3
  9. # 1->monitor
  10. exec 1>&3
  11. echo "4. This should display on the monitor"

同样的方法用在重定向输入中:

  1. #!/bin/bash
  2. # 保存 STDIN,之后再恢复
  3. exec 6<&0
  4. exec 0< states
  5. count=1
  6. while read line
  7. do
  8. echo "State #$count: $line"
  9. count=$((count+1))
  10. done
  11. exec 0<&6
  12. read -p "Are you done now? " answer
  13. case $answer in
  14. Y|y) echo "Goodbye";;
  15. N|n) echo "Sorry, this is the end";;
  16. esac

创建读写文件描述符,即打开单个文件描述符来作为输入和输出,注意,任何读或写都会从文件指针上次的位置开始

  1. #!/bin/bash
  2. # 创建读写文件描述符
  3. exec 3<>testfile
  4. # 读第一行
  5. read line <&3
  6. echo "Read: $line"
  7. # 此时文件指针在第二行的开头
  8. echo "This is a test line" >&3
  9. #testfile
  10. # This is the first line.
  11. # This is the second line.
  12. # This is the third line.
  13. # 运行结束查看 testfile
  14. # This is the first line.
  15. # This is a test line
  16. # ine.
  17. # This is the third line.

关闭文件描述符 exec 3>&-,关闭之后就不能使用了:

  1. #!/bin/bash
  2. exec 3>test31file
  3. echo "This is a test line of data" >&3
  4. # 关闭文件描述符
  5. # mac没有生效
  6. echo 3>&-
  7. echo "This won't work" >&3

如果在关闭之后,随后在脚本中打开了同一个输出文件,会用一个新文件来替换已有文件。

  1. #!/bin/bash
  2. exec 3>test32file
  3. echo "This is a test line of data" >&3
  4. exec 3>&-
  5. cat test32file
  6. # 会覆盖上面的文件
  7. exec 3>test32file
  8. echo "This'll override file" >&3

列出打开的文件描述符 lsof

lsof 会列出整个 Linux 系统打开的所有文件描述符。

在很多 Linux 系统中,lsof 命令位于 /usr/sbin 目录,要想以普通用户账号来运行,必需通过全路径名来引用:/usr/sbin/lsof

-p 指定进程ID(PID)

-d 指定要显示的文件描述符编号

-a 用来对其它两个选项的结果执行布尔 AND 运算

$$ 环境变量,当前进程的PID

  1. $ lsof -a -p $$ -d 0,1,2
  2. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
  3. zsh 45488 xiaocan 0u CHR 16,0 0t1828098 4717 /dev/ttys000
  4. zsh 45488 xiaocan 1u CHR 16,0 0t1828098 4717 /dev/ttys000
  5. zsh 45488 xiaocan 2u CHR 16,0 0t1828098 4717 /dev/ttys000

FD 文件描述符以及访问类型(r-read,w-write,u-读写)

TYPE 文件的类型(CHR 代表字符型,BLK 代表块型,DIR 代表目录,REG 代表常规文件)

与 STDIN、STDOUT 和 STDERR 关联的文件类型是字符型。

  1. #!/bin/bash
  2. exec 3> test33file1
  3. exec 6> test33file2
  4. exec 7< states
  5. /usr/sbin/lsof -a -p $$ -d 0,1,2,3,6,7,8
  6. # 结果都是 REG 类型的,代表它们都是常规的文件
  7. $ sh test33-lsof.sh
  8. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
  9. bash 48443 xiaocan 0u CHR 16,1 0t22389 5055 /dev/ttys001
  10. bash 48443 xiaocan 1u CHR 16,1 0t22389 5055 /dev/ttys001
  11. bash 48443 xiaocan 2u CHR 16,1 0t22389 5055 /dev/ttys001
  12. bash 48443 xiaocan 3w REG 1,4 0 8635172069 /Users/xiaocan/Documents/learn/shell-learn/test33file1
  13. bash 48443 xiaocan 6w REG 1,4 0 8635172070 /Users/xiaocan/Documents/learn/shell-learn/test33file2
  14. bash 48443 xiaocan 7r REG 1,4 50 8634917711 /Users/xiaocan/Documents/learn/shell-learn/states

阻止命令输出

将文件重定向到 /dev/null 文件。

可以用它来清空日志文件:cat /dev/null > logfile

创建临时文件

系统上任何用户都有权限读写 /tmp 目录中的文件。

  1. # mac 上的运行结果
  2. $ mktemp
  3. /var/folders/gc/j2xc0rlx5bx4frx_54gb6sbr0000gn/T/tmp.olLTfDFA
  4. $ mktemp testing.XXXXXX
  5. testing.t3DvSz
  6. # 只有属主用户有权限
  7. $ ls -l testing.t3DvSz
  8. -rw------- 1 xiaocan staff 0 Feb 13 20:15 testing.t3DvSz
  9. # -t 参数指定在临时文件夹中创建文件
  10. $ mktemp -t testing.XXXXXX
  11. /var/folders/gc/j2xc0rlx5bx4frx_54gb6sbr0000gn/T/testing.XXXXXX.B5qQrUge
  12. # -d 参数创建临时文件夹
  13. $ mktemp -d
  14. # 在脚本中使用
  15. # tempfile=$(mktemp test34.XXXXXX)

记录消息 tee

同时重定向到控制台和文件中。

tee 命令相当于管道的一个T型接头。 它将从 STDIN 过来的数据同时发往两处。 一处是 STDOUT,另一处是tee命令行所指定的文件名:

  1. $ date | tee testfile
  2. echo "test message" | tee testfile
  3. # 使用 -a 追加
  4. echo "test message" | tee -a testfile