Shell学习笔记(5)——理解Shell的重定向

理解输入与输出

脚本的两种显示方法就是:第一,在显示器屏幕上显示输出;第二,将输出重定向到文件。这篇笔记主要是理解Linux如何处理输入与输出的。

标准文件描述符

Linux系统将每个对象当作文件处理,这就包括了输入和输出过程,Linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件,每个进程最多可以有9个文件描述符,而bash shell保留了前3个文件描述符,即0、1和2。

文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误

这三个特殊文件描述符会处理脚本中的输入和输出。shell用它们将shell默认的输入和输出导向到相应的位置。

STDIN

STDIN文件描述符代表了shell的标准输入,对于终端来说,标准输入就是键盘,shell从STDIN文件描述符对应的键盘获得输入,在用户输入时处理每个字符。在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是键盘上的键输入的。许多bash命令能接受STDIN的输入,尤其是在命令行上指定文件的话。下面看一下cat这个案例:

1
2
3
4
5
biotest@ubuntu:~/userdata$ cat
test cat command
test cat command
cat command can input from keyboard
cat command can input from keyboard

当在命令行上只输入cat时,它会从STDIN接受僌,输入一行,cat命令就会显示出一行。但也可以通过STDIN重定向符号强制cat命令接受来自另一个非STDIN文件的输入,如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ cat testfile
This is the first line.
This is the second line.
This is the third line.
biotest@ubuntu:~/input$ cat < testfile
This is the first line.
This is the second line.
This is the third line.

这个结果显示,cat命令会用testfile文件中的行作为输入。

STDOUT

STDOUT文件描述符代表shell的标准输出。在终端界面上,标准输出应时终显示器。Shell的所有输出(包括shell文中运行的程序和脚本)会被定向到标准输出中,也就是显示器。默认情况下,大多数bash命令会将输出导向STDOUT文件描述符,也可以使用输出重定向来改变,如下所示:

1
2
3
4
5
biotest@ubuntu:~/input$ ls -l > test2
biotest@ubuntu:~/input$ cat test2
total 4
-rw-rw-r-- 1 biotest biotest 0 May 5 19:35 test2
-rw-rw-r-- 1 biotest biotest 73 May 5 19:32 testfile

使用>>符号可以向一个文件中追加数据,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
biotest@ubuntu:~/input$ cat test2
total 4
-rw-rw-r-- 1 biotest biotest 0 May 5 19:35 test2
-rw-rw-r-- 1 biotest biotest 73 May 5 19:32 testfile
biotest@ubuntu:~/input$ who
biotest tty7 2018-04-27 22:30 (:0)
biotest@ubuntu:~/input$ who >> test2
biotest@ubuntu:~/input$ cat test2
total 4
-rw-rw-r-- 1 biotest biotest 0 May 5 19:35 test2
-rw-rw-r-- 1 biotest biotest 73 May 5 19:32 testfile
biotest tty7 2018-04-27 22:30 (:0)

当某个主文件不存在时,无产进行重定向,但会生成要重定向的文件,如下所示:

1
2
3
biotest@ubuntu:~/input$ ls -al badfile > test3
ls: cannot access 'badfile': No such file or directory
biotest@ubuntu:~/input$ cat test3

由于不存在badfile文件,因此bash无法获取里面的信息,也无法将其信息重定向到test3,但是test3这个文件会被创建,只是里面是空的。

STDERR

STDERR文件描述符被设成2,可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。该值必须紧紧地放在重定向符号前,否则不会工作。如下所示;

1
2
3
biotest@ubuntu:~/input$ ls -al badfile 2> test3
biotest@ubuntu:~/input$ cat test3
ls: cannot access 'badfile': No such file or directory

此时,错误消息不会出现在屏幕上,该命令生怕任何错误消息都会保存在输出文件中,用这种方法,shell只重定向错误消息,而非普通数据。下面的一个案例是将STDOUTSTDERR消息混杂在同一个输出中的例子,如下所示;

1
2
3
4
5
biotest@ubuntu:~/input$ ls -al test badtest test2 2> test5
-rw-rw-r-- 1 biotest biotest 157 May 5 19:38 test2
biotest@ubuntu:~/input$ cat test5
ls: cannot access 'test': No such file or directory
ls: cannot access 'badtest': No such file or directory

ls命令的正常STDOUT输出仍然公改善到默认的STDOUT文件描述符,也就是显示器,由于该命令将文件描述符2的输出(STDERR)重定向到了一个输出文件,shell将生成的所有错误消息直接改善到指定的重定向文件中。

