15.1 理解输入和输出
15.1.1 标准文件描述符
- 每个进程一次最多可以有9个文件描述符
- 保留前三个文件描述符:
1. STDIN
- 终端界面的 shell 的 stdin 是键盘
输入重定向:
$ cat < testfile
2. STDOUT
- 终端界面的 shell 的 stdout 是屏幕
输出重定向:
$ ls -l > test2
追加:
$ who >> test2
3. STDERR
- 默认情况下 STDERR 的位置与 STDOUT 相同
15.1.2 重定向错误
1. 只重定向错误
2>
$ ls -al badfile 2> test4
2. 重定向错误和数据
1>
$ ls -al test test2 test3 badtest 2> test6 1> test7
&>
: 将 stderr, stdout 重定向到同一文件
$ ls -al test test2 test3 badtest &> test7
15.2 在脚本中重定向输出
15.2.1 临时重定向
>&2
- 在数字前加& (应该是与文件名做区分)
- 为什么没有文件名?
echo "This is an error message" >&2
使用:
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
正常运行, 没发现什么不同:
$ ./my_script.sh
This is an error
This is normal output
重定向 stderr:
$ ./my_script.sh 2> test9
This is normal output
15.2.2 永久重定向
用 exec 命令告诉 shell 在脚本执行期间重定向某个特定文件描述符。
- 重定向 stdout
#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
- 重定向 stderr
#!/bin/bash
# redirecting output to different locations
exec 2>testerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>testout
echo "This output should go to the testout file"
echo "but this should go to the testerror file" >&2
$
$ ./test11
This is the start of the script
now redirecting all output to another location
$ cat testout
This output should go to the testout file
$ cat testerror
but this should go to the testerror file
15.3 在脚本中重定向输入
exec 0< testfile
使用:
#!/bin/bash
# redirecting file input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
15.4 创建自己的重定向
15.4.1 创建输出文件描述符
#!/bin/bash
# using an alternative file descriptor
exec 3>test13out
echo "This should display on the monitor"
echo "and this should be stored in the file" >&3
echo "Then this should be back on the monitor"
- 追加
exec 3>>test13out
15.4.2 重定向文件描述符
恢复原来的文件描述符:
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1
exec 1>test14out
echo "This should store in the output file"
echo "along with this line."
exec 1>&3 # 恢复
echo "Now things should be back to normal"
15.4.3 创建输入文件描述符
#!/bin/bash
# redirecting input file descriptors
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$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
15.4.4 创建读写文件描述符
- 注意数据可能会被覆盖
#!/bin/bash
# testing input/output file descriptor
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3
15.4.5 关闭文件描述符
>&-
exec 3>&-
- 向已关闭的文件描述符输出
- 报错
#!/bin/bash
# testing closing file descriptors 5
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3
$ ./badtest
./badtest: 3: Bad file descriptor
- 重复重定向同一文件, 之前的数据会被覆盖
#!/bin/bash
# testing closing file descriptors
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
cat test17file
exec 3> test17file
echo "This'll be bad" >&3
$ ./test17
This is a test line of data
$ cat test17file
This'll be bad
15.5 列出打开的文件描述符
lsof 命令
-p
: 指定 pid-d
: 文件描述符编号$$
: 当前进程的 pid 的环境变量-a
: 执行布尔运算
$ lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
zsh 6372 jdxj 0u CHR 136,2 0t0 5 /dev/pts/2
zsh 6372 jdxj 1u CHR 136,2 0t0 5 /dev/pts/2
zsh 6372 jdxj 2u CHR 136,2 0t0 5 /dev/pts/2
各个列含义:
- stdin, stdout, stderr 是字符类型
- 文件名通常是终端名
查看打开了多个文件描述符的脚本:
#!/bin/bash
# testing lsof with file descriptors
exec 3> test18file1
exec 6> test18file2
exec 7< testfile
lsof -a -p $$ -d0,1,2,3,6,7
执行:
$ ./my_script.sh
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
my_script 16724 jdxj 0u CHR 136,2 0t0 5 /dev/pts/2
my_script 16724 jdxj 1u CHR 136,2 0t0 5 /dev/pts/2
my_script 16724 jdxj 2u CHR 136,2 0t0 5 /dev/pts/2
my_script 16724 jdxj 3w REG 259,2 0 8262019 /home/jdxj/tmp/test18file1
my_script 16724 jdxj 6w REG 259,2 0 8262021 /home/jdxj/tmp/test18file2
my_script 16724 jdxj 7r REG 259,2 4 8262022 /home/jdxj/tmp/testfile
15.6 阻止命令输出
null 文件:
/dev/null
$ ls -al > /dev/null
$ ls -al badfile test16 2> /dev/null
快速清空文件内容:
$ cat /dev/null > testfile
15.7 创建临时文件
/tmp
- 一般在启动系统时清除
mktemp 命令
15.7.1 创建本地临时文件
- 在文件名后面加
XXXXXX
(六个大写X
)
$ mktemp testing.XXXXXX
在脚本中使用:
#!/bin/bash
# creating and using a temp file
tempfile=$(mktemp test19.XXXXXX)
exec 3>$tempfile
echo "This script writes to temp file $tempfile"
echo "This is the first line" >&3
echo "This is the second line." >&3
echo "This is the last line." >&3
exec 3>&-
echo "Done creating temp file. The contents are:"
cat $tempfile
rm -f $tempfile 2> /dev/null
15.7.2 在 /tmp 目录创建临时文件
-t
: 在 /tmp 目录创建临时文件- mktemp 在使用
-t
选项时, 会返回创建的路径
$ mktemp -t test.XXXXXX
/tmp/test.QEoHXH
在脚本中使用:
#!/bin/bash
# creating a temp file in /tmp
tempfile=$(mktemp -t tmp.XXXXXX)
echo "This is a test file." > $tempfile
echo "This is the second line of the test." >> $tempfile
echo "The temp file is located at: $tempfile"
cat $tempfile
rm -f $tempfile
15.7.3 创建临时目录
-d
#!/bin/bash
# using a temporary directory
tempdir=$(mktemp -d dir.XXXXXX)
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX)
tempfile2=$(mktemp temp.XXXXXX)
exec 7> $tempfile1
exec 8> $tempfile2
echo "Sending data to directory $tempdir"
echo "This is a test line of data for $tempfile1" >&7
echo "This is a test line of data for $tempfile2" >&8
15.8 记录消息
tee 命令相当于管道的一个T型接头。它将从 STDIN 过来的数据同时发往两处。一处是 STDOUT, 另一处是 tee 命令行所指定的文件名.
tee filename
使用:
- tee 会覆盖原文件的内容
-a
: 追加
$ date | tee testfile