Linux 系统将每个对象当作文件处理。Linux 用文件描述符(file descriptor)来标识每个文件对象。它是一个非负整数。
每个进程一次最多可以有九个文件描述符。bash shell 保留了个前三个(0、1、2)。
0 STDIN,标准输入;
1 STDOUT,标准输出;
2 STDERR,标准错误;
当在命令行上只输入 cat 命令时,它会从 STDIN 接受输入。输入一行,cat 命令就会显示出一行。
可以通过 STDIN 重定向符号强制 cat 命令接受来自另一个非 STDIN 文件的输入。cat < testfile
# 只重定向错误$ ls -la badfile 2> errorfile# 重定向错误和数据$ ls -la test test2 badfile 2> errorfile 1> outfile# 重定向错误和数据到一个文件$ ls -la test test2 badfile &> outfile# 注意,为了避免错误信息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级。# 所以输出文件中,错误信息集中显示在最开头
在脚本中重定向输出、输入
在重定向到文件描述符时,必需在文件描述符数字之前加一个 &
echo "This is an error msg" >&2echo "This is a normal msg"
如果脚本中有大量数据需要重定向,重定向每个 echo 语句就很烦琐。可以使用 exec告诉 shell 在脚本执行期间重定向某个特定文件描述符。exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到文件。
exec 1>testoutecho "This is a test of redirecting all outoupt"echo " from a script to another file"echo "This is an other msg" >&2exec 2>testerrorecho "This is an other normal msg"echo "This is an other msg2" >&2
重定向输入:
exec 0< statescount=1while read linedoecho "State #$count: $line"count=$((count+1))done
创建自己的重定向
其它6个3~8的文件描述符可用作输入或输出重定向。
exec 3>test28out# 当然也可以追加# exec 3>>test28outecho "1. This should display on the monitor"echo "and this should be stored in the file" >&3
一旦重定向了 STDOUT 或 STDERR,怎么再切回来呢?
# 在脚本中临时重定向输出,然后恢复默认输出设置# 3->monitorexec 3>&1echo "2. This should display on the monitor"# 1->fileexec 1>test28out2echo "This should be stored in the file"echo "3. This should display on the monitor" >&3# 1->monitorexec 1>&3echo "4. This should display on the monitor"
同样的方法用在重定向输入中:
#!/bin/bash# 保存 STDIN,之后再恢复exec 6<&0exec 0< statescount=1while read linedoecho "State #$count: $line"count=$((count+1))doneexec 0<&6read -p "Are you done now? " answercase $answer inY|y) echo "Goodbye";;N|n) echo "Sorry, this is the end";;esac
创建读写文件描述符,即打开单个文件描述符来作为输入和输出,注意,任何读或写都会从文件指针上次的位置开始:
#!/bin/bash# 创建读写文件描述符exec 3<>testfile# 读第一行read line <&3echo "Read: $line"# 此时文件指针在第二行的开头echo "This is a test line" >&3#testfile# This is the first line.# This is the second line.# This is the third line.# 运行结束查看 testfile# This is the first line.# This is a test line# ine.# This is the third line.
关闭文件描述符 exec 3>&-,关闭之后就不能使用了:
#!/bin/bashexec 3>test31fileecho "This is a test line of data" >&3# 关闭文件描述符# mac没有生效echo 3>&-echo "This won't work" >&3
如果在关闭之后,随后在脚本中打开了同一个输出文件,会用一个新文件来替换已有文件。
#!/bin/bashexec 3>test32fileecho "This is a test line of data" >&3exec 3>&-cat test32file# 会覆盖上面的文件exec 3>test32fileecho "This'll override file" >&3
列出打开的文件描述符 lsof
lsof 会列出整个 Linux 系统打开的所有文件描述符。
在很多 Linux 系统中,lsof 命令位于 /usr/sbin 目录,要想以普通用户账号来运行,必需通过全路径名来引用:/usr/sbin/lsof
-p 指定进程ID(PID)
-d 指定要显示的文件描述符编号
-a 用来对其它两个选项的结果执行布尔 AND 运算
$$ 环境变量,当前进程的PID
$ lsof -a -p $$ -d 0,1,2COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEzsh 45488 xiaocan 0u CHR 16,0 0t1828098 4717 /dev/ttys000zsh 45488 xiaocan 1u CHR 16,0 0t1828098 4717 /dev/ttys000zsh 45488 xiaocan 2u CHR 16,0 0t1828098 4717 /dev/ttys000
FD 文件描述符以及访问类型(r-read,w-write,u-读写)
TYPE 文件的类型(CHR 代表字符型,BLK 代表块型,DIR 代表目录,REG 代表常规文件)
与 STDIN、STDOUT 和 STDERR 关联的文件类型是字符型。
#!/bin/bashexec 3> test33file1exec 6> test33file2exec 7< states/usr/sbin/lsof -a -p $$ -d 0,1,2,3,6,7,8# 结果都是 REG 类型的,代表它们都是常规的文件$ sh test33-lsof.shCOMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEbash 48443 xiaocan 0u CHR 16,1 0t22389 5055 /dev/ttys001bash 48443 xiaocan 1u CHR 16,1 0t22389 5055 /dev/ttys001bash 48443 xiaocan 2u CHR 16,1 0t22389 5055 /dev/ttys001bash 48443 xiaocan 3w REG 1,4 0 8635172069 /Users/xiaocan/Documents/learn/shell-learn/test33file1bash 48443 xiaocan 6w REG 1,4 0 8635172070 /Users/xiaocan/Documents/learn/shell-learn/test33file2bash 48443 xiaocan 7r REG 1,4 50 8634917711 /Users/xiaocan/Documents/learn/shell-learn/states
阻止命令输出
将文件重定向到 /dev/null 文件。
可以用它来清空日志文件:cat /dev/null > logfile
创建临时文件
系统上任何用户都有权限读写 /tmp 目录中的文件。
# mac 上的运行结果$ mktemp/var/folders/gc/j2xc0rlx5bx4frx_54gb6sbr0000gn/T/tmp.olLTfDFA$ mktemp testing.XXXXXXtesting.t3DvSz# 只有属主用户有权限$ ls -l testing.t3DvSz-rw------- 1 xiaocan staff 0 Feb 13 20:15 testing.t3DvSz# -t 参数指定在临时文件夹中创建文件$ mktemp -t testing.XXXXXX/var/folders/gc/j2xc0rlx5bx4frx_54gb6sbr0000gn/T/testing.XXXXXX.B5qQrUge# -d 参数创建临时文件夹$ mktemp -d# 在脚本中使用# tempfile=$(mktemp test34.XXXXXX)
记录消息 tee
同时重定向到控制台和文件中。
tee 命令相当于管道的一个T型接头。 它将从 STDIN 过来的数据同时发往两处。 一处是 STDOUT,另一处是tee命令行所指定的文件名:
$ date | tee testfileecho "test message" | tee testfile# 使用 -a 追加echo "test message" | tee -a testfile