重定向错误和数据

如果想重定向错误和正常输出,需要用2个重定向符号,需要在符号前放上待重定向数据所对应的文件描述符,然后指向用于保存数据的输出文件,如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~/input$ ls -al test test2 test3 badtest 2> test6 1> test7
biotest@ubuntu:~/input$ cat test6
ls: cannot access 'test': No such file or directory
ls: cannot access 'badtest': No such file or directory
biotest@ubuntu:~/input$ cat test7
-rw-rw-r-- 1 biotest biotest 157 May 5 19:38 test2
-rw-rw-r-- 1 biotest biotest 55 May 5 19:48 test3

在这个案例中,shell利用1>符号将ls命令的正常输出重定向到了test7文件,而这些输出本该是进入STDOUT的。所有本该输出到STDERR的错误消息通过2>符号被重定向到了test6文件。 这种方法哦可以将脚本的正常输出和脚本的错误消息分离开来。

除此之外,还可以将STDERRSTDOUT的输出重定向到同一个输出文件,可以使用特殊的重定向符号$>,如下所示::

1
2
3
4
5
6
biotest@ubuntu:~/input$ ls -al test test2 test3 badtest &> test7
biotest@ubuntu:~/input$ cat test7
ls: cannot access 'test': No such file or directory
ls: cannot access 'badtest': No such file or directory
-rw-rw-r-- 1 biotest biotest 157 May 5 19:38 test2
-rw-rw-r-- 1 biotest biotest 55 May 5 19:48 test3

当使用&>符号哩主,命令生成的所有输出都会改善到同一位置,包括数据和错误。bash shell会自动赋予错误消息更高的优先级,在输出文件的最开头部分显示错误消息。

在脚本中重定向输出

有2种方法在脚本中重定向输出,第一,临时重定向行输出;第二,永久重定向脚本中的所有命令。

临时重定向

如果脚本中意外生成错误消息,可以将单独的一行输出重定向到STDERR,在重定向到文件描述符时,需要在文件描述符前面添加一具&,如下所示:

1
2
3
4
5
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"

运行过程如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ chmod u+x test8.sh
biotest@ubuntu:~/input$ ./test8.sh
This is an error
This is normal output
biotest@ubuntu:~/input$ ./test8.sh 2>test9
This is normal output
biotest@ubuntu:~/input$ cat test9
This is an error

从结果可以看出,STDOUT显示的文件会出现在屏幕上,而发送给STDERR的echo语句的文本则被重定向到了输出文件。这个方便适合在脚本中生成错误的消息。

永久重定向

如果脚本中有大量数据需要重定向,则可以使用exec命令告诉shell脚本执行期间重定向某个特定文件的描述符,如下所示:

1
2
3
4
5
6
7
#!/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"

运行结果如下所示:

1
2
3
4
5
biotest@ubuntu:~/input$ chmod u+x test10.sh
biotest@ubuntu:~/input$ ./test10.sh
biotest@ubuntu:~/input$ cat testout
This is a test of redirecting all output
from a script to another file.

exec命令会启动一个新的shell,并将STDOUT文件描述符重定向到文件。脚本中发给STDOUT的所有输出会被重定向到文件。可以在脚本执行过程中重定向STDOUT,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# redirecting output to different locatons
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

运行结果如下所示:

1
2
3
4
5
6
biotest@ubuntu:~/input$ chmod u+x test11.sh
biotest@ubuntu:~/input$ ./test11.sh
This is the start of the script
now redirecting all output to another location
biotest@ubuntu:~/input$ cat testerror
but this should go to the testerror file

在这个案例中,exec命令将发给STDERR的输出重定向到了文件testerror,接下来,脚本用echo语句向STDOUT显示了几行文本,随后再次使用exec命令来将STDOUT重定向到了testout文件,虽然STDOUT被重定向了,但仍然可以将echo语句的输出发给STDERR,在此案例中,重定向到了testerror文件。

在脚本中重定向输入

在脚本中可以使用与重向STDOUT和STDERR同样的方法来将STDIN从键盘重定向到其他位置。exec命令可以将STDIN重定向到linux系统上的文件中,用法是exec 0< testfile,这个命令会告诉shell应该从文件testfile中获得输入,而不是STDIN,这个重定向只要在脚本需要输入时就会作用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
# redirecting fiel input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count+1 ]
done

结果运行如下所示:

