15.1 理解输入和输出

15.1.1 标准文件描述符

  • 每个进程一次最多可以有9个文件描述符
  • 保留前三个文件描述符:

image.png

1. STDIN

  • 终端界面的 shell 的 stdin 是键盘

输入重定向:

  1. $ cat < testfile

2. STDOUT

  • 终端界面的 shell 的 stdout 是屏幕

输出重定向:

  1. $ ls -l > test2

追加:

  1. $ who >> test2

3. STDERR

  • 默认情况下 STDERR 的位置与 STDOUT 相同

15.1.2 重定向错误

1. 只重定向错误

  • 2>
  1. $ ls -al badfile 2> test4

2. 重定向错误和数据

  • 1>
  1. $ ls -al test test2 test3 badtest 2> test6 1> test7
  • &> : 将 stderr, stdout 重定向到同一文件
  1. $ ls -al test test2 test3 badtest &> test7

15.2 在脚本中重定向输出

15.2.1 临时重定向

  • >&2
    • 在数字前加& (应该是与文件名做区分)
    • 为什么没有文件名?
  1. 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

各个列含义:

image.png
image.png

  • 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

15.9 实例

15.10 小结