ABS_Guide_Cn_1
来源:互联网 发布:郑州淘宝曹帅兵 编辑:程序博客网 时间:2024/05/20 18:45
高级Bash脚本编程指南
一本深入学习shell脚本艺术的书籍
版本 6.0.35
2009年6月29日
作者Mendel Cooper
该教程假设你以前没有脚本知识或者编程知识,但是如果你具备该知识的话很快就能达到中级或者高级水平。整个书中涵盖了UNIX®的特有智慧和知识。你可以把它当成自学教材、手册或者shell脚本技术的参考资料。练习题和例子中的注释能够引起读者的积极性,前提是真正学习脚本的唯一途径就是写脚本。
本书也适合作为教材来讲解一般的介绍编程概念。
第一部分:介绍
Shell是一种命令解释器。它不仅仅是操作系统内核和用户之间的连接器,更是功能强大的编程语言。一个shell程序,被叫做一个脚本,是一个很容易使用的工具,可以通过系统调用、工具、实用程序或者编译过的二进制连接在一起。几乎所有的UNIX指令、实用程序和工具都可以通过shell脚本调用。如果这些还不够,那么shell本身类似于testing和loop循环结构的命令可以增加脚本的强力支持及灵活性。Shell脚本更加适合管理系统进程以及其他重复工作的进程,而不需要那些多余的完全成熟的结构化变成语言。
目录
1. Shell Programming!
2. Starting Off With a Sha-Bang
第一章 shell 编程
对于任何想精通系统管理的人来说,掌握shell脚本知识是必不可少的,即使他们之前没有真正的写过脚本。想想Linux机器的启动过程,它是通过执行/etc/rc.d 目录下的脚本去恢复系统配置及建立相关服务的。详细的了解这些启动脚本对于分析系统是非常重要的,同时还可能需要修改它。
掌握脚本并不难,因为这些脚本都可以分割成小的片段去学习,并且这些小的片段又是相对独立的操作[1]。Shell语法很简单也很直观,类似于把一些实用程序在命令行连接起来调用,而且只用到很少的规则。绝大多少短小的脚本第一次就可以很好的运行,即使调试一个比较长的脚本也是比较直观的。
在20世纪70年代, BASIC语言适合在早期的微机上编写程序。10年后,Bash脚本作为Linux或者UNIX的基础知识被用在更强大的机器上。
Shell脚本在复杂的应用程序模型设计的简捷方法。项目开发的第一阶段,使用脚本完成部分功通常都是很有用的。使用这种方法,在使用C,C++,Java,Perl或者Python语言完成程序编码之前,通过对应用程序结构测试、演示就能够反映程序的主要缺陷。
Shell脚本遵循经典的UNIX体系,把复杂的项目拆分成简单的单元,并且组件和程序之间相互连接。序贯观点认为这种方法比较好,至少比使用新一代语言更加完美的解决问题,例如Perl语言,就是尝试所有人用之去做所有事情,但是代价就是强迫你使用这种语言思考解决问题的方法。
根据Herbert Mayer的理论,“一种有用的语言需要数组、指针、以及泛型结构来创建数据结构”。根据这个标准,Shell脚本就不那么“有用”,或许不能……
我们将开始用Bash,Bourne-Again Shell的首字母缩写组合,也是Stephen Bourne的经典Bourne Shell。Bash已经成了主流UNIX的shell脚本,本书绝大部分原则涵盖了其他shell,比如Korn Shell,Bash也包含了一些Korn Shell的特性,同时也包含了一些C Shell的变种。(注意C Shell编程由于存在内在的问题不被推荐,这在1993年10月已经由Tom Christiansen 在网络上公告了)
第二章 与Sha-Bang一起出发
一个简单的例子,一个脚本无非是将一些系统命令列在一个文件中。最起码的用处就是,在调用特殊的有顺序的命令时,可以节省工作。
例 2-1 清除:一个清除/var/log 目录下日志文件的脚本
1 # Cleanup
2 # Run as root, of course.
3
4 cd /var/log
5 cat /dev/null > messages
6 cat /dev/null > wtmp
7 echo "Logs cleaned up."
这没有什么异常的,只不过是在控制台命令行或者终端窗口一个接一个的调用一些命令,好处是不用每次都去重新敲命令。这个脚本就是一个程序——一个工具——能够很容易修改或者定制。
例 2-2 清除:一个改进的清除脚本
1 #!/bin/bash
2 # Proper header for a Bash script.
3
4 # Cleanup, version 2
5
6 # Run as root, of course.
7 # Insert code here to print error message and exit if not root.
8
9 LOG_DIR=/var/log
10 # Variables are better than hard-coded values.
11 cd $LOG_DIR
12
13 cat /dev/null > messages
14 cat /dev/null > wtmp
15
16
17 echo "Logs cleaned up."
18
19 exit # The right and proper method of "exiting" from a script.
现在让我们开始看一个真正的脚本,这样我们能够做更多事情……
例 2-3 清除:一个增强的和普遍的清除脚本的版本
1 #!/bin/bash
2 # Cleanup, version 3
3
4 # Warning:
5 # -------
6 # This script uses quite a number of features that will be explained
7 #+ later on.
8 # By the time you've finished the first half of the book,
9 #+ there should be nothing mysterious about it.
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0 # Only users with $UID 0 have root privileges.
15 LINES=50 # Default number of lines saved.
16 E_XCD=86 # Can't change directory?
17 E_NOTROOT=87 # Non-root exit error.
18
19
20 # Run as root, of course.
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23 echo "Must be root to run this script."
24 exit $E_NOTROOT
25 fi
26
27 if [ -n "$1" ]
28 # Test whether command-line argument is present (non-empty).
29 then
30 lines=$1
31 else
32 lines=$LINES # Default, if not specified on command-line.
33 fi
34
35
36 # Stephane Chazelas suggests the following,
37 #+ as a better way of checking command-line arguments,
38 #+ but this is still a bit advanced for this stage of the tutorial.
39 #
40 # E_WRONGARGS=85 # Non-numerical argument (bad argument format).
41 #
42 # case “$1” in
43 # “” ) lines=50
44 # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 # * ) lines=$1;;
46 # esac
47 #
48 #* Skip ahead to "Loops" chapter to decipher all this.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ] # or if [ "$PWD" != "$LOG_DIR" ]
54 # Not in /var/log?
55 then
56 echo "Can't change to $LOG_DIR."
57 exit $E_XCD
58 fi # Doublecheck if in right directory before messing with log file.
59
60 # Far more efficient is:
61 #
62 # cd /var/log || {
63 # echo "Cannot change to necessary directory." >&2
64 # exit $E_XCD;
65 # }
66
67
68
69
70 tail -n $lines messages > mesg.temp # Save last section of message log file.
71 mv mesg.temp messages # Becomes new log directory.
72
73
74 # cat /dev/null > messages
75 #* No longer needed, as the above method is safer.
76
77 cat /dev/null > wtmp # ': > wtmp' and '> wtmp' have the same effect.
78 echo "Logs cleaned up."
79
80 exit 0
81 # A zero return value from the script upon exit indicates success
82 #+ to the shell.
因为你也不希望清除所有的系统日志,因此这个改进的脚本保留了最后的部分完整日志。你可以不断的发现新的方法完善上面的脚本,提高效力。
***
脚本开头的#!符号告诉系统这个文件的命令需要命令解释器来解释。#!实际上就是1个2字节的幻数,表示这一个特殊的标记,表明该文件类型,或者表示本例是可知性shell脚本(键入man magic 来获取详更详细的丰富的主题)。紧跟这sha-bang的是路径名,这个路径在脚本中是解释命令是否是一个shell,或者是一个编程语言,或者是实用程序。然后命令解释器从第一行(sha-bang下面的一行)开始执行脚本中的命令,执行过程中忽略注释。
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/perl
4 #!/usr/bin/tcl
5 #!/bin/sed -f
6 #!/usr/awk -f
2.1 调用脚本
脚本写好后,可以通过 sh 脚本名 或者 bash 脚本名 来调用它(不推荐使用 sh <脚本名 调用,因为不能有效的读取脚本中的标准输入)更有效的方法是直接赋予脚本本身可执行权限。
或者:
chmod 555 scriptname (赋予任何人 读/执行 权限)
或者:
chmod +rx scriptname(赋予任何人 读/执行 权限)
chmod u+rx scriptname (赋予脚本自身读/执行 权限)
当脚本是可执行的,就可以通过 ./脚本名字 来测试它。若果脚本是以“sha-bang”行开始的,调用脚本需要正确的命令解释器来运行。
最后一步,测试和调试之后你可能想把它移到/usr/local/bin目录(当然,移动需要以root用户才可以),这样的话脚本就可以在系统中被所有用户执行,然后用户就可以通过在命令行简单键入脚本名 [回车] 来调用脚本。
2.2 初步练习
1. 系统管理员常常需要写脚本使得系统一些公共进程能够自动执行,举一些例子说明这类脚本的用途。
2. 写一个脚本调用一些程序显示出系统的日期和时间,列出所有登录过的用户,显示出系统的正常运行时间,然后脚本把这些信息保存到日志文件。
第二部分 基础知识
目录
3. 特殊字符
4. 变量和参数介绍
5. 引用
6. 退出和退出状态
7. 测试
8. 操作符相关主题
第三章 特殊字符
什么是特殊字符?如果按照字面意思是一个词转意了,然后就成了特殊字符。
在脚本或其他地方找特殊字符
#
注释,以 # 符号开始的行是注释(#!是例外),不会被执行
1 # This line is a comment.
注释也可以跟在命令结尾后面
1 echo "A comment will follow." # 注释.
2 # ^ 注意 #前面有空格
注释也可以在行的空格后面
1 # A tab precedes this comment.
注释也可能嵌在管道符里面
1 initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '/n' |/
2 # Delete lines containing ‘#’ 注释字符
3 sed –e `s//.//. /g’ –e ‘s/_/_ /g’` )
4 # Excerpted from life.sh script
同一行上命令不能跟在注释后面,没有办法结束注释接着开始新的命令,要开始新命令就需要另起一行。
当然在echo语句中,引用或者转码中的“#”不能作为注释的。同样的,#号出现在特定的参数替换结构中和数字常量表达式中,也不作为注释的。
1 echo "The # 不是注释"
2 echo 'The # 不是注释'
3 echo The /# 不是注释
4 echo The # 注释
5
6 echo ${PATH#*:} # 参数替换,不是注释
7 echo $(( 2#101011 )) # 基数转换,不是注释
8
9 # Thanks, S.C.
标准的应用字符和转意字符(“ ‘ /)也可以转意“#”号,固定的匹配操作也可以用“#”号。
;
命令分隔符【分号】。允许两条或者多条命令在同一行。
1 echo hello; echo there
2
3
4 if [ -x "$filename" ]; then # 注意分号后面的空格
5 #+ ^^
6 echo "File $filename exists."; cp $filename $filename.bak
7 else # ^^
8 echo "File $filename not found."; touch $filename
9 fi; echo "File test complete."
注意“;”有时候需要被转意。
;;
终止选择【双分号】
1 case “$variable” in
2 abc) echo “/$variable = abc” ;;
3 xyz) echo “/$variable = xyz” ;;
4 esac
;;&,;&
终止选择【第4版以后的Bash】
.
“.”命令【句号】,相当于来源【参考例子14-22】,这是一个bash内建函数。
“.”作为文件名的组成部分,是隐藏文件的前缀,当执行ls命令时不能显示出文件名。
[root@localhost tmp]# touch .hidden-file
[root@localhost tmp]# ls -l
total 100
-rw------- 1 root root 9248 Jul 13 14:34 grub-install.img.fV3362
-rw------- 1 root root 0 Jul 13 14:34 grub-install.log.vE3363
-rw-r--r-- 1 root root 73406 Aug 18 2008 netconfig-0.8.24-1.2.2.1.i386.rpm
[root@localhost tmp]#
[root@localhost tmp]# ls -al
total 136
drwxrwxrwx 4 root root 4096 Jul 14 16:35 .
drwxr-xr-x 25 root root 4096 Jul 9 11:57 ..
drwxrwxrwt 2 root root 4096 Jan 9 2002 .font-unix
-rw------- 1 root root 9248 Jul 13 14:34 grub-install.img.fV3362
-rw------- 1 root root 0 Jul 13 14:34 grub-install.log.vE3363
-rw-r--r-- 1 root root 0 Jul 14 16:35 .hidden-file
drwxrwxrwt 2 root root 4096 Jan 9 2002 .ICE-unix
-rw-r--r-- 1 root root 73406 Aug 18 2008 netconfig-0.8.24-1.2.2.1.i386.rpm
[root@localhost tmp]#
作为目录名,“.”表示当前目录,“..”则表示上一级目录
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
“.”经常出现在移动命令中,由于这个原因,“.”表示的是当前目录。
bash$ cp /home/zozo/current_work/junk/* .
把junk目录下所有文件复制到$PWD目录(即当前目录)。
. 字符匹配。“.”作为正则表达式的一部分,匹配单个字符。
“
部分引用【双引号】。“STRING”保留了STRING中大多数特殊字符,详细见第5章。
‘
全部引用【单引号】。‘STRING’保留了STRING中的所有特殊字符,‘STRING’的引用是“STRING”的较强形式。参见第5章。
,
逗号操作符。逗号操作符连接着连续的算术操作,所有的都是可以计算的,只有最后一个被返回。
1 let “t2 = ((a=9,15/3()” #set “a =
2 9” and “t2 = 15 / 3”
逗号操作符也可以连接字符串。
1 for file in /{,usr/}bin/*calc
2 # ^ 查找所有以“calc”结尾的可执行文件
3 #+ 在 /bin 目录和 /usr/bin 目录
4 do
5 if [ -x “$file” ]
6 then
7 echo $file
8 fi
9 done
10 下面是查找结果
11 # /bin/ipcalc
12 # /usr/bin/kcalc
13 # /usr/bin/oidcalc
14 # /usr/bin/oocalc
15
16
17 # Thank you, Rory Winston, for pointing this out.
” ’
参数替代小写字母(第4版bash中增加)。
/
换码【反斜杠】。单字符引用
/x 引用x 字符,它引用x 等同于‘X’。“/”常常被“ 和‘ 引用,所以这些表达简洁。
参考第5章,更详细的说明反斜杠。
/
文件名路径分隔符【向前斜线】。分隔开文件名名字中的各级路径(如/home/bozo/projects/Makefile)。
这也是算数运算中的除法操作。
`
命令替换。`command` 结构把命令正确的输出给变量。这也是通常所说的backquotes或者backticks。
:
空命令【冒号】。这个shell命令等同于“NOP”(没有操作,一个空操作)。它通常被认为是shell内建的“真”的同义词,“:”指令是bash内部的函数,退出状态是true(即0)。
1 :
2 Echo $? # 0
死循环,例如:
1 while :
2 do
3 operation-1
4 operation-2
5 …
6 operation –n
7 done
8
9 # 与下面相同
10 # while true
11 # do
12 …
13 done
在if/then 例子中占位符
1 if condition
2 then : # Do nothing and branch ahead
3 else # or else …
4 take-some-action
5 fi
在预期的二元操作中提供占位符。
1 : ${username=`whoami `}
2 # ${username=`whoami `} 如果不以:开始就报错
3 # 除非“username”是一个命令或者内建函数 …
用替换参数计算变量中的字符串(例 9-16)。
1 :${HOSTNAME} ${USER?} ${MAIL?}
2 # Prints errot message
3 #+ if one or more essential environmental variables not set.
变量扩展/参数替换
在和重定向符号>相结合时,在不改变文件权限的情况下清空文件内容,如果文件不存在则创建文件。
1 : > data.xxx # “data.xxx” 文件变成空
2
3 # 效果同 cat /dev/null > data.xxx
# 然而这不能 产生一个新进程,因为“:”是内建函数
也可以参考例 15-15
在和重定向操作符>>连接时,对已有的目标文件不起作用(: >> target_file),如果文件不存在则创建文件。
这使用语规则的文件,不包含管道符、连接符和某些特殊的文件。
也可能用在注释行的开始,尽管这中情况不被推荐。使用#注释,不对该行剩余内容错误检测,所以注释内容可以写任何东西。然而,使用“:”情况则不同:
1 : This is a comment that generates an error, ( if [ 4x –eq 3 ] ).
“:”在/etc/passwd中和在$PATH变量中是字段分隔符。
Bash$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/sbin:/usr/games
!
取反(或否定)。!操作反置了适当命令的退出状态(参考例6-2),意思刚好和实验的意思相反。举个例子,它能够将等于(=)变成不等于(!=),!是Bash脚本的一个关键字。
在不同的环境中,!还用在变量引用中。
在命令行中,!还能够调用历史记录,注意,在脚本中历史记录是隐藏的。
*
通用字符【星号】。*作为通配符用在文件名的替换中,替换给定目录的所有文件明。
Bash$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh
在正则表达式中,*可以替代任何数字字符(或者0)。
*
算数运算。在算数运算中,*表示乘法运算。
双星代表幂运算或者在文件名替换中代表补充的文件名。
?
假设操作。在某些表达式中,?代表假设条件。
在双括号结构中,?可以作为C语言风格中的三元操作元素,如 ?:
1 (( vr0 = var1<98?9:21 ))
2 # ^ ^
3
4 # if [ “$var1” –lt 98 ]
5 # then
6 # var0=9
7 # else
8 # var0=21
9 # fi
在 参数替换中,?表示假设变量是否是设定的。
?
通配符。在文件名替换中,?代表一个单个的字符,也代表正则表达式中的一个字符。
$
变量替换。(属于变量)
1 var1=5
2 var2=23skidoo
3
4 echo $var1 # 5
5 echo $var2 # 23skidoo
一个变量名字的$前缀表示跟着的是变量的值。
$
行尾。在正则表达式中,文本的末尾常常有个$符号。
${}
替换参数。
$*,$@
定位参数。
$?
退出变量。$?变量保持一个命令、一个函数或者脚本本身处于退出状态。
$$
进程序号。$$变量保存了一个进程出现在脚本中的序号。
()
命令组。
1 (a=hello; echo $a)
括号中的命令列表作为subshell运行。
括号中的变量,只在内subshell中运行,对括号外的脚本程序不起作用,父进程,也就是脚本本身不能读取子进程中创建的变量,也就是在subshell中创建的变量。
1 a=123
2 ( a=321; )
3
4 echo "a = $a" # a = 123
5 # "a" within parentheses acts like a local variable.
数组初始化。
1 Array=(element1 element2 element3)
{xxx,yyy,zzz,…}
大括号。
1 echo /"{These,words,are,quoted}/" # " 前缀和后缀
2 # 输出的结果如 "These" "words" "are" "quoted"
3
4
5 cat {file1,file2,file3} > combined_file
6 # 将file1, file2, file3合并成combined_file.
7
8 cp file22.{txt,backup}
9 # 复制"file22.txt" 到"file22.backup"
一个命令可能对大括号中以逗号分开的文件列表规格起作用[3]。文件名扩展(文件名替换)适用于大括号中的文件规格。
大括号中不允许有空格,除非括号中的空格是引用或者转码。
echo {file1,file2}/ :{/ A," B",' C'}
file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C #上面语句的结果
{a..z}
大括号扩展。
1 echo {a..z} # 结果是 a b c d e f g h i j k l m n o p q r s t u v w x y z
2 # 返回 a 到 z 之间的字母
3
4 echo {0..3} # 0 1 2 3
5 # 返回 0 到 3 之间的数字
{}
代码块【花括号】。也被当作构造函数组,事实上创建了一个匿名函数(一个没有名字的函数)。然而,与“标准”函数不同的是,代码块中的变量对于脚本来说还是可见的。
bash$ { local a;
a=123; }
bash: local: can only be used in a function
1 a=123
2 { a=321; }
3 echo "a = $a" # a = 321 结果是代码组里面的
4
5 # Thanks, S.C.
大括号中的代码输入/输出重定向。
例 3-1 代码 I/O 重定向
1 #!/bin/bash
2 # 从 /etc/fstab中读行
3
4 File=/etc/fstab
5
6 {
7 read line1
8 read line2
9 } < $File
10
11 echo "First line in $File is:"
12 echo "$line1"
13 echo
14 echo "Second line in $File is:"
15 echo "$line2"
16
17 exit 0
18
19 # 现在考虑一下,你怎样分析单独的每一行?
20 # 提示: 可以使用 awk, 或者其他方式 . . .
21 # . . . Hans-Joerg Diers 建议使用bash内建函数 "set"
例 3-2 将代码块的输出结果保存在一个文件中
1 #!/bin/bash
2 # rpm-check.sh
3
4 # 查寻一个rpm文件描述、清单
5 #+ 无论这个包能不能安装
6 # 将输出结果保存在一个文件中.
7 #
8 # 这个脚本距离说明了代码块
9
10 SUCCESS=0
11 E_NOARGS=65
12
13 if [ -z "$1" ]
14 then
15 echo "Usage: `basename $0` rpm-file"
16 exit $E_NOARGS
17 fi
18
19 { # Begin code block.
20 echo
21 echo "Archive Description:"
22 rpm -qpi $1 # Query description.
23 echo
24 echo "Archive Listing:"
25 rpm -qpl $1 # Query listing.
26 echo
27 rpm -i --test $1 # Query whether rpm file can be installed.
28 if [ "$?" -eq $SUCCESS ]
29 then
30 echo "$1 can be installed."
31 else
32 echo "$1 cannot be installed."
33 fi
34 echo # End code block.
35 } > "$1.test" # Redirects output of everything in block to file.
36
37 echo "Results of rpm test in file $1.test"
38
39 # See rpm man page for explanation of options.
40
41 exit 0
和()命令不同的是{}封装的代码常常不能创建subshell。
{}
文本占位标志。{}用在xargs –i(替换字符串操作)之后表示文本占位。{}在输出文本中表示占位。
1 ls . | xargs -i -t cp ./{} $1
2 # ^^ ^^
3
4 # From "ex42.sh" (copydir.sh) example.
- ABS_Guide_Cn_1
- Ajax Post Get 混合传值 有概念不对的请指正~!
- 热烈祝贺顶嵌0907嵌入式实训班开学典礼
- 全面解读Objective-C语言及Cocoa特性——《Objective-C基础教程》
- C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(四)实现2D人物动画①
- 备份Ubuntu操作系统
- ABS_Guide_Cn_1
- 或曰之三省吾身
- locate: warning: database /var/lib/slocate/slocate.db' is more than 8 days old
- 【转】搞定了YAFFS IMAGE烧写在新内核linux2.6.23.9上
- C#中英文语音合成与中文语音识别技术
- 【参考】+【原创】uboot 烧写 yaffs的补丁+补充
- 在SAP 菜单项上显示事务码
- 正确设置wince音量
- hdu 1267 下沙的沙子有几粒?