1
2
3
4
5
biotest@ubuntu:~/input$ chmod u+x test12.sh
biotest@ubuntu:~/input$ ./test12.sh
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.

STDIN重定向到文件后,当read命令试图从STDIN读入数据时,它会到文件中去读取数据,而不是键盘。

创建自己的重定向

在脚本中重定向输入和输出时,并不局限于这3个默认的文件描述符(即0,1和2)。shell中最多可以有9个打开的文件描述符。其他6个从 3 ~ 8 的文件描述符均可用作输入或输出重定向。用户可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。

创建输出文件描述符

exec 命令可以给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到你重新分配。看一个案例:

1
2
3
4
5
6
7
8
#!/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 shold be back on the monitor"

运行结果如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ chmod u+x test13.sh
biotest@ubuntu:~/input$ ./test13.sh
This should display on the monitor
Then this shold be back on the monitor
biotest@ubuntu:~/input$ cat test13out
and this should be stored in the file

在这个案例中,exec命令将文件描述符3重定向到另一个文件。当脚本执行echo语句时,输出内容会像预想中那样显示在STDOUT 上。但你重定向到文件描述符3 的那行echo语句的输出却进入了另一个文件。这样你就可以在显示器上保持正常的输出,而将特定信息重定向到文件中(比如日志文件)。 也可以不用创建文件,使用exec命令来将输出追加到现有文件早,例如exec 3>> test13out

重定向文件描述符

看一个案例,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1 # 将文件描述符3重定向到文件描述符1的位置,也就是STDOUT,这意味着任何发送给文件描述符3的输出都将会出现在显示器上
exec 1> test14out # 将STDOUT重定向到文件,shell现在会将改善给STDOUT的输出直接重定向到输出文件上,但文件描述符3仍然指向STDOUT原来的位置,也就是显示器,如果此时将输出数据改善给文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向了
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
# 在向STDOUT发送一些输出后,脚本将STDOUT重定向到文件描述符3的当前位置(显示器),这意味着现在STDOUT又指向了原来的位置,即显示器
echo "Now things should be back to normal"

运行结果如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ chmod u+x test14.sh
biotest@ubuntu:~/input$ ./test14.sh
Now things should be back to normal
biotest@ubuntu:~/input$ cat test14out
This should store in the output file
along with this line.

创建输入文件描述符

可以用和重定向输出文件描述符同样的办法重定向输入文件描述符。在重定向到文件之前,先将 STDIN 文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将 STDIN 恢复到它原来的位置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# redirecting input file descriptors
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$cont: $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

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
biotest@ubuntu:~/input$ chmod u+x test15.sh
biotest@ubuntu:~/input$ ./test15.sh
Line #: This is the first line.
Line #: This is the second line.
Line #: This is the third line.
Are you done now Y
Goodbye
biotest@ubuntu:~/input$ ./test15.sh
Line #: This is the first line.
Line #: This is the second line.
Line #: This is the third line.
Are you done now n
Sorry, this is the end.

在这个案例中,文件描述符6用来保存STDIN的位置,然后脚本将STDIN重定向到一个文件,read命令的所有输入都来自重定向后的STDIN(也就是输入文件),在读取了所有行之后,脚本将STDIN重定向到文件描述符6,从而将STDIN恢复到原来的位置,该脚本用了另外一个read命令来测试STDIN是否恢复正常,这将它等待键盘的输入。

创建读写文件描述符

在bash shell中,可以打开单个文件描述符来作为输入和输出,可以用一个文件描述符对同一个文件进行读写,不过使用这种方法需要谨慎,由于用户是对一个文件进行数据读写,shell会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始,如果不够小心,会生成严重的后果,如下所示:

1
2
3
4
5
6
7
#!/bin/bash
# testing input/output file descriptor
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
biotest@ubuntu:~/input$ chmod u+x test16.sh
biotest@ubuntu:~/input$ cat testfile
This is the first line.
This is the second line.
This is the third line.
biotest@ubuntu:~/input$ ./test16.sh
Read: This is the first line.
biotest@ubuntu:~/input$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.

在这个案例中,exec命令将文件描述符分配文件testfile以进行文件读写,接下来,通过使用分配好的文件描述符,使read命令读取文件中的第一行,然后将这一行显示在STDOUT上,最后使用echo语句将一行数据写入由同一个文件描述符打开的文件中。在运行结果中,开始运行正常,输出内容表明脚本读取了testfile文件中的第一行,如果脚本运行完毕,查看testfile文件时发现,写入文件中的数据覆盖了已有的数据。原因是,当脚本向中文件中写入数据时,它会从文件指针所处的位置开始,read命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符,在echo语句将数据输出到文件时,它会将数据放在文件指针的当前位置,覆盖了该位置的已有数据。

