Linux shell - processing Data
有两种显示脚本输出的方法。
- 在显示器屏幕上显示输出。
- 将输出重定向到文件中。
这两种方法要么将数据输出全部显示出来,要么什么都不显示。但有时将一部分数据显示在屏幕上,另一部分数据保存到文件中更合适。对此,了解 Linux 如何处理输入和输出有助于将脚本输出送往所需的位置。
标准文件描述符
Linux 系统会将每个对象当作文件来处理,这包括输入和输出。Linux 用文件描述符来标识每个文件对象。文件描述符是一个非负整数,唯一会标识的是会话中打开的文件。每个进程一次最多可以打开 9 个文件描述符。出于特殊目的,bash shell 保留了前 3 个文件描述符(0、1 和 2)。
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
这 3 个特殊的文件描述符会处理脚本的输入和输出。shell 会用它们将其默认的输入和输出送往适合的位置。
标准输入
STDIN 文件描述符代表 shell 的标准输入。对终端界面来说,标准输入就是键盘。shell 会从 STDIN 文件描述符对应的键盘获得输入并进行处理。
在使用输入重定向符(<)时,Linux 会用重定向指定的文件替换标准输入文件描述符。于是,命令就会从文件中读取数据,就好像这些数据是从键盘键入的。
许多 bash 命令能从 STDIN 接收输入,尤其是在命令行中没有指定文件的情况下。下面例子使用 cat 命令来处理来自 STDIN 的输入:
cat
# this is a test
# this is a test
# this is a second test.
# this is a second test.
当在命令行中只输入 cat 命令时,它会从 STDIN 接收输入。输入一行,cat 命令就显示一行。
但也可以通过输入重定向符强制 cat 命令接收来自 STDIN 之外的文件输入:
cat < testfile
# This is the first line.
# This is the second line.
# This is the third line.
cat 命令现在从 testfile 文件中获取输入。你可以使用这种技术将数据导入任何能从 STDIN 接收数据的 shell 命令中。
STDOUT
STDOUT 文件描述符代表 shell 的标准输出。在终端界面上,标准输出就是终端显示器。shell 的所有输出(包括 shell 中运行的程序和脚本)会被送往标准输出,也就是显示器。
在默认情况下,大多数 bash 命令会将输出送往 STDOUT 文件描述符,可以用输出重定向来更改此设置:
ls -l > test2
cat test2
# total 20
# -rw-rw-r-- 1 roger roger 53 2024-06-04 11:30 test
# -rw-rw-r-- 1 roger roger 0 2024-06-04 11:32 test2
# -rw-rw-r-- 1 roger roger 73 2024-06-04 11:23 testfile
通过输出重定向符(>),原本应该出现在屏幕上的所有输出被 shell 重定向到了指定的文件。
也可以使用>>将数据追加到某个文件:
who >> test2
cat test2
# total 20
# -rw-rw-r-- 1 roger roger 53 2024-06-04 11:30 test
# -rw-rw-r-- 1 roger roger 0 2024-06-04 11:32 test2
# -rw-rw-r-- 1 roger roger 73 2024-06-04 11:23 testfile
# roger pts/0 2024-06-04 15:34 (192.168.1.2)
who 命令生成的输出被追加到了 test2 文件中已有数据之后。
但是,如果对脚本使用标准输出重定向,就会遇到一个问题。来看下面的例子:
ls -al badfile > test3
# ls: cannot access badfile: No such file or directory
cat test3
当命令产生错误消息时,shell 并未将错误消息重定向到指定文件。shell 创建了输出重定向文件,但错误消息依然显示在屏幕上。注意,在查看 test3 文件的内容时,里面没有错误消息。test3 文件创建成功了,但里面空无一物。
shell 对于错误消息的处理是跟普通输出分开的。如果你创建了一个在后台运行的 shell 脚本,则通常必须依赖发送到日志文件的输出消息。用这种方法的话,如果出现错误消息,这些消息也不会出现在日志文件中,因此需要换一种方法来处理。
STDERR
shell 通过特殊的 STDERR 文件描述符处理错误消息。STDERR 文件描述符代表 shell 的标准错误输出。shell 或运行在 shell 中的程序和脚本报错时,生成的错误消息都会被送往这个位置。
在默认情况下,STDERR 和 STDOUT 指向同一个地方(尽管二者的文件描述符索引值不同)。也就是说,所有的错误消息也都默认会被送往显示器。
如你所见,STDERR 并不会随着 STDOUT 的重定向发生改变。在使用脚本时,你常常会想改变这种行为,尤其是希望将错误消息保存到日志文件中的时候。
重定向错误
现在已经知道如何用重定向符来重定向 STDOUT 数据。重定向 STDERR 数据也没太大差别,只要在使用重定向符时指定 STDERR 文件描述符就可以了。以下是两种实现方法。
只重定向错误
在之前已经看到,STDERR 的文件描述符为 2。可以将该文件描述符索引值放在重定向符号之前,只重定向错误消息。注意,两者必须紧挨着,否则无法正常工作:
ls -al badfile 2> test4
cat test4
# ls: cannot access badfile: No such file or directory
现在运行该命令,错误消息就不会出现在屏幕上了。命令生成的任何错误消息都会保存在指定文件中。用这种方法,shell 只重定向错误消息,而非普通数据。下面是另一个混合使用 STDOUT 和 STDERR 错误消息的例子:
ls -al test badtest test2 2> test5
# -rw-rw-r-- 1 roger roger 158 2024-06-04 11:32 test2
cat test5
# ls: cannot access test: No such file or directory
# ls: cannot access badtest: No such file or directory
ls 命令尝试列出了 3 个文件(test、badtest 和 test2)的信息。正常输出被送往默认的 STDOUT 文件描述符,也就是显示器。由于该命令将文件描述符 2(STDERR)重定向到了一个输出文件,因此 shell 会将产生的所有错误消息直接送往指定文件。
重定向错误消息和正常输出
如果想重定向错误消息和正常输出,则必须使用两个重定向符号。你需要在重定向符号之前放上需要重定向的文件描述符,然后让它们指向用于保存数据的输出文件:
ls -al test test2 test3 badtest 2> test6 1> test7
cat test6
# ls: cannot access test: No such file or directory
# ls: cannot access badtest: No such file or directory
cat test7
# -rw-rw-r-- 1 roger roger 158 2024-06-04 11:32 test2
# -rw-rw-r-- 1 roger roger 0 2024-06-04 11:33 test3
ls 命令的正常输出本该送往 STDOUT,shell 使用 1>将其重定向到了文件 test7,而本该送往 STDERR 的错误消息则通过 2>被重定向到了文件 test6。
你可以用这种方法区分脚本的正常输出和错误消息。这样就可以轻松识别错误消息,而不用在成千上万行正常输出中翻找了。
另外,如果愿意,也可以将 STDERR 和 STDOUT 的输出重定向到同一个文件。为此,bash shell 提供了特殊的重定向符&>:
ls -al test test2 test3 badtest &> test7
cat test7
# ls: cannot access test: No such file or directory
# ls: cannot access badtest: No such file or directory
# -rw-rw-r-- 1 roger roger 158 2024-06-04 11:32 test2
# -rw-rw-r-- 1 roger roger 0 2024-06-04 11:33 test3
当使用&>时,命令生成的所有输出(正常输出和错误消息)会被送往同一位置。注意,其中一条错误消息出现的顺序和预想不同。badtest 文件(列出的最后一个文件)的这条错误消息出现在了输出文件的第二行。为了避免错误消息散落在输出文件中,相较于标准输出,bash shell 自动赋予了错误消息更高的优先级。这样你就能集中浏览错误消息了。
在脚本中重定向输出
只需简单地重定向相应的文件描述符,就可以在脚本中用文件描述符 STDOUT 和 STDERR 在多个位置生成输出。在脚本中重定向输出的方法有两种。
- 临时重定向每一行。
- 永久重定向脚本中的所有命令。
临时重定向
如果你有意在脚本中生成错误消息,可以将单独的一行输出重定向到 STDERR。这只需要使用输出重定向符号将输出重定向到 STDERR 文件描述符。在重定向到文件描述符时,必须在文件描述符索引值之前加一个&:
echo "This is an error message" >&2
这行会在脚本的 STDERR 文件描述符所指向的位置显示文本。下面这个例子就使用了这项功能:
echo "This is an error" >&2
echo "This is normal output"
如果像平常一样运行这个脚本,你看不出任何区别:
./test8
# This is an error
# This is normal output
记住,在默认情况下,STDERR 和 STDOUT 指向的位置是一样的。但是,如果在运行脚本时重定向了 STDERR,那么脚本中所有送往 STDERR 的文本都会被重定向:
./test8 2> test9
# This is normal output
cat test9
# This is an error
太好了!通过 STDOUT 显示的文本出现在了屏幕上,而送往 STDERR 的 echo 语句的文本则被重定向到了输出文件。
这种方法非常适合在脚本中生成错误消息。脚本用户可以像上面的例子中那样,直接通过 STDERR 文件描述符重定向错误消息。
永久重定向
如果脚本中有大量数据需要重定向,那么逐条重定向所有的 echo 语句会很烦琐。这时可以用 exec 命令,它会告诉 shell 在脚本执行期间重定向某个特定文件描述符:
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"
./test10
cat testout
# This is a test of redirecting all output
# from a script to another file.
# without having to redirect every individual line
exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到指定文件。脚本中送往 STDOUT 的所有输出都会被重定向。
也可以在脚本执行过程中重定向 STDOUT:
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
该脚本使用 exec 命令将送往 STDERR 的输出重定向到了文件 testerror。接下来,脚本用 echo 语句向 STDOUT 显示了几行文本。随后再次使用 exec 命令将 STDOUT 重定向到 testout 文件。注意,尽管 STDOUT 被重定向了,仍然可以将 echo 语句的输出发送给 STDERR,在本例中仍是重定向到 testerror 文件。
当只想将脚本的部分输出重定向到其他位置(比如错误日志)时,这个特性用起来非常方便。不过这样做的话,会遇到一个问题。
一旦重定向了 STDOUT 或 STDERR,就不太容易将其恢复到原先的位置。如果需要在重定向中来回切换,那么可以创建自己的重定向。
在脚本中重定向输入
可以使用与重定向 STDOUT 和 STDERR 相同的方法,将 STDIN 从键盘重定向到其他位置。在 Linux 系统中,exec 命令允许将 STDIN 重定向为文件:
exec 0< testfile
该命令会告诉 shell,它应该从文件 testfile 中而不是键盘上获取输入。只要脚本需要输入,这个重定向就会起作用。来看一个用法示例:
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
./test12
# Line #1: This is the first line.
# Line #2: This is the second line.
# Line #3: This is the third line.
将 STDIN 重定向为文件后,当 read 命令试图从 STDIN 读入数据时,就会到文件中而不是键盘上检索数据。
这是一种在脚本中从待处理的文件读取数据的绝妙技术。Linux 系统管理员的日常任务之一就是从日志文件中读取并处理数据。这是完成该任务最简单的办法。
创建自己的重定向
在脚本中重定向输入和输出时,并不局限于这 3 个默认的文件描述符。前文提到过,在 shell 中最多可以打开 9 个文件描述符。替代性文件描述符从 3 到 8 共 6 个,均可用作输入或输出重定向。这些文件描述符中的任意一个都可以分配给文件并用在脚本中。
创建输出文件描述符
可以用 exec 命令分配用于输出的文件描述符。和标准的文件描述符一样,一旦将替代性文件描述符指向文件,此重定向就会一直有效,直至重新分配。来看一个在脚本中使用替代性文件描述符的简单例子:
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"
./test13
# This should display on the monitor
# Then this should be back on the monitor
cat test13out
# and this should be stored in the file
这个脚本使用 exec 命令将文件描述符 3 重定向到了另一个文件。当脚本执行 echo 语句时,文本会像预想中那样显示在 STDOUT 中。但是,重定向到文件描述符 3 的那行 echo 语句的输出进入了另一个文件。这样就可以维持显示器的正常输出,并将特定信息重定向到指定文件(比如日志文件)。
也可以不创建新文件,而是使用 exec 命令将数据追加到现有文件:
exec 3>>test13out
现在,输出会被追加到 test13out 文件,而不是创建一个新文件。
重定向文件描述符
有一个技巧能帮助你恢复已重定向的文件描述符。你可以将另一个文件描述符分配给标准文件描述符,反之亦可。这意味着可以将 STDOUT 的原先位置重定向到另一个文件描述符,然后再利用该文件描述符恢复 STDOUT。这听起来可能有点儿复杂,但实际上并不难。来看一个例子:
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"
./test14
# Now things should be back to normal
cat test14out
# This should store in the output file
# along with this line.
这个例子有点儿疯狂,我们一行一行地看。第一个 exec 命令将文件描述符 3 重定向到了文件描述符 1(STDOUT)的当前位置,也就是显示器。这意味着任何送往文件描述符 3 的输出都会出现在屏幕上。
第二个 exec 命令将 STDOUT 重定向到了文件,shell 现在会将发送给 STDOUT 的输出直接送往该文件。但是,文件描述符 3 仍然指向 STDOUT 原先的位置(显示器)。如果此时将输出数据发送给文件描述符 3,则它仍然会出现在显示器上,即使 STDOUT 已经被重定向了。
向 STDOUT(现在指向一个文件)发送一些输出之后,第三个 exec 命令将 STDOUT 重定向到了文件描述符 3 的当前位置(现在仍然是显示器)。这意味着现在 STDOUT 又恢复如初了,即指向其原先的位置——显示器。
这种方法可能有点儿让人犯晕,但的确是一种在脚本中临时重定向输出,然后恢复原位的常用方法。
创建输入文件描述符
可以采用和重定向输出文件描述符同样的办法来重定向输入文件描述符。在重定向到文件之前,先将 STDIN 指向的位置保存到另一个文件描述符,然后在读取完文件之后将 STDIN 恢复到原先的位置:
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
./test15
# Line #1: This is the first line.
# Line #2: This is the second line.
# Line #3: This is the third line.
# Are you done now? y
# Goodbye
在这个例子中,文件描述符 6 用于保存 STDIN 指向的位置。然后脚本将 STDIN 重定向到一个文件。read 命令的所有输入都来自重定向后的 STDIN(也就是输入文件)。
在读完所有行之后,脚本会将 STDIN 重定向到文件描述符 6,恢复 STDIN 原先的位置。该脚本使用另一个 read 命令来测试 STDIN 是否恢复原位,这次 read 会等待键盘的输入。
创建读/写文件描述符
尽管看起来可能很奇怪,但你也可以打开单个文件描述符兼做输入和输出,这样就能用同一个文件描述符对文件进行读和写两种操作了。
但使用这种方法时要特别小心。由于这是对一个文件进行读和写两种操作,因此 shell 会维护一个内部指针,指明该文件的当前位置。任何读或写都会从文件指针上次的位置开始。如果粗心的话,这会产生一些令人瞠目的结果。来看下面这个例子:
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3
cat testfile
# This is the first line.
# This is the second line.
# This is the third line.
./test16
# Read: This is the first line.
cat testfile
# This is the first line.
# This is a test line
# ine.
# This is the third line.
在这个例子中,exec 命令将文件描述符 3 用于文件 testfile 的读和写。接下来,使用分配好的文件描述符,通过 read 命令读取文件中的第一行,然后将其显示在 STDOUT 中。最后,使用 echo 语句将一行数据写入由同一个文件描述符打开的文件中。
在运行脚本时,一开始还算正常。输出内容表明脚本读取了 testfile 文件的第一行。但如果在脚本运行完毕后查看 testfile 文件内容,则会发现写入文件中的数据覆盖了已有数据。
当脚本向文件中写入数据时,会从文件指针指向的位置开始。read 命令读取了第一行数据,这使得文件指针指向了第二行数据的第一个字符。当 echo 语句将数据输出到文件时,会将数据写入文件指针的当前位置,覆盖该位置上的已有数据。
关闭文件描述符
如果创建了新的输入文件描述符或输出文件描述符,那么 shell 会在脚本退出时自动将其关闭。然而在一些情况下,需要在脚本结束前手动关闭文件描述符。
要关闭文件描述符,只需将其重定向到特殊符号&-即可。在脚本中如下所示:
exec 3>&-
该语句会关闭文件描述符 3,不再在脚本中使用。下面的例子演示了试图使用已关闭的文件描述符时的情况:
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
一旦关闭了文件描述符,就不能在脚本中向其写入任何数据,否则 shell 会发出错误消息。
在关闭文件描述符时还要注意另一件事。如果随后你在脚本中打开了同一个输出文件,那么 shell 就会用一个新文件来替换已有文件。这意味着如果你输出数据,它就会覆盖已有文件。来看下面这个例子:
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
在向 test17file 文件发送了字符串并关闭该文件描述符之后,脚本会使用 cat 命令显示文件内容。到这一步的时候,一切都还好。接下来,脚本重新打开了该输出文件并向它发送了另一个字符串。在显示该文件的内容时,你能看到的就只有第二个字符串。shell 覆盖了原来的输出文件。
列出打开的文件描述符
能用的文件描述符只有 9 个,你可能会觉得这没什么复杂的。但有时要记住哪个文件描述符被重定向到了哪里就没那么容易了。为了帮助你厘清条理,bash shell 提供了 lsof 命令。
lsof 命令会列出整个 Linux 系统打开的所有文件描述符,这包括所有后台进程以及登录用户打开的文件。
有大量的命令行选项和参数可用于过滤 lsof 的输出。最常用的选项包括-p 和-d,前者允许指定进程 ID(PID),后者允许指定要显示的文件描述符编号(多个编号之间以逗号分隔)。
要想知道进程的当前 PID,可以使用特殊环境变量$$(shell 会将其设为当前 PID)。-a 选项可用于对另外两个选项的结果执行 AND 运算,命令输出如下:
lsof -a -p $$ -d 0,1,2
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# bash 245336 roger 0u CHR 136,1 0t0 4 /dev/pts/1
# bash 245336 roger 1u CHR 136,1 0t0 4 /dev/pts/1
# bash 245336 roger 2u CHR 136,1 0t0 4 /dev/pts/1
结果显示了当前进程(bash shell)的默认文件描述符(0、1 和 2)。lsof 的默认输出中包含多列信息。
列 | 描述 |
---|---|
COMMAND | 进程对应的命令名的前 9 个字符 |
PID | 进程的 PID |
USER | 进程属主的登录名 |
FD | 文件描述符编号以及访问类型(r 代表读,w 代表写,u 代表读写) |
TYPE | 文件的类型(CHR 代表字符型,BLK 代表块型,DIR 代表目录,REG 代表常规文件) |
DEVICE | 设备号(主设备号和从设备号) |
SIZE | 如果有的话,表示文件的大小 |
NODE | 本地文件的节点号 |
NAME | 文件名 |
与 STDIN、STDOUT 和 STDERR 关联的文件类型是字符型,因为文件描述符 STDIN、STDOUT 和 STDERR 都指向终端,所以输出文件名就是终端的设备名。这 3 个标准文件都支持读和写(尽管向 STDIN 写数据以及从 STDOUT 读数据看起来有点儿奇怪)。
现在,在打开了多个替代性文件描述符的脚本中,看一下使用 lsof 命令的结果:
exec 3> test18file1
exec 6> test18file2
exec 7< testfile
lsof -a -p $$ -d0,1,2,3,6,7
./test18
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# bash 355809 roger 0u unix 0x0000000000000000 0t0 1802451 type=STREAM
# bash 355809 roger 1u unix 0x0000000000000000 0t0 1802453 type=STREAM
# bash 355809 roger 2u unix 0x0000000000000000 0t0 1802455 type=STREAM
# bash 355809 roger 3w REG 8,2 0 1089070 /home/roger/Downloads/test18file1
# bash 355809 roger 6w REG 8,2 0 1089071 /home/roger/Downloads/test18file2
# bash 355809 roger 7r REG 8,2 72 1051167 /home/roger/Downloads/testfile
这个脚本创建了 3 个替代性文件描述符,两个用作输出(3 和 6),一个用作输入(7)。在脚本运行 lsof 命令时,你会在输出中看到新的文件描述符。文件名显示了文件描述符所使用文件的完整路径。每个文件都显示为 REG 类型,说明这些是文件系统中的常规文件。
抑制命令输出
有时候,你可能不想显示脚本输出。将脚本作为后台进程运行时这很常见。如果在后台运行的脚本出现错误消息,那么 shell 就会将其通过邮件发送给进程属主。这会很麻烦,尤其是当运行的脚本生成很多烦琐的小错误时。
要解决这个问题,可以将 STDERR 重定向到一个名为 null 文件的特殊文件。跟它的名字很像,null 文件里什么都没有。shell 输出到 null 文件的任何数据都不会被保存,全部会被丢弃。
在 Linux 系统中,null 文件的标准位置是/dev/null。重定向到该位置的任何数据都会被丢弃,不再显示:
ls -al > /dev/null
cat /dev/null
这是抑制错误消息出现且无须保存它们的一种常用方法:
ls -al badfile test16 2> /dev/null
# -rw-rw-r-- 1 roger roger 31 Jun 3 16:36 test16
也可以在输入重定向中将/dev/null 作为输入文件。由于/dev/null 文件不含任何内容,因此程序员通常用它来快速清除现有文件中的数据,这样就不用先删除文件再重新创建了:
cat testfile
# This is the first line.
# This is the second line.
# This is the third line.
cat /dev/null > testfile
cat testfile
文件 testfile 仍然还在,但现在是一个空文件。这是清除日志文件的常用方法,因为日志文件必须时刻等待应用程序操作。
使用临时文件
Linux 系统有一个专供临时文件使用的特殊目录/tmp,其中存放那些不需要永久保留的文件。大多数 Linux 发行版配置系统在启动时会自动删除/tmp 目录的所有文件。
系统中的任何用户都有权限读写/tmp 目录中的文件。这个特性提供了一种创建临时文件的简单方法,而且还无须担心清理工作。
甚至还有一个专门用于创建临时文件的命令 mktemp,该命令可以直接在/tmp 目录中创建唯一的临时文件。所创建的临时文件不使用默认的 umask 值。作为临时文件属主,你拥有该文件的读写权限,但其他用户无法访问(当然,root 用户除外)。
创建本地临时文件
在默认情况下,mktemp 会在本地目录中创建一个文件。在使用 mktemp 命令时,只需指定一个文件名模板即可。模板可以包含任意文本字符,同时在文件名末尾要加上 6 个 X:
mktemp testing.XXXXXX
ls -al testing*
# -rw------- 1 roger roger 0 Jun 4 15:41 testing.e0IV4d
mktemp 命令会任意地将 6 个 X 替换为同等数量的字符,以保证文件名在目录中是唯一的。你可以创建多个临时文件,并确保每个文件名都不重复:
mktemp testing.XXXXXX
# testing.Gkbu8D
mktemp testing.XXXXXX
# testing.2nYWbp
mktemp testing.XXXXXX
# testing.GB0qzj
ls -l testing*
# -rw------- 1 roger roger 0 Jun 4 15:46 testing.2nYWbp
# -rw------- 1 roger roger 0 Jun 4 15:46 testing.GB0qzj
# -rw------- 1 roger roger 0 Jun 4 15:46 testing.Gkbu8D
如你所见,mktemp 命令的输出正是它所创建的文件名。在脚本中使用 mktemp 命令时,可以将文件名保存到变量中,这样就能在随后的脚本中引用了:
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
./test19
# This script writes to temp file test19.vCHoya
# Done creating temp file. The contents are:
# This is the first line
# This is the second line.
# This is the last line.
该脚本使用 mktemp 命令创建了临时文件并将文件名赋给了$tempfile 变量。接下来将这个临时文件作为文件描述符 3 的输出重定向文件。将临时文件名显示在 STDOUT 之后,向临时文件中写入了几行文本,然后关闭了文件描述符。最后,显示临时文件的内容,用 rm 命令将其删除。
在/tmp 目录中创建临时文件
-t 选项会强制 mktemp 命令在系统的临时目录中创建文件。在使用这个特性时,mktemp 命令会返回所创建的临时文件的完整路径名,而不只是文件名:
mktemp -t test.XXXXXX
# /tmp/test.xG3374
ls -al /tmp/test*
# -rw------- 1 roger roger 0 Jun 4 15:55 /tmp/test.kOSRbP
由于 mktemp 命令会返回临时文件的完整路径名,因此可以在文件系统的任何位置引用该临时文件:
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
./test20
The temp file is located at: /tmp/tmp.Ma3390
This is a test file.
This is the second line of the test.
在创建临时文件时,mktemp 会将全路径名返回给环境变量。这样就能在任何命令中使用该值来引用临时文件了。
创建临时目录
-d 选项会告诉 mktemp 命令创建一个临时目录。你可以根据需要使用该目录,比如在其中创建其他的临时文件:
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
./test21
Sending data to directory dir.ouT8S8
ls -al
# drwx------ 2 roger roger 4.0K Jun 4 22:41 dir.mS6XxQ
cd dir.mS6XxQ
ls -al
# -rw------- 1 roger roger 44 Jun 4 22:41 temp.HUuzLL
# -rw------- 1 roger roger 44 Jun 4 22:41 temp.Sq6DsF
cat temp.HUuzLL
# This is a test line of data for temp.HUuzLL
cat temp.Sq6DsF
# This is a test line of data for temp.Sq6DsF
这段脚本在当前目录中创建了一个临时目录,然后使用 cd 命令进入该目录,在其中创建了两个临时文件。这两个临时文件又被分配给了文件描述符以用来保存脚本的输出。
记录消息
有时候,也确实需要将输出同时送往显示器和文件。与其对输出进行两次重定向,不如改用特殊的 tee 命令。
tee 命令就像是连接管道的 T 型接头,它能将来自 STDIN 的数据同时送往两处。一处是 STDOUT,另一处是 tee 命令行所指定的文件名:
tee filename
由于 tee 会重定向来自 STDIN 的数据,因此可以用它配合管道命令来重定向命令输出:
date | tee testfile
# Tue Jun 4 10:45:58 PM CST 2024
cat testfile
# Tue Jun 4 10:45:58 PM CST 2024
输出出现在了 STDOUT 中,同时写入了指定文件。注意,在默认情况下,tee 命令会在每次使用时覆盖指定文件的原先内容:
who | tee testfile
# roger pts/0 2024-05-30 13:05 (10.211.55.2)
cat testfile
# roger pts/0 2024-05-30 13:05 (10.211.55.2)
如果想将数据追加到指定文件中,就必须使用-a 选项:
date | tee -a testfile
# Tue Jun 4 10:48:42 PM CST 2024
cat testfile
# roger pts/0 2024-05-30 13:05 (10.211.55.2)
# Tue Jun 4 10:48:42 PM CST 2024
利用这种方法,既能保存数据,又能将其显示在屏幕上:
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
./test22
# This is the start of the test
# This is the second line of the test
# This is the end of the test
cat test22file
# This is the start of the test
# This is the second line of the test
# This is the end of the test
现在,你可以在为用户显示输出的同时再永久保存一份输出内容了。