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" >&2
echo "This is a normal msg"
如果脚本中有大量数据需要重定向,重定向每个 echo 语句就很烦琐。可以使用 exec
告诉 shell 在脚本执行期间重定向某个特定文件描述符。exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到文件。
exec 1>testout
echo "This is a test of redirecting all outoupt"
echo " from a script to another file"
echo "This is an other msg" >&2
exec 2>testerror
echo "This is an other normal msg"
echo "This is an other msg2" >&2
重定向输入:
exec 0< states
count=1
while read line
do
echo "State #$count: $line"
count=$((count+1))
done
创建自己的重定向
其它6个3~8的文件描述符可用作输入或输出重定向。
exec 3>test28out
# 当然也可以追加
# exec 3>>test28out
echo "1. This should display on the monitor"
echo "and this should be stored in the file" >&3
一旦重定向了 STDOUT 或 STDERR,怎么再切回来呢?
# 在脚本中临时重定向输出,然后恢复默认输出设置
# 3->monitor
exec 3>&1
echo "2. This should display on the monitor"
# 1->file
exec 1>test28out2
echo "This should be stored in the file"
echo "3. This should display on the monitor" >&3
# 1->monitor
exec 1>&3
echo "4. This should display on the monitor"
同样的方法用在重定向输入中:
#!/bin/bash
# 保存 STDIN,之后再恢复
exec 6<&0
exec 0< states
count=1
while read line
do
echo "State #$count: $line"
count=$((count+1))
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end";;
esac
创建读写文件描述符,即打开单个文件描述符来作为输入和输出,注意,任何读或写都会从文件指针上次的位置开始:
#!/bin/bash
# 创建读写文件描述符
exec 3<>testfile
# 读第一行
read line <&3
echo "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/bash
exec 3>test31file
echo "This is a test line of data" >&3
# 关闭文件描述符
# mac没有生效
echo 3>&-
echo "This won't work" >&3
如果在关闭之后,随后在脚本中打开了同一个输出文件,会用一个新文件来替换已有文件。
#!/bin/bash
exec 3>test32file
echo "This is a test line of data" >&3
exec 3>&-
cat test32file
# 会覆盖上面的文件
exec 3>test32file
echo "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,2
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 45488 xiaocan 0u CHR 16,0 0t1828098 4717 /dev/ttys000
zsh 45488 xiaocan 1u CHR 16,0 0t1828098 4717 /dev/ttys000
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 关联的文件类型是字符型。
#!/bin/bash
exec 3> test33file1
exec 6> test33file2
exec 7< states
/usr/sbin/lsof -a -p $$ -d 0,1,2,3,6,7,8
# 结果都是 REG 类型的,代表它们都是常规的文件
$ sh test33-lsof.sh
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 48443 xiaocan 0u CHR 16,1 0t22389 5055 /dev/ttys001
bash 48443 xiaocan 1u CHR 16,1 0t22389 5055 /dev/ttys001
bash 48443 xiaocan 2u CHR 16,1 0t22389 5055 /dev/ttys001
bash 48443 xiaocan 3w REG 1,4 0 8635172069 /Users/xiaocan/Documents/learn/shell-learn/test33file1
bash 48443 xiaocan 6w REG 1,4 0 8635172070 /Users/xiaocan/Documents/learn/shell-learn/test33file2
bash 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.XXXXXX
testing.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 testfile
echo "test message" | tee testfile
# 使用 -a 追加
echo "test message" | tee -a testfile