关闭文件描述符

如果用户创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们,有些情况下需要在脚本关闭之前手动关闭文件描述符,要关闭文件描述符,将它重定向到特殊符号&-,在脚本中是这样的exec 3>&-,这条语句会关闭文件描述符3,不再脚本中使用它,如下所示:

1
2
3
4
5
6
7
8
9
#!/bin/bash
# testing closing file descriptors
exec 3> tets17file
echo "This is a test line of data " >&3
exec 3>&-
echo "This won't work" >&3

运行结果如下所示:

1
2
biotest@ubuntu:~/input$ bash badtest2.sh
badtest2.sh: line 9: 3: Bad file descriptor

一旦关闭了文件描述符,就无法在脚本中写入任何数据,否则shell会生成错误消息。在关闭文件描述符时还需要注意,如果在随后的脚本中打开了同一个输出文件,shell会用一个新文件来替换已有的文件,这就意味着,如果输出数据,它就会覆盖已有鹰爪,如下所示:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
# testing closing file descriptors
exec 3> test17file
echo "This is a test line of test" >&3
exec 3>&-
cat test17file
exec 3>test17file
echo "This will be bad" >&3

结果运行如下所示:

1
2
3
4
biotest@ubuntu:~/input$ bash test17.sh
This is a test line of test
biotest@ubuntu:~/input$ cat test17file
This will be bad

在向test17file文件发送一个数据字符串并关闭该文件描述符之后,脚本用了 cat 命令来显示文件的内容。下一步,脚本重新打开了该输出文件并向它发送了另一个数据字符串。当显示该输出文件的内容时,你所能看到的只有第二个数据字符串。shell覆盖了原来的输出文件。

列出打开的文件描述符

bash shell中有一个lsof命令,这个命令会列出整个Linux系统打开的所有文件描述符,lsof命令位于/usr/sbin目录中,这个命令会产生大量的输出,它会显示Linux系统中打开的每个文件的有产信息,这包括后台运行的所有进程,以及登录到系统的任何用户。lsof有各种命令选项和参数可以使用,最常用的是-p-d,前者用于指定进程ID(即PID),后者用于指定显示的文件描述符编号,如果要知道进程的当前PID,可以使用特殊的环境变量$$$$(shell会将它设为当前PID),-a选项用来对其他两个选项的结果执行布尔AND运算,如下所示:

1
2
3
4
5
biotest@ubuntu:~/input$ lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 10009 biotest 0u CHR 136,4 0t0 7 /dev/pts/4
bash 10009 biotest 1u CHR 136,4 0t0 7 /dev/pts/4
bash 10009 biotest 2u CHR 136,4 0t0 7 /dev/pts/4

结果显示了当前进程的国俯文件描述符(0、1和2),lsof的默认输出有7列信息,如下所示:

描述
COMMAND 正在运行的命令名的前9个字符
PID 进程的PID
USER 进程属主的登录名
FD 文件描述符号以及访问类型(r代表读,w代表写,u代表读写)
TYPE 文件的类型(CHR代表字符型,BLK代表块型,DIR代表目录,REG代表常规文件)
DEVICE 设备的设备号(主设备号和从设备号)
SIZE 如果有的话,表示文件的大小
NODE 本地文件的节点号
NAME 文件名

与 STDIN 、 STDOUT 和 STDERR 关联的文件类型是字符型。因为 STDIN 、 STDOUT 和 STDERR 文
件描述符都指向终端,所以输出文件的名称就是终端的设备名。现在看一个案例:

1
2
3
4
5
6
7
8
#!/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

运行结果如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ bash test18.sh
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 28264 biotest 0u CHR 136,4 0t0 7 /dev/pts/4
bash 28264 biotest 1u CHR 136,4 0t0 7 /dev/pts/4
bash 28264 biotest 2u CHR 136,4 0t0 7 /dev/pts/4
bash 28264 biotest 3w REG 8,1 0 928519 /home/biotest/input/test18file1
bash 28264 biotest 6w REG 8,1 0 928521 /home/biotest/input/test18file2
bash 28264 biotest 7r REG 8,1 73 928491 /home/biotest/input/testfile

