《linux命令行与shell脚本编程大全》
全书4部分:
☆ 【1】linux命令行(1-10章)
☆ 【2】shell脚本编程基础(11-16章)下:输入输出与脚本控制
☆ 【3】高级shell脚本编程(17-23章)
☆ 【4】创建实用的脚本(24-26章)
>>第14章丶处理用户输入
$n命令行参数
bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数。
位置参数变量是标准的数字:
$0是程序名,
$1是第一个参数, $2是第二个参数,依次类推,直到第九个参数
$9。
/* 位置参数演示 */
#!/bin/bash
# using one command line parameter
#
factorial=1
for (( number = 1; number <= $1 ; number++ ))
do
factorial=$[ $factorial * $number ]
done
echo "The factorial of $1 is $factorial."
[~/shell/4]$: test1.sh 5
The factorial of 5 is 120.
如果需要输入更多的命令行参数,则每个参数都必须用
空格分开。
shell会将每个参数分配给对应的变量,变量可以分配数字,也可以分配字符串。
[
注意]每个参数都是用空格分隔的,所以shell会将空格当成两个值的分隔符。要在
参数值中包含空格,必须要用引号(单引号或双引号均可)。
/* 位置参数变量传递字符串 */
#!/bin/bash
# testing string parameters
#
echo Hello $1, glad to meet you.
[~/shell/4]$: test3.sh "Rich Blum"
Hello Rich Blum, glad to meet you.
在第9个变量之后,你必须在变量数字周围加上花括号,比如
${10}。
如:total=$[ ${10} * ${11} ]
$0读取脚本名
可以用 $0 参数获取shell在命令行启动的脚本名。这在编写多功能工具时很方便。
"
basename" 命令会返回不包含路径的脚本名。
/* basename 和 $0 演示*/
#!/bin/bash
# Using basename with the $0 parameter
#
name=$(basename $0)
echo The script name is: $name
[~/shell/4]$: bash /home/jiangyuan/shell/4/test2.sh
The script name is: test2.sh
/* 不同功能的脚本 */
#!/bin/bash
# Testing a Multi-function script
#
name=$(basename $0)
#
if [ $name = "addem" ]
then
total=$[ $1 + $2 ]
#
elif [ $name = "multem" ]
then
total=$[ $1 * $2 ]
fi
#
echo
echo The calculated value is $total
[~/shell/4]$: cp test6.sh addem
[~/shell/4]$: chmod u+x addem
[~/shell/4]$: ln -s test6.sh multem
[~/shell/4]$: ./addem 2 5
The calculated value is 7
[~/shell/4]$: ./multem 2 5
The calculated value is 10
在使用参数前一定要检查其中是否存在数据。如,使用了
-n测试来检查命令行参数$1中是否有数据。
if [ -n "$1" ]
then
echo Hello $1, glad to meet you.
else
echo "Sorry, you did not identify yourself. "
fi
$#特殊参数变量
特殊变量
$#含有脚本运行时携带的命令行参数的个数。
echo There were $# parameters supplied.
$ ./test8.sh 1 2 3 4 5 6 7 8 9 10
There were 10 parameters supplied.
如:测试参数总数是否等于2,如果不等于2,使用 if [ $# -ne 2 ]
${!#} 表示最后一个命令行参数的值。原型为:${$#},此种写法会报错,故使用!#表示最后一个参数。
$*和$@抓取所有数据
$*和$@变量可以用来轻松访问所有的参数。这两个变量都能够在单个变量中存储所有的命令行参数。
$*变量会将命令行上提供的所有参数当作一个单词保存。
$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。
/* $* 和 $@ */
#!/bin/bash
# testing $* and $@
#
count=1
#
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done
#
count=1
#
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done
[~/shell/4]$: test3.sh 1 2 3 4
$* Parameter #1 = 1 2 3 4
$@ Parameter #1 = 1
$@ Parameter #2 = 2
$@ Parameter #3 = 3
$@ Parameter #4 = 4
shift移动变量
shift命令会根据它们的相对位置来移动命令行参数。
在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。
/* shift命令演示 */
#!/bin/bash
# test shift command
count=1
while [ -n "$1" ]
do
echo "Param #$count=$1"
count=$[ $count + 1 ]
shift
done
[~/shell/4]$: test4.sh hello world how are you
Param #1=hello
Param #2=world
Param #3=how
Param #4=are
Param #5=you
[
注意]使用shift命令的时候要小心。如果某个参数被移出,它的值就被丢弃了,无法再恢复。
一次移动多个位置,使用 shift n,移动n个位置,如:
echo "The original parameters: $*"
shift 2
echo "Here's the new first parameter: $1"
$: ./test14.sh 1 2 3 4 5
The original parameters: 1 2 3 4 5
Here's the new first parameter: 3
查找选项:
提取每个单独参数时,使用case判断是否为选项:
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
--区分选项和参数
shell脚本中同时使用选项和参数时,Linux中处理这个问题的标准方式是用特殊字符双破折线(--)来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。
要检查双破折线,只要在case语句中加一项就行了。
/* --双破折线演示 */
#!/bin/bash
# extracting options and parameters
while [ -n "$1" ]
do
case "$1" in
-a) echo "found the -a option";;
-b) echo "found the -b option";;
-c) echo "found the -c option";;
--) shift
break;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in $@
do
echo "param #$count: $param"
count=$[ $count + 1 ]
done
[~/shell/4]$: test5.sh -a -a -b test1.sh test2.sh test3.sh
found the -a option
found the -a option
found the -b option
test1.sh is not an option
test2.sh is not an option
test3.sh is not an option
[~/shell/4]$: test5.sh -a -a -b -- test1.sh test2.sh test3.sh
found the -a option
found the -a option
found the -b option
param #1: test1.sh
param #2: test2.sh
param #3: test3.sh
处理带值的选项:
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
$: ./test17.sh -a -b test1 -d
Found the -a option
Found the -b option, with parameter value test1
-d is not an option
getopt转换选项和参数格式
命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格式。它的命令格式如下:
getopt optstring parameters
在optstring中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。 getopt命令会基于你定义的optstring解析提供的参数。
$: getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
在脚本中使用getopt来格式化脚本所携带的任何命令行选项或参数。方法是用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数。用set命令能够做到。
set命令的选项之一是双破折线(
--),它会将命令行参数替换成set命令的命令行值。
$: set -- $(getopt -q ab:cd "$@")
/* getopt 脚本演示 */
#!/bin/bash
# extracting options and parameters
set -- $(getopt -q ab:cd "$@")
while [ -n "$1" ]
do
case "$1" in
-a) echo "found the -a option";;
-b) param="$2"
echo "found the -b option, with param value $param"
shift;;
-c) echo "found the -c option";;
--) shift
break;;
*) echo "$1 is not an option";;
esac
shift
done
#
count=1
for param in $@
do
echo "param #$count: $param"
count=$[ $count + 1 ]
done
[~/shell/4]$: test6.sh -a -b test1 -cd test2 test3 test4
found the -a option
found the -b option, with param value 'test1'
found the -c option
-d is not an option
param #1: 'test2'
param #2: 'test3'
param #3: 'test4'
getopts转换选项和参数格式支持含空格
每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合用解析命令行所有参数的循环中。getopts命令的格式如下:
getopts optstring variable
/* getopts 脚本演示 */
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option, with value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt";;
esac
done
$: ./test19.sh -b "test1 test2" -a
Found the -b option, with value test1 test2
Found the -a option
在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,你可以使用shift命令和OPTIND值来移动参数:shift $[ $OPTIND - 1 ]
脚本中常用linux命令选项:
-a 显示所有对象
-c 生成一个计数
-d 指定一个目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略文本大小写
-l 产生输出的长格式版本
-n 使用非交互模式(批处理)
-o 将所有输出重定向到的指定的输出文件
-q 以安静模式运行
-r 递归地处理目录和文件
-s 以安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答yes
read获得用户输入
read命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后, read命令会将数据放进一个变量。
/* read 演示 */
echo -n "Enter your name: " # -n 可以去除echo输出行的最后一个\n符号
read name
echo "Hello $name, welcome to my program. "
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That makes you over $days days old! "
[~/shell/4]$: test7.sh
Please enter your age:27
That makes you over 9855 days old!
也可以在read命令行中不指定变量。如果是这样, read命令会将它收到的任何数据都放进特殊环境变量REPLY中,对其取值即可 $REPLY
-t选项指定了read命令等待输入的秒数。当计时器过期后, read命令会返回一个非零退出状态码。
如:if read -t 5 -p "Please enter your name: " name
也可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋给变量。
/* read 统计输入字符数演示 */
#!/bin/bash
# getting just one character of input
#
read -n2 -p "Do you want to continue [Y/n]? " answer
case $answer in
Y | y) echo
echo "fine, continue on…";;
N | n) echo
echo "OK, goodbye…"
exit;;
*) echo
echo "fine, continue on…";;
esac
echo "This is the end of the script."
[~/shell/4]$: test8.sh
Do you want to continue [Y/n]? Y
fine, continue on…
This is the end of the script.
[~/shell/4]$: test8.sh
Do you want to continue [Y/n]?
fine, continue on…
This is the end of the script.
[~/shell/4]$: test8.sh
Do you want to continue [Y/n]? n
OK, goodbye…
// 本例中-n2可以接收read的2个字符,如Y\n,或者一个\n选项的话效果一样。
-s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样)。
如:read -s -p "Enter your password: " pass
read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时, read命令会退出并返回非零退出状态码。
/* read 读取文件内容 */
#!/bin/bash
# test read file
count=1
cat $1 | while read line
do
echo "Line $count: $line"
count=$[ $count + 1 ]
done
echo "Finished processing the $1."
[~/shell/4]$: test10.sh test10.sh
Line 1: #!/bin/bash
Line 2: # test read file
Line 3: count=1
Line 4: cat $1 | while read line
Line 5: do
Line 6: echo "Line $count: $line"
Line 7: count=$[ $count + 1 ]
Line 8: done
Line 9: echo "Finished processing the $1."
Finished processing the test10.sh.
>>第15章丶呈现数据
bash shell保留了前三个文件描述符0、 1、2,分别对应STDIN(<)、STDOUT(>)、STDERR。
cat 单独输入命令行,shell会从STDIN输入。输入一行, cat命令就会显示出一行。
如:cat < testfile 会将testfile的内容重定向输入到cat命令对应的STDIN中。输入追加符号:<<
如:ls -l > test2 会将ls -l的内容重定向输出到test2文件中。输出追加符号:>>
重定向错误:
$: ls -al badfile
2> test4
$: cat test4
ls: cannot access badfile: No such file or directory
重定向错误和数据:
$: 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 rich rich 158 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:33 test3
&>将标准错误和输出重定向到同一文件
将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 rich rich 158 2014-10-16 11:32 test2
-rw-rw-r-- 1 rich rich 0 2014-10-16 11:33 test3
>&临时重定向:
如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到STDERR。格式为:>&stdfileno
$: echo "This is an error message" >&2
默认情况下, Linux会将STDERR导向STDOUT。但是,如果你在运行脚本时重定向了STDERR,脚本中所有导向STDERR的文本都会被重定向。
/* 临时重定向演示 */
$: cat test8
#!/bin/bash
# testing STDERR messages
echo "This is an error" >&2
echo "This is normal output"
$: ./test8
This is an error
This is normal output
$: ./test8 2> test9
This is normal output
$: cat test9
This is an error
[
窍门]通过STDOUT显示的文本显示在了屏幕上,而发送给STDERR的echo语句的文本则被重定向到了输出文件。这个方法非常适合在脚本中生成错误消息。
exec永久重定向
如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很烦琐。取而代之,你可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。
/* exec永久重定向演示 */
#!/bin/bash
# redirecting all output to a file
exec 1>$1
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 testfile
$ cat testfile
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
在脚本中重定向输入:
exec命令允许你将STDIN重定向到Linux系统上的文件中:
exec 0< testfile
这个命令会告诉shell它应该从文件testfile中获得输入,而不是STDIN。
这是在脚本中从待处理的文件中读取数据的绝妙办法。 Linux系统管理员的一项日常任务就是从日志文件中读取数据并处理。
创建输出文件描述符:
用exec命令来给输出分配文件描述符。和标准的文件描述符一样,一旦将另一个文件描述符分配给一个文件,这个重定向就会一直有效,直到你重新分配。
/* 演示 */
#!/bin/bash
# using an alternative file descriptor
exec 3>test13out
# exec 3>>test13out2 追加到文件中去
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>filename →→→ echo "some string" >&3 即可将单独的内容输入到文件filename中
创建输入文件描述符:
/* 演示 */
#!/bin/bash
# redirecting input file descriptors
touch testfile
exec 6<&0
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
exec 0<&6
read -n2 -p "Are you done now? [Y/n]" answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end.";;
*) echo "Goodbye";;
esac
// 文件描述符6用来保存STDIN的位置。然后脚本将STDIN重定向到一个文件。read命令的所有输入都来自重定向后的STDIN(也就是输入文件)。在读取了所有行之后,脚本会将STDIN重定向到文件描述符6,从而将STDIN恢复到原先的位置。该脚本用了另外一个read命令来测试STDIN是否恢复正常了。这次它会等待键盘的输入。
$: test15.sh
Are you done now? [Y/n]Y
Goodbye
创建读写文件描述符:
exec 3<> testfile
read line <&3
关闭文件描述符:
exec 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
$: test17.sh
./test17.sh: 行 6: 3: 错误的文件描述符
lsof
会列出整个Linux系统打开的所有文件描述符。lsof命令位于/usr/bin目录。
最常用的有
-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 2447 jiangyuan 0u CHR 136,0 0t0 3 /dev/pts/0
bash 2447 jiangyuan 1u CHR 136,0 0t0 3 /dev/pts/0
bash 2447 jiangyuan 2u CHR 136,0 0t0 3 /dev/pts/0
/* 演示多个替代性文件描述符 */
#!/bin/bash
# testing lsof with file descriptors
exec 3> test18file1
exec 6> test18file2
exec 7< testfile
/usr/bin/lsof -a -p $$ -d0,1,2,3,6,7
$: test18.sh
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
test18.sh 3162 jiangyuan 0u CHR 136,0 0t0 3 /dev/pts/0
test18.sh 3162 jiangyuan 1u CHR 136,0 0t0 3 /dev/pts/0
test18.sh 3162 jiangyuan 2u CHR 136,0 0t0 3 /dev/pts/0
test18.sh 3162 jiangyuan 3w REG 8,1 0 397905 /home/jiangyuan/shell/4/test18file1
test18.sh 3162 jiangyuan 6w REG 8,1 0 397921 /home/jiangyuan/shell/4/test18file2
test18.sh 3162 jiangyuan 7r REG 8,1 55 397712 /home/jiangyuan/shell/4/testfile
/dev/null
阻止命令输出,将STDERR重定向到一个叫作null文件的特殊文件。shell输出到null文件的任何数据都不会保存,全部都被丢掉了。
你重定向到该位置的任何数据都会被丢掉,不会显示。
这是避免出现错误消息,也无需保存它们的一个常用方法。
$: ls -al
> /dev/null
$: ls -al badfile test16 2> /dev/null
也可以在输入重定向中将/dev/null作为输入文件。
$: cat /dev/null > testfile
[
窍门] 这是清除日志文件的一个常用方法,因为日志文件必须时刻准备等待应用程序操作。
mktemp
创建临时文件到
/tmp目录下创建一个唯一的临时文件。 shell会创建这个文件,但不用默认的umask值。
一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然, root用户除外)。
要用mktemp命令在本地目录中创建一个临时文件,你只要指定一个文件名模板就行了。模板可以包含任意文本文件名,
在文件名末尾加上6个X就行了。
mktemp命令会用6个字符码替换这6个X,从而保证文件名在目录中是唯一的。
$: mktemp testing.XXXXXX
testing.97qCFL
$: mktemp testing.XXXXXX
testing.2TQqpp
/* 演示 */
#!/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
$: test19.sh
This script writes to temp file test19.IwhZwI
Done creating temp file. The contents are:
This is the first line
This is the second line.
This is the last line.
在/tmp 目录创建临时文件
-t选项会强制mktemp命令来在系统的临时目录来创建该文件。
$: mktemp -t tmp.XXXXXX
/tmp/tmp.MwA0wt
在/tmp 目录创建临时目录
-d选项告诉mktemp命令来创建一个临时目录而不是临时文件。
$: mktemp -d dir.XXXXXX
/tmp/dir.OAeZrU/
tee 记录消息
将输出同时发送到显示器和日志文件,相当于管道的一个T型接头。它将从STDIN过来的数据同时发往两处,一处是STDOUT,另一处是tee命令行所指定的文件名。
格式:
tee filename
默认情况下, tee命令会在每次使用时覆盖输出文件内容。
$: date | tee testfile
Sun Jun 18 17:37:27 CST 2017
$: cat testfile
Sun Jun 18 17:37:27 CST 2017
将数据追加到文件中,必须用-a选项。
/* 重定向实例(操作.csv文件到SQL数据库) */
#!/bin/bash
# read file and create INSERT statements for MySQL
outfile='members.sql'
IFS=','
while read lname fname address city state zip
do
cat >> $outfile << EOF
INSERT INTO members (lname,fname,address,city,state,zip) VALUES
('$lname', '$fname', '$address', '$city', '$state', '$zip');
EOF
done < ${1}
$: cat members.csv
Blum,Richard,123 Main St.,Chicago,IL,60601
Blum,Barbara,123 Main St.,Chicago,IL,60601
Bresnahan,Christine,456 Oak Ave.,Columbus,OH,43201
Bresnahan,Timothy,456 Oak Ave.,Columbus,OH,43201
$: ./test23 < members.csv
$: cat members.sql
INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('Blum',
'Richard', '123 Main St.', 'Chicago', 'IL', '60601');
INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('Blum',
'Barbara', '123 Main St.', 'Chicago', 'IL', '60601');
INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('Bresnahan',
'Christine', '456 Oak Ave.', 'Columbus', 'OH', '43201');
INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('Bresnahan',
'Timothy', '456 Oak Ave.', 'Columbus', 'OH', '43201');
>>第16章丶控制脚本
处理信号:
Linux系统和应用程序可以生成超过30个信号。最常见的信号:
1 SIGHUP 挂起进程
2 SIGINT 终止进程
3 SIGQUIT 停止进程
9 SIGKILL 无条件终止进程
15 SIGTERM 尽可能终止进程
17 SIGSTOP 无条件停止进程,但不是终止进程
18 SIGTSTP 停止或暂停进程,但不终止进程
19 SIGCONT 继续运行停止的进程
默认情况下, bash shell会忽略收到的任何SIGQUIT (3)和SIGTERM (5)信号(正因为这样,交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP (1)和SIGINT (2)信号。
中断进程:Ctrl+C组合键会生成SIGINT信号。
暂停进程:Ctrl+Z组合键会生成一个SIGTSTP信号,停止shell中运行的任何进程。
trap捕获信号
trap命令允许你来指定shell脚本要监看并从shell中拦截的Linux信号。如果脚本收到了trap命令中列出的信号,该信号不再由shell处理,而是交由本地处理。trap命令的格式是:
trap commands signals
/* trap命令演示 */
#!/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 10 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
$: test1.sh
This is a test script
Loop #1
Loop #2
^CSorry! I have trapped Ctrl+C
Loop #3
^CSorry! I have trapped Ctrl+C
Loop #4
Loop #5
^CSorry! I have trapped Ctrl+C
Loop #6
Loop #7
Loop #8
Loop #9
Loop #10
捕获脚本退出:
要捕获shell脚本的退出,只要在trap命令后加上EXIT信号就行。
/* 演示 */
#!/bin/bash
# Trapping the script exit
#
trap "echo Goodbye..." EXIT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
$: test2.sh
Loop #1
Loop #2
Loop #3
^CGoodbye...
修改或移除捕获:
在脚本中的不同位置进行不同的捕获处理,只需重新使用带有新选项的trap命令。
只需要在trap命令与希望恢复默认行为的信号列表之间加上两个破折号--就行了。
# Remove the trap
trap -- SIGINT
echo "I just removed the trap"
[
窍门] 也可以在trap命令后使用单破折号来恢复信号的默认行为。单破折号和双破折号都可以正常发挥作用。
移除信号捕获后,脚本按照默认行为来处理SIGINT信号,也就是终止脚本运行。
以后台模式运行脚本:
只要在命令后加个
&符就行了。
$: ./test4.sh &
[1] 3327
// [1] 作业号;3327 进程唯一pid
运行多个后台作业:
每次启动新作业时, Linux系统都会为其分配一个新的作业号和PID。通过ps命令,可以看到所有脚本处于运行状态。
在非控制台下运行脚本:
nohup
nohup命令运行了另外一个命令来阻断所有发送给该进程的SIGHUP信号。这会在退出终端会话时阻止进程退出。
$: nohup ./test1.sh &
nohup命令会自动将STDOUT和STDERR的消息重定向到一个名为
nohup.out的文件中。
在进程完成运行后,你可以查看nohup.out文件中的输出结果。
作业控制:
jobs
jobs命令允许查看shell当前正在处理的作业。
$: test4.sh > test4.out &
[1] 3360
$: ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1001 2447 2446 0 80 0 - 3259 wait pts/0 00:00:01 bash
0 S 1001 3360 2447 0 80 0 - 1651 wait pts/0 00:00:00 test4.sh
0 S 1001 3361 3360 0 80 0 - 1403 hrtime pts/0 00:00:00 sleep
0 R 1001 3362 2447 0 80 0 - 1528 - pts/0 00:00:00 ps
$: jobs -l
[1]+ 3360 运行中 test4.sh > test4.out &
jobs命令参数:
-l 列出进程的PID以及作业号
-n 只列出上次shell发出的通知后改变了状态的作业
-p 只列出作业的PID
-r 只列出运行中的作业
-s 只列出已停止的作业
重启停止的作业:
bg
要以后台模式重启一个作业,可用bg命令加上作业号。
$
bg 2
[2]+ ./test12.sh &
// 命令bg 2用于将第二个作业置于后台模式。
[
窍门] 最低值-20是最高优先级,而最高值19是最低优先级,这太容易记混了。只要记住那句俗语“好人难做”就行了。越是“好”或高的值,获得CPU时间的机会越低。
调整任务优先级:
nice
nice命令允许你设置命令启动时的调度优先级。要让命令以更低的优先级运行,只要用nice的-n命令行来指定新的优先级级别(
n可以省略)。
$: nice -10 ./test4.sh > test4.out &
[1] 4973
$: ps -p 4973 -o pid,ppid,ni,cmd
PID PPID NI CMD
4973 4721 10 /bin/bash ./test4.sh
renice
允许指定运行进程的PID来改变它的优先级。
$: ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 0 /bin/bash ./test11.sh
$: renice -n 10 -p 5055
5055: old priority 0, new priority 10
$: ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 10 /bin/bash ./test11.sh
只能对属于你的进程执行renice;
只能通过renice降低进程的优先级;
root用户可以通过renice来任意调整进程的优先级。
定时运行作业:
Linux系统提供了多个在预选时间运行脚本的方法: at命令和cron表。
at
at命令允许指定Linux系统何时运行脚本。 at命令会将作业提交到队列中,指定shell何时运行该作业。 at的守护进程atd会以后台模式运行,检查作业队列来运行作业。
atd守护进程会检查系统上的一个特殊目录(通常位于/var/spool/at)来获取用at命令提交的作业。默认情况下, atd守护进程会每60秒检查一下这个目录。有作业时, atd守护进程会检查作业设置运行的时间。如果时间跟当前时间匹配, atd守护进程就会运行此作业。
at命令的基本格式:
at [-f filename] time
// 用
-f参数来指定用于读取命令(脚本文件)的文件名。
// time参数指定了Linux系统何时运行该作业。如果你指定的时间已经错过, at命令会在第二天的那个时间运行指定的作业。
at命令能识别多种不同的时间格式。
标准的小时和分钟格式,比如10:15。
AM/PM指示符,比如10:15 PM。
特定可命名时间,比如now、 noon、 midnight或者teatime(4 PM)。
除了指定运行作业的时间,也可以通过不同的日期格式指定特定的日期。
标准日期格式,比如MMDDYY、 MM/DD/YY或DD.MM.YY。
文本日期,比如Jul 4或Dec 25,加不加年份均可。
你也可以指定时间增量。
当前时间+25 min
明天10:15 PM
10:15+7天
作业队列的字母排序越高,作业运行的优先级就越低(更高的nice值)。
如果想以更高优先级运行作业, 可以用
-q参数指定不同的队列字母。
获取作业的输出:
在使用at命令时,最好在脚本中对STDOUT和STDERR进行重定向来获取输出。
echo "string" >> test13b.out
-M选项来屏蔽作业产生的输出信息。
列出等待的作业:
atq
atq命令可以查看系统中有哪些作业在等待。
$: at -M -f test13b.sh 13:30
job 19 at 2015-07-14 13:30
$: at -M -f test13b.sh now
job 20 at 2015-07-14 13:03
$: atq
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
atrm
删除等待中的作业,格式:
atrm 作业号
安排需要定期执行的脚本:
Linux系统使用
cron程序来安排要定期执行的作业。 cron程序会在后台运行并检查一个特殊的表(被称作cron时间表) ,以获知已安排执行的作业。
cron时间表采用一种特别的格式来指定作业何时运行。其格式如下:
min hour dayofmonth month dayofweek command
例如,如果想在每天的10:15运行一个命令,可以用cron时间表条目:
15 10 * * * command
在dayofmonth、 month以及dayofweek字段中使用了通配符,表明cron会在每个月每天的10:15执行该命令。要指定在每周一4:15 PM运行的命令,可以用下面的条目:
15 16 * * 1 command
在每个月的第一天中午12点执行命令。可以用下面的格式:
00 12 1 * * command
[
窍门] 如何设置一个在每个月的最后一天执行的命令,因为你无法设置dayofmonth的值来涵盖所有的月份。这个问题困扰着Linux和Unix程序员,也激发了不少解决办法。常用的方法是加一条使用date命令的if-then语句来检查明天的日期是不是01:
00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command
它会在每天中午12点来检查是不是当月的最后一天,如果是, cron将会运行该命令。
crontab
构建cron时间表。
默认情况下,用户的cron时间表文件并不存在。要为cron时间表添加条目,可以用-e选项。(测试,暂未达效果)
在添加条目时, crontab命令会启用一个文本编辑器。
浏览cron目录:
有4个基本目录: hourly、 daily、 monthly和weekly。
$:
ls /etc/cron.*ly
/etc/cron.daily:
0anacron bsdmainutils man-db popularity-contest
apport dpkg mlocate standard
apt logrotate passwd update-notifier-common
/etc/cron.hourly:
/etc/cron.monthly:
0anacron
/etc/cron.weekly:
0anacron apt-xapian-index man-db
如果脚本需要每天运行一次,只要将脚本复制到
daily目录
, cron就会每天执行它。
anacron程序
anacron程序只会处理位于cron目录的程序,比如/etc/cron.monthly。它用时间戳来决定作业是否在正确的计划间隔内运行了。每个cron目录都有个时间戳文件,该文件位于/var/spool/anacron。
$:
sudo cat /var/spool/anacron/cron.monthly
20170529
anacron程序使用自己的时间表(通常位于/etc/anacrontab)来检查作业目录。
$:
sudo cat /etc/anacrontab
# These replace cron's entries
1 5 cron.daily nice run-parts --report /etc/cron.daily
7 10 cron.weekly nice run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly nice run-parts --report /etc/cron.monthly
anacron时间表的基本格式和cron时间表略有不同:
period delay identifier command
period条目定义了作业多久运行一次,以天为单位。 anacron程序用此条目来检查作业的时间戳文件。 delay条目会指定系统启动后anacron程序需要等待多少分钟再开始运行错过的脚本。command条目包含了run-parts程序和一个cron脚本目录名。 run-parts程序负责运行目录中传给它的任何脚本。
anacron不会运行位于/etc/cron.hourly的脚本。这是因为anacron程序不会处理执行时间需求小于一天的脚本。
登录shell会从5个不同的启动文件里读取命令:
~/.bash_profile
~/.bashrc
~/.bash_login
~/.profile
/etc/profile
因此,应该将需要在登录时运行的脚本放在上面第一个文件中。
每次启动一个新shell时, bash shell都会运行
~/.bashrc文件。
2017.06.18
第14章、15章、16章完...未完待续!