shell脚本编程系列
理解输入和输出
目前为止了解了两种显示脚本输出的方法:在显示器屏幕上显示输出,将输出重定向到文件中,这两种方法将数据输出全部显示出来,要么什么都不显示,但有时一部分数据显示屏幕上,另一部分保存文件更合适。
标准文件描述符
Linux系统会将每个对象当做文件来处理,包括输入和输出。
Linux用文件描述符来标识每个文件对象,文件描述符是一个非负证书,唯一会标识的是会话中打开的文件。
bash shell保留前3个文件描述符(0,1,2)
-
0 STDIN 标准输入
代表shell的标准输入,对终端界面来说,标准输入就是键盘。shell会从STDIN文件描述符对应的键盘获得输入并进行处理。
使用输入重定向符<,Linux会用重定向指向的文件替换标准输入描述符。
当在命令行中只输入cat命令时,会从STDIN接收输入,输入一行,cat命令就会显示一行。
使用重定向符强制cat命令接收来自STDIN之外的文件输入。
-
1 STDOUT 标准输出
shell的标准输出,在终端界面上,标准输出就是终端显示器。
在默认情况下,绝大多数的bash命令会将输出送往STDOUT文件描述符。
可以通过重定向将标准输出改为其他目的地,其中>为覆写,而>>为追加。
当产生错误消息时,shell并未将消息重定向到指定文件。
- 2 STDERR 标准错误
shell或运行在shell中的程序和脚本报错时,生成的错误消息都会送到标准错误输出。
默认情况下,STDERR和STDOUT指向同一个地方,默认为显示器。
STDERR并不会随着STDOUT的重定向发生改变。
重定向错误
- 只重定向错误
STDERR的文件描述符为2,可以将该文件描述符索引值放在重定向符号之前,只重定向错误消息
- 重定向错误消息和正常输出
1> 正常输出重定向 2>错误重定向
可以将STDERR和STDOUT的输出重定向到同一个文件,使用特殊的重定向符&>即可
在脚本中重定向输出
- 临时重定向
在脚本中,重定向到文件描述符时,必须在文件描述符索引值之前加一个&,比如错误重定向 >&2
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
通过在脚本中将内容重定向到错误描述符,然后在执行脚本时,再将错误描述符重定向
./test8.sh 2> test9
- 永久重定向
使用exec命令,告诉shell在脚本执行期间重定向某个特定文件描述符
#!/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"
在脚本执行过程中重定向STDOUT
#!/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
在脚本中重定向输入
exec 0< testfile 从testfile中而不是键盘上获取输入
#!/bin/bash
# redirecting file input
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
前面介绍过使用read命令读取用户在键盘上输入的数据,将STDIN重定向为文件后,当read命令试图从STDIN读入数据时,就会从文件而不是键盘上检索数据
创建自己的重定向
-
创建输出文件描述符
使用exec命令分配用于输出的文件描述符,比如exec 3> test13out将文件描述符3重定向到了另一个文件,当执行echo语句后面有 >&3时重定向到文件描述符3的输出会进入另一个文件,当然也可以通过 exec 3>>test13out进行追加写入
-
重定向文件描述符
有一个技巧可以帮助恢复已重定向的文件描述符。比如先将STDOUT的原先位置重定向到另一个文件描述符,然后再利用该文件描述符恢复STDOUT
-
创建输入文件描述符
可以采用和重定向输出文件描述符同样的办法来重定向输入文件描述符,在重定向到文件之前,先将STDIN指向的位置保存到另一个文件描述符,然后在读取文件之后将STDIN恢复到原先的位置#!/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
-
创建读/写文件描述符
尽管看起来奇怪,但是可以打开单个文件描述符兼做输入和输出,但是需要特别小心#!/bin/bash # testing input/output file descriptor exec 3<> testfile read line <&3 echo "Read: $line" echo "This is a test line" >&3
-
关闭文件描述符
如果创建了新的输入文件描述符或者输出文件描述符,那么shell会在脚本退出时自动将其关闭.
在有一些情况下,需要在脚本结束前手动关闭文件描述符。要关闭文件描述符,只需要将其重定向到特殊符号&-即可。比如 exec 3>&-会关闭文件描述符3,不再在脚本中使用。#!/bin/bash # testing closing file descriptors exec 3> test17file echo "This is a test line of data" >&3 exec 3>&- echo "This won’t work" >&3
如果已经关闭了文件描述符,随后又打开一个,那么会覆盖已有文件
#!/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 [root@192 Chapter15-Scripts]# ./test17.sh This is a test line of data
列出打开的文件描述符
lsof命令会列出整个Linux系统打开的所有文件描述符,包括所有后台进程以及登录用户打开的文件。lsof命令可以使用-p指定进程ID,-d指定要显示的文件描述符编号。另外如果要执行进程的当前PID,可以使用特殊环境变量$$
。-a选项则用于对两个选项的结果执行AND运算。
lsof -a -p $$ -d 0,1,2
多打开一些文件描述符
#!/bin/bash
# testing lsof with file descriptors
exec 3> test18file1
exec 6> test18file2
exec 7< testfile
/usr/sbin/lsof -a -p $$ -d0,1,2,3,6,7
抑制命令输出
有时候你可能不想脚本输出,将脚本作为后台进程运行时这很常见,如果在后台运行的脚本出现错误信息,那么shell就会将其通过邮件发送给进程属主,这会很麻烦
可以将STDERR重定向到一个名为null文件的特殊文件,跟它的名字很像,null文件里面什么都没有,shell输出到null文件的任何数据都不会被保存,全部会被丢弃。Linux中,null文件的标准位置是/dev/null,重定向到该位置的数据都会被丢弃
ls -al > /dev/null
也可以在输入重定向中将/dev/null作为输入文件,由于/dev/null不含任何内容,因此通常可以使用它来清空现有文件中的数据
cat /dev/null > testfile
使用临时文件
-
创建本地临时文件
Linux系统有一个专供临时文件使用的特殊目录/tmp,其中存放不需要永久保留的文件,在启动时会自动删除/tmp目录的所有文件。
系统中的任何用户都有权限读写/tmp目录中的文件。
默认情况下,mktemp会在本地目录中创建一个文件,在使用该命令时,需指定一个文件名模板,模板可以包含任意文本字符,同时在文件名末尾加上6个Xmktemp testing.XXXXXX
可以创建多个临时文件,并确保每个文件名都不重复
mktemp命令的输出正是创建的临时文件,这样在脚本中使用该命令时,可以将文件名保存到变量中,在随后的脚本中就能使用了
#!/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
-
在/tmp目录中创建临时文件
-t选项会强制mktemp命令在系统的临时目录中创建文件,此时命令会返回临时文件的完整路径名,而不是文件名mktemp -t test.XXXXXX
#!/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
-
创建临时目录
-d选项会告诉mktemp命令创建一个临时目录#!/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
记录消息
有时候需要将消息同时送往显示器和文件,与其对输出进行两次重定向,不如使用特殊的tee命令,它能将来自STDIN的数据同时送往两处。一处是STDOUT,另一处是tee命令行指定的文件名。通常用来配合管道命令来重定向命令输出
date | tee testfile
如果想追加数据到指定文件中,比如使用-a选项
#!/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
总结
在创建脚本时,能够理解bash shell如何处理输入和输出会带来很多方便。你可以改变脚本获取数据和显示数据的方式,对脚本进行定制以适应各种环境。脚本的输入/输出都可以从标准输入(STDIN)/标准输出(STDOUT)重定向到系统中的任意文件。
除了STDOUT,你可以通过重定向STDERR来改变错误消息的流向。这是通过重定向与STDERR关联的文件描述符(文件描述符2)来实现的。你可以将STDERR和STDOUT重定向到同一个文件,也可以重定向到不同的文件,以此区分脚本的正常输出和错误消息。
bash shell允许在脚本中创建自己的文件描述符。你可以创建文件描述符3到8,将其分配给要用的任何输出文件。一旦创建了文件描述符,就可以利用标准的重定向符号将任意命令的输出重定向到那里。
bash shell也允许重定向输入,这样就能方便地将文件数据读入脚本。你可以通过lsof命令来显示shell中使用的文件描述符。
Linux系统提供了一个特殊文件/dev/null,用于重定向无用的输出。Linux系统会丢弃重定向到/dev/null的任何数据。你也可以通过将/dev/null的内容重定向到文件来清空该文件的内容。
mktemp命令很有用,它可以直接创建临时文件和目录。只需给mktemp命令指定一个模板,它就能在每次调用时基于该文件模板的格式创建一个唯一的文件。你也可以在Linux系统的/tmp目录中创建临时文件和目录,系统启动时会清空这个特殊位置中的内容。
tee命令便于将输出同时发送给标准输出和日志文件,这样你就可以在屏幕上显示脚本消息的同时,将其保存到日志文件中。