这个脚本创建了3个替代性文件描述符,两个是输出(3和6),一个是输入(7),在脚本运行了lsof命令时,可以在输出中看到新的文件描述符,如果去掉输出中的第一部分,就会看到文件名的结果,文件名显示了文件描述符所使用的文件的完整路径名,它将每个文件都显示成了REG类型,这就说明它们是文件系统中的常规文件。

阻止命令输出

有些情况下,用户不想显示脚本的输出。这在将脚本作为后台进程运行时很常见,如果在运行在后台的脚本出现错误消息,shell会通过电子邮件将它们发给进程的属主。这会很麻烦,尤其是当运行会生成很多烦琐的小错误的脚本时。要解决这个问题,可以将STDERR重定向到一个叫作null文件的特殊文件。null文件跟它的名字很像,文件里什么都没有。shell输出到null文件的任何数据都不会保存,全部都被丢掉了。在Linux系统上null文件的标准位置是/dev/null。你重定向到该位置的任何数据都会被丢掉,不会显示。

1
2
3
4
5
6
biotest@ubuntu:~/input$ ls -al > /dev/null
biotest@ubuntu:~/input$ cat /dev/null
biotest@ubuntu:~/input$ ls -al badfile2 test16.sh 2> /dev/null
-rwxrw-r-- 1 biotest biotest 135 May 5 23:28 test16.sh
biotest@ubuntu:~/input$ cat /dev/null
biotest@ubuntu:~/input$

也可以在输入重定向中将dev/null作为输入文件,由于dev/null文件不含有任何内容,用户通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建,如下所示:

1
2
3
4
5
6
7
biotest@ubuntu:~/input$ cat testfile
This is the first line.
This is a test line
ine.
This is the third line.
biotest@ubuntu:~/input$ cat /dev/null > testfile
biotest@ubuntu:~/input$ cat testfile

文件testfile仍然存在于系统上,但现在是空文件,这是清除日志文件的一个常用的方法。

创建临时文件

Linux系统有特殊的目录,专供临时文件使用。Linux使用/tmp目录来存放不需要永久保留的文件。系统上的任何用户账户都有权限在读写/tmp目录中的文件。这个特性为用户提供了一种创建临时文件的简单方法,而且还不用操心清理工作。有个特殊命令可以用来创建临时文件。mktemp命令可以在/tmp目录中创建一个唯一的临时文件。shell会创建这个文件,但不用默认的umask值(参见第7章)。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然,root用户除外)。

创建本地临时文件

默认情况下,mktemp会在本地目录中创建一个文件,要用mktemp在本地目录中创建一个临时文件,只要指定一个文件名模板就行了,模板可以包含任意文本文件名,在文件名末尾加上6个x就行了,如下所示:

1
2
3
4
biotest@ubuntu:~/input$ mktemp testing.XXXXXX
testing.ATlZBX
biotest@ubuntu:~/input$ ls -al testing*
-rw------- 1 biotest biotest 0 May 6 00:32 testing.ATlZBX

mktemp命令会用6个字符码替换这6个x,从而保证文件名在目录中是唯一的。用户可以创建多个临床文件,这种用法可以使每个文件名都是唯一的。如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ mktemp testing.XXXXXX
testing.EWLscI
biotest@ubuntu:~/input$ mktemp testing.XXXXXX
testing.Y6pjo8
biotest@ubuntu:~/input$ ls -al testing*
-rw------- 1 biotest biotest 0 May 6 00:32 testing.ATlZBX
-rw------- 1 biotest biotest 0 May 6 00:37 testing.EWLscI
-rw------- 1 biotest biotest 0 May 6 00:37 testing.Y6pjo8

在脚本中使用mktemp命令可以将文件名保存到变量中,这样就可以在后面的脚本中引用了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/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

运行结果如下所示:

1
2
3
4
5
6
7
8
biotest@ubuntu:~/input$ bash test19.sh
This script writes to temp file test19.RyVPfD
Done creating temp file. The contents are:
This is the first line
This is the second line
This is the last line
biotest@ubuntu:~/input$ ls -al test19*
-rw-rw-r-- 1 biotest biotest 354 May 6 00:40 test19.sh

在这个脚本中,使用mktemp命令创建临时文件,并将文件名赋给$tempfile变量,接着将这个临时文件作为文件描述3的输出重定向文件,在将临时文件名显示在STDOUT之后,向中临时文件中写入了几行文本,然后关闭了文件描述符,最后显示出临时文件的内容,并用rm命令将其删除。

/tmp目录创建临时文件

