第二部分 shell脚本编程基础
第11章构建基础脚本
第12章结构化命令
第13章更多的结构化命令
第14章处理用户输入
第15章呈现数据
第16章脚本控制
第15章 呈现数据
15.1 理解输入和输出
15.1.1 标准文件描述符
Linux 系统会将每个对象当作文件来处理,这包括输入和输出。Linux 用文件描述符来标识
每个文件对象。
15.1.2 重定向错误 #
15.2 在脚本中重定向输出
15.2.1 临时重定向
如果你有意在脚本中生成错误消息,可以将单独的一行输出重定向到STDERR。这只需要使
用输出重定向符号将输出重定向到STDERR 文件描述符符。在重定向到文件描述符时,必须在文件描述符索引值之前加一个&:
echo "This is an error message" >&2
这行会在脚本的STDERR 文件描述符所指向的位置显示文本。
$ cat test10
#!/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"
$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
$
15.2.2 永久重定向: exec 命令
如果脚本中有大量数据需要重定向,那么逐条重定向所有的echo 语句会很烦琐。这时可以
用exec 命令,exec 命令会启动一个新shell 并将STDOUT 文件描述符重定向到指定文件。脚本中送往STDOUT 的所有输出都会被重定向
15.3 在脚本中重定向输入
在Linux 系统中,exec 命令允许将STDIN 重定向为文件:
exec 0< testfile
15.4 创建自己的重定向
15.4.1 创建输出文件描述符
可以用exec 命令分配用于输出的文件描述符
exec 3 > test13out
15.4.2 重定向文件描述符
exec 3>&1 # 将文件描述符3 重定向到了文件描述符1(STDOUT)的当前位置,
将发送给STDOUT 的输出直接送
往该文件。
$ cat test14
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1 # 将文件描述符3 重定向到了文件描述符1(STDOUT)的当前位置
exec 1>test14out # 将STDOUT 重定向到了test14out文件
echo "This should store in the output file"
echo "along with this line."
exec 1>&3 #令将STDOUT 重定向到了文件描述符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.
$
15.4.3 创建输入文件描述符 #
$ cat test15
#!/bin/bash
# redirecting input file descriptors
exec 6<&0 #文件描述符6 用于保存STDIN 指向的位置
exec 0< testfile #将STDIN 重定向到一个文件
count=1
while read line # read 命令的所有输入都来自输入文件
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
$
15.4.4 创建读/写文件描述符 #
打开单个文件描述符兼做输入和输出,这样就能用同一个文件描述符对文件进行读和写两种操作
15.4.5关闭文件描述符 #
要关闭文件描述符,只需将其重定向到特殊符号&-即可。在脚本中如下所示:
exec 3>&-
15.5 列出打开的文件描述符
bash shell 提供了lsof 命令
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0
$
15.6 抑制命令输出
不想显示脚本输出。将脚本作为后台进程运行时这很常见。要解决这个问题,可以将STDERR 重定向到一个名为null 文件的特殊文件。跟它的名字很像,null 文件里什么都没有。shell 输出到null 文件的任何数据都不会被保存,全部会被丢弃。
$ ls -al > /dev/null
$ cat /dev/null
$
15.7 使用临时文件
15.7.1 创建本地临时文件
在默认情况下,mktemp 会在本地目录中创建一个文件。在使用mktemp 命令时,只需指定
一个文件名模板即可。模板可以包含任意文本字符,同时在文件名末尾要加上6 个X:
$ mktemp testing.XXXXXX
15.7.2 在/tmp目录中创建临时文件
-t 选项会强制mktemp 命令在系统的临时目录中创建文件。在使用这个特性时,mktemp
命令会返回所创建的临时文件的完整路径名,而不只是文件名:
$ mktemp -t test.XXXXXX
15.7.3 创建临时目录
-d 选项会告诉mktemp 命令创建一个临时目录。你可以根据需要使用该目录,比如在其中
创建其他的临时文件:
$ cat test21
#!/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 命令。
tee 命令就像是连接管道的T 型接头
$ date | tee testfile
Sun Jun 21 18:56:21 EDT 2020
$ cat testfile
Sun Jun 21 18:56:21 EDT 2020
$
第16章 脚本控制
16.1 处理信号
16.1.1 重温Linux信号
16.1.2 产生信号
1. 中断进程:Ctrl+C 组合
2. 暂停进程: Ctrl+Z 组合
16.1.3 捕获信号:trap 命令
trap 命令可以指定shell脚本需要侦测并拦截的Linux 信号。如果脚本收到了trap 命令中列出的信号,则该信号不再由shell 处理,而是由本地处理。
trap 命令的格式如下:
trap commands signals
$ cat trapsignal.sh
#!/bin/bash
#Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT # 捕获信号
#
echo This is a test script.
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
echo "This is the end of test script."
exit
$
$ ./trapsignal.sh
This is a test script.
Loop #1
Loop #2
^C Sorry! I have trapped Ctrl-C
Loop #3
^C Sorry! I have trapped Ctrl-C
Loop #4
Loop #5
This is the end of test script.
$
16.1.4 捕获脚本退出
在shell 脚本中捕获信号,也可以在shell 脚本退出时捕获信号。这是在shell 完成任务时执行命令的一种简便方法。要捕获shell 脚本的退出,只需在trap 命令后加上EXIT 信号即可
$ cat trapexit.sh
#!/bin/bash
#Testing exit trapping
#
trap "echo Goodbye..." EXIT # 捕获脚本退出
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ ./trapexit.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...
$
16.1.5 修改或移除信号捕获
想在脚本中的不同位置进行不同的信号捕获处理,只需重新使用带有新选项的trap 命令即可
16.2 以后台模式运行脚本
使用ps -e 命令,可以看到Linux 系统中运行的多个进程
16.2.1 后台运行脚本
以后台模式运行shell 脚本非常简单,只需在脚本名后面加上&即可
$ cat backgroundscript.sh
#!/bin/bash
#Test running in the background
#
count=1
while [ $count -le 5 ]
do
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ ./backgroundscript.sh &
[1] 2595
$
16.2.2 运行多个后台作业
在使用命令行提示符的情况下,可以同时启动多个后台作业
$ ./testAscript.sh &
[1] 2753
$ This is Test Script #1.
$ ./testBscript.sh &
[2] 2755
$ This is Test Script #2.
$ ./testCscript.sh &
[3] 2757
$ And... another Test script.
$ ./testDscript.sh &
[4] 2759
$ Then...there was one more Test script.
16.3 在非控制台下运行脚本: nohup
即便退出了终端会话,你也想在终端会话中启动shell 脚本,让脚本一直以后台模式运行到结束。这可以用nohup 命令来实现
nohup 命令的格式如下:
nohup command
下面的例子使用一个后台脚本作为command:
$ nohup ./testAscript.sh &
[1] 1828
$ nohup: ignoring input and appending output to 'nohup.out'
注意 nohup.out 文件一般在当前工作目录中创建,否则会在$HOME 目录中创建
16.4 作业控制
在作业停止后,Linux 系统会让你选择是“杀死”该作业还是重启该作业。用kill 命令可以“杀死”该作业。要重启停止的进程,则需要向其发送SIGCONT 信号。
16.4.1 查看作业
jobs 是作业控制中的关键命令,通过jobs 命令可以查看分配给shell 的作业
jobs 命令的-l 选项(小写字母l)查看作业的PID。
16.4.2 重启已停止的作业
要以后台模式重启作业,可以使用bg 命令
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ bg
[1]+ ./restartjob.sh &
$
$ jobs
[1]+ Running ./restartjob.sh &
$
16.5 调整谦让度
在多任务操作系统(比如Linux)中,内核负责为每个运行的进程分配CPU 时间。调度优先
级[也称为谦让度(nice value)]是指内核为进程分配的CPU 时间(相对于其他进程)
调度优先级是一个整数值,取值范围从-20(最高优先级)到+19(最低优先级)。
只要记住那句俗话“好人难做。”(Nice guys finish last.)即可。越是“谦让”(nice)或是值越
大,获得CPU 的机会就越低。
16.5.1 nice命令
nice 命令允许在启动命令时设置其调度优先级。要想让命令以更低的优先级运行,只需用
nice 命令的-n 选项指定新的优先级即可:
$ nice -n 10 ./jobcontrol.sh > jobcontrol.out &
注意,nice 命令和要启动的命令必须出现在同一行中。ps 命令的输出证实,谦让度(NI列)已经调整到了10。
16.5.2 renice命令
修改系统中已运行命令的优先级。可以使用renice 命令
renice 命令对于非特权用户也有一些限制:只能对属主为自己的进程使用renice 且只能降低调度优先级。
16.6 定时运行作业
Linux 系统提供了多个在预选时间运行脚本的方法:at 命令、cron 表以及anacron。
16.6.1 使用at命令调度作业
at 命令允许指定Linux 系统何时运行脚本。
1. at 命令的格式
at 命令的基本格式非常简单:
at [-f filename] time
在使用at 命令时,该作业会被提交至作业队列。作业队列保存着通过at 命令提交的待处
理作业。针对不同优先级,有52 种作业队列。作业队列通常用小写字母a~z 和大写字母A~Z 来
指代,A 队列和a 队列是两个不同的队列。
2. 获取作业的输出
at 命令会显示分配给作业的作业号以及为作业安排的运行时间。-f 选项指明使用哪个脚本
文件。now 指示at 命令立刻执行该脚本。
3. 列出等待的作业
atq 命令可以查看系统中有哪些作业在等待
4. 删除作业
就可以用atrm 命令删除等待中的作业。指定要删除的作业号即可
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
7 Thu Jun 18 20:30:00 2020 a christine
8 Thu Jun 18 17:54:00 2020 a christine
$
$ atrm 5
16.6.2 调度需要定期运行的脚本
使用cron 程序调度需要定期执行的作业。cron 在后台运行,并会检查一个特殊的表(cron 时间表),从中获知已安排执行的作业。
1. cron 时间表
cron 时间表通过一种特别的格式指定作业何时运行,其格式如下:
minutepasthour hourofday dayofmonth month dayofweek command
2. 构建cron 时间表
要列出已有的cron 时间表,可以用-l 选项:
$ crontab -l
3. 浏览cron 目录
创建的脚本对于执行时间的精确性要求不高,则用预配置的cron 脚本目录会更方便。
预配置的基础目录共有4 个:hourly、daily、monthly 和weekly
$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apt-compat cracklib-runtime logrotate [...]
apport bsdmainutils dpkg man-db [...]
4. anacron 程序
使用场景:如果某个作业在cron 时间表中设置的运行时间已到,但这时候Linux 系统处于关闭状态,那么该作业就不会运行。当再次启动系统时,cron 程序不会再去运行那些错过的作业。
anacron 判断出某个作业错过了设置的运行时间,它会尽快运行该作业
16.7 使用新shell启动脚本
每次启动新shell,bash shell 都会运行.bashrc 文件。