-t选项会强制mktemp命令在系统的临时目录来创建文件,在使用这个特性时,mktemp命令会返回用来创建临时文件的全路径,而不是仅有文件名,如下所示:

1
2
3
4
biotest@ubuntu:~/input$ mktemp -t test.XXXXXX
/tmp/test.oL5jbT
biotest@ubuntu:~/input$ ls -al /tmp/test*
-rw------- 1 biotest biotest 0 May 6 00:45 /tmp/test.oL5jbT

由于mktemp返回了全路径,因此用户可以在Linux系统的任意目录下引用该临时文件,不管目录在哪里,如下所示:

1
2
3
4
5
6
7
8
9
10
11
#!/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

结果运行如下所示:

1
2
3
4
biotest@ubuntu:~/input$ bash test20.sh
The temp file is located at: /tmp/tmp.4hLOyR
This is a test file.
This is the second line of the test.

mktemp创建了临时文件时,它会返回全路径给变量,这样就可以在任何命令中使用该值来引用临时变量了。

创建临时目录

-d选项告诉mktemp命令来创建一个临时目录,而不是临时文件,这样就能用此目录进行任何需要的操作,体积如创建其他的临时文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/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

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
biotest@ubuntu:~/input$ bash test21.sh
Sending data to directory dir.4ZeP0T
biotest@ubuntu:~/input$ ls -al
drwx------ 2 biotest biotest 4096 May 6 01:02 dir.4ZeP0T
... ...
-rw-rw-r-- 1 biotest biotest 29 May 5 23:41 tets17file
biotest@ubuntu:~/input$ cd dir.4ZeP0T
biotest@ubuntu:~/input/dir.4ZeP0T$ ls
temp.7KoQoD temp.jj2vb1
biotest@ubuntu:~/input/dir.4ZeP0T$ cat temp.7KoQoD
This is a test line of data for temp.7KoQoD
biotest@ubuntu:~/input/dir.4ZeP0T$ cat temp.jj2vb1
This is a test line of data for temp.jj2vb1

这段脚本在当前目录创建了一个目录,然后它用cd命令进入该目录,并创建了两个临时文件。之后这两个临时文件被分配给文件描述符,用来存储脚本的输出。

记录消息

有时候需要将输出同时发送到显示器和日志文件,此时需要tee命令。tee在英文就是T的意思,在Linux中,这个命令相当于管道一个T接头,它将从STDIN过来的数据同时发往两处,一处是STDOUT,另一处是tee命令所指定的文件名tee filename,由于tee会重定向来自STDIN的数据,可以用它配合管道命令来重定向命令输出,如下所示:

1
2
3
4
biotest@ubuntu:~/input/dir.4ZeP0T$ date | tee testfile2
Sun May 6 01:12:57 PDT 2018
biotest@ubuntu:~/input/dir.4ZeP0T$ cat testfile2
Sun May 6 01:12:57 PDT 2018

输出出现在了STDOUT中,同时也写入了指定的文件中,默认情况下,tee命令会在每次使用时覆盖输出文件的内容,如下挂满:

1
2
3
4
biotest@ubuntu:~/input/dir.4ZeP0T$ who | tee testfile2
biotest tty7 2018-04-27 22:30 (:0)
biotest@ubuntu:~/input/dir.4ZeP0T$ cat testfile2
biotest tty7 2018-04-27 22:30 (:0)

如果需要追加数据,则要使用-a参数,如下所示:

1
2
3
4
5
biotest@ubuntu:~/input/dir.4ZeP0T$ date | tee -a testfile2
Sun May 6 01:14:55 PDT 2018
biotest@ubuntu:~/input/dir.4ZeP0T$ cat testfile2
biotest tty7 2018-04-27 22:30 (:0)
Sun May 6 01:14:55 PDT 2018

看一个案例:

1
2
3
4
5
6
7
8
#!/bin/bash
# using the tee command for logging
tempfile=test22file
echo "This is the start of the test" |tee $tempfile
echo "This is the second line of the test" | tee -a $tempfile
echo "This is the end of the test" | tee -a $tempfile

运行结果如下所示:

1
2
3
4
5
6
7
8
9
biotest@ubuntu:~/input/dir.4ZeP0T$ bash test22.sh
This is the start of the test
This is the second line of the test
This is the end of the test
biotest@ubuntu:~/input/dir.4ZeP0T$ cat test22file
This is the start of the test
This is the second line of the test
This is the end of the test

从结果可以看出,用户显示输出的同时,还能保存一份输出内容。