第 8 章 操作符和相关的主题
8.1 操作符
Example 8-1 最大公约数
#!/bin/bash
ARGS=2
E_BADARGS=65
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` first_number second_number"
exit $BASARGS
fi
gcd(){
a=$1
b=$2
while [ $b -ne 0 ]
do
let "c=$a % $b"
a=$b
b=$c
done
}
gcd $1 $2
echo "GCD of $1 and $2 = $a"
echo
Example 8-2 使用算术操作符
#!/bin/bash
n=1
let "n=$n+1"
echo "$n"
let "n=n+1"
echo "$n"
: $((n = $n + 1))
echo "$n"
(( n=n + 1 ))
echo "$n"
n=$(($n+1))
echo "$n"
: $[n=$n+1]
echo "$n"
let "n++"
echo "$n"
exit 0
~
~
#!/bin/bash
a=24
b=47
if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
then
echo "Test #1 succeeds"
else
echo "Test #1 fails"
fi
if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
then
echo "Test #2 succeeds."
else
echo "Test #2 fails"
fi
# -a 和-o 选项提供了 一种可选的混合 test 方法.
if [ "$a" -eq 24 -a "$b" -eq 47 ]
then
echo "Test #3 succeeds."
else
echo "Test #3 fails"
fi
if [ "$a" -eq 98 -o "$b" -eq 47 ]
then
echo "Test #4 succeeds."
else
echo "Test #4 fails"
fi
a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
echo "Test #5 succeeds."
else
echo "Test #5 fails"
fi
第 9 章
9.2 操作字符串
#字符串长度
${#string}
expr length $string
expr "$string" : '.*'
Example 9-10 在一个文本文件的段间插入空行
#!/bin/bash
MINLEN=45 # 可能需要修改这个值.
while read line # 对于需要多行输入的文件基本都是这个样子
do
echo "$line" # 输出 line.
len=${#line}
if [ "$len" -lt "$MINLEN" ]
then echo # 在短行后边添加一个空行
fi
done
exit 0
从字符串开始的位置匹配子串的长度
expr match "$string" '$substring'
$substring 是一个正则表达式
expr "$string" : '$substring'
$substring 是一个正则表达式
#!/bin/bash
stringZ="abcABC123ABCabc"
echo `expr match "$stringZ" 'abc[A-Z]*.2'`
echo `expr "$stringZ" : 'abc[A-Z]*.2'`
索引
expr index $string $substring #匹配到子串的第一个字符的位置.
echo `expr index "$stringZ" C12`
提取子串
${string:position}#在 string 中从位置$position 开始提取子串
echo ${stringZ:0}
echo ${stringZ:7}
${string:position:length} #在 string 中从位置$position 开始提取$length 长度的子串.
echo `expr substr $stringZ 1 2`
echo `expr substr $stringZ 4 3`
反向提取子串
echo ${stringZ:-4}
expr match "$string" '\($substring\)' #从$string 的开始位置提取$substring,$substring 是一个正则表达式.
expr "$string" : '\($substring\)' #从$string 的开始位置提取$substring,$substring 是一个正则表达式.
stringZ=abcABC123ABCabc
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'`
echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'`
子串削除
${string#substring} #从$string 的左边截掉第一个匹配的$substring
${string##substring} #从$string 的左边截掉最后一个个匹配的$substring
stringZ=abcABC123ABCabc
echo ${stringZ#a*C} #截掉'a'和'C'之间最近的匹配.
echo ${stringZ##a*C} # 截掉'a'和'C'之间最远的匹配.
${string%substring} #从$string 的右边截掉第一个匹配的$substring
${string%%substring} #从$string 的右边截掉最后一个匹配的$substring
echo ${stringZ%b*c}
echo ${stringZ%%b*c}
Example 9-11 利用修改文件名,来转换图片格式
#!/bin/bash
# cvt.sh:
# 把一个目录下的所有sh格式的文件都转换为"txt"格式的
图片文件.
OPERATION=mv
SUFFIX=txt # 新的文件名后缀
if [ -n "$1" ]
then
directory=$1 # 如果目录名作为第1个参数给出...
else
directory=`pwd` # 否则使用当前的工作目录.
fi
for file in $directory/* # Filename globbing.
do
filename=${file%.sh} #从$string 的右边截掉第一个匹配的$substring
字符串).
$OPERATION $file "$filename.$SUFFIX"
# 转换为新的文件名.
rm -f $file # 转换完毕后删除原有的文件.
echo "$filename.$SUFFIX" # 从stdout输出反馈.
done
exit 0
子串替换
${string/substring/replacement} #使用$replacement 来替换第一个匹配的$substring.
${string//substring/replacement} #使用$replacement 来替换所有匹配的$substring.
${string/#substring/replacement} #如果$substring 匹配$string 的开头部分,那么就用$replacement 来替换$substring.
${string/%substring/replacement} #如果$substring 匹配$string 的结尾部分,那么就用$replacement 来替换$substring.
#!/bin/bash
stringZ=abcABC123ABCabc
echo ${stringZ/abc/XYZ}# 用'xyz'来替换第一个匹配的'abc'
echo ${stringZ//abc/XYZ}# 用'xyz'来替换所有匹配的'abc'.
echo ${stringZ/#abc/XYZ}# 用'XYZ'替换开头的'abc'
echo ${stringZ/%abc/XYZ}# 用'XYZ'替换结尾的'abc
9.2.1 使用 awk 来操作字符串
#!/bin/bash
s=23skidoo1
echo ${s:2:4}
echo $s | awk '{print substr($s,3,4)}'
exit 0
9.3 参数替换
操作和扩展变量
${parameter}和$parameter相同
${parameter:-default}当变量未定义或者值为空时,返回值为default的内容,否则返回变量的值
#!/bin/bash
v1=
echo "v1=${v1:-`ls`}"
echo
echo "v2=${v2:-`ls`}"
${parameter:=default}若变量未定义或者值为空时,在返回default的值的同时将default赋值给value
${parameter:+default}若变量已赋值的话,其值才用default替换,否则不进行任何替换
#!/bin/bash
a=${param1:+xyz}
echo "a = $a"
param2=
a=${param2:+xyz}
echo "a = $a"
param3=123
a=${param3:+xyz}
echo "a = $a"
变量长度
${#var}字符串长度($var 的字符数量)
.对于一个数组,${#array}是数组中第一个元素的长度.
${#*}和${#@}将给出位置参数的个数.
9.4 指定类型的变量:declare 或者 typeset
declare 或者 typeset 内建命令(这两个命令是完全一样的)允许指定变量的具体类型.
-r 只读
declare -r var1
readonly var1
-i 整形
-a 数组
-f 函数
declare -f
如果使用 declare -f 而不带参数的话,将会列出这个脚本中之前定义的所有函数.
declare -f function_name
如果使用 declare -f function_name 这种形式的话,将只会列出这个函数的名字
注意:使用 declare 内建命令将会限制变量的作用域.
#!/bin/bash
foo(){
FOO="bar"
}
bar(){
foo
echo $FOO
}
bar
#!/bin/bash
foo(){
declare FOO="bar"
}
bar(){
foo
echo $FOO
}
bar #输出为空
9.5 变量的间接引用
#!/bin/bash
a=letterofalphabet
letterofalphabet=z
echo
echo "a = $a" #直接引用
eval a=\$$a #间接引用
echo "now a = $a"
9.7 双圆括号结构
((...))与 let 命令很像,允许算术扩展和赋值.举个简单的例子 a=$(( 5 + 3 )),将把 a 设为"5+3"或者 8.
然而,双圆括号也是一种在 Bash 中允许使用 C 风格的变量处理的机制.
第 11 章 内部命令与内建
内部命令实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留在系统内存中。内部命令是写在bashy源码里面的,其执行速度比外部命令快,因为解析内部命令shell不需要创建子进程。比如:exit,history,cd,echo等。
外部命令是linux系统中的实用程序部分,因为实用程序的功能通常都比较强大,所以其包含的程序量也会很大,在系统加载时并不随系统一起被加载到内存中,而是在需要时才将其调用内存。通常外部命令的实体并不包含在shell中,但是其命令执行过程是由shell程序控制的。shell程序管理外部命令执行的路径查找、加载存放,并控制命令的执行。外部命令是在bash之外额外安装的,通常放在/bin,/usr/bin,/sbin,/usr/sbin......等等。可通过“echo $PATH”命令查看外部命令的存储路径,比如:ls、vi等。
一般的,脚本中的内建命令在执行时将不会 fork 出一个子进程.但是脚本中的外部或过滤命令通常会 fork 一个子进程.
11.1 作业控制命令
jobs
在后台列出所有正在运行的作业,给出作业号.
fg,bg
fg 命令可以把一个在后台运行的作业放到前台来运行.而 bg 命令将会重新启动一个挂起的
作业,并且在后台运行它.如果使用 fg 或者 bg 命令的时候没指定作业号,那么默认将对当前
正在运行的作业做操作.
wait
停止脚本的运行,直到后台运行的所有作业都结束为止,或者直到指定作业号或进程号为选
项的作业结束为止.
你可以使用 wait 命令来防止在后台作业没完成(这会产生一个孤儿进程)之前退出脚本.
孤儿进程与僵尸进程[总结]
Example 11-24 在继续处理之前,等待一个进程的结束
#!/bin/bash
ROOT_ID=0
E_NOTROOT=65
E_NOPARAMS=66
if [ "$UID" -ne "$ROOT_ID" ]
then
echo "Must be root to run this script"
fi
if [ -z "$1" ]
then
echo "Usage:`basename $0` find-string"
exit $E_NOPARAMS
fi
echo "update 'locate' database"
updatedb &
wait # 将不会继续向下运行,除非 'updatedb'命令执行完成.
locate $1
exit 0
如果没有'wait'命令的话,而且在比较糟的情况下,脚本可能在'updatedb'命令还在运行的时候退出,这将会导致'updatedb'成为一个孤儿进程.
Example 11-25 一个结束自身的脚本.
#!/bin/bash
kill $$
echo "将不会输出这一行"
exit 0
143 = 128 + 15
kill -l 将列出所有信号. kill -9 是"必杀"命令,这个命令将会结束哪些顽固的不想被 kill 掉的进程.有时候 kill -15 也可以干这个活.一个僵尸进程不能被登陆的用户 kill 掉, -- 因为你不能杀掉一些已经死了的东西 -- ,但是 init 进程迟早会把它清除干净.僵尸进程就是子进程已经结束掉,而父进程却没 kill 掉这个子进程,那么这个子进程就成为僵尸进程
第 14 章 命令替换
textfile_listing=`ls *.txt`
textfile_listing2=$(ls *.txt)
注意: 命令替换将会调用一个 subshell
注意: 当一个变量是使用命令替换的结果做为值的时候, 然后使用 echo 命令来输出这个变量(并且不引用这个变量, 就是不用引号括起来), 那么命令替换将会从最终的输出中删掉换行符. 这可能会引起一些异常情况.
#!/bin/bash
dir_listing=`ls -l`
echo $dir_listing
echo "$dir_listing"
命令替换甚至允许将整个文件的内容放到变量中, 可以使用重定向或者 cat 命令.
variable1=`<file1`
variable2=`cat file2` #但是这行将会 fork 一个新进程,所以这行代码将会比第一行代码执行得慢.
第 16 章 I/O 重定向
exec命令 | 作用 |
exec ls | 在shell中执行ls,ls结束后不返回原来的shell中了 |
exec <file | 将file中的内容作为exec的标准输入 |
exec >file | 将file中的内容作为标准写出 |
exec 3<file | 将file读入到fd3中 |
sort <&3 | fd3中读入的内容被分类 |
exec 4>file | 将写入fd4中的内容写入file中 |
ls >&4 | Ls将不会有显示,直接写入fd4中了,即上面的file中 |
exec 5<&4 | 创建fd4的拷贝fd5 |
exec 3<&- | 关闭fd3 |
Example 16-1 使用 exec 重定向标准输入
exec 6<&0 ## 将文件描述符#6 与 stdin 链接起来.
exec < data-file #将file中的内容作为exec的标准输入
read a1
read a2
echo $a1
echo $a2
exec 0<&6
6<&- #关闭fd6
#现在将 stdin 从 fd #6 中恢复, 因为刚才我们把 stdin 重定向到#6 了,然后关闭 fd 6 ( 6<&- ), 好让这个描述符继续被其他进程所使用.
echo -n "Enter data "
read b1
echo "b1 = $b1"
exit 0
第 19 章 正则表达式
一个正则表达式包含下面一个或多个项:
1. 一个字符集.
这里的字符集里的字符表示的就是它们字面上的意思.正则表达式最简单的情况就是仅仅由字符集组成,而没有其他的元字符.
2. 锚.
一个锚指明了正则表达式在一行文本中要匹配的位置,例如^和$就是锚.
3. 修饰符
它们用于展开或缩小(即是修改了)正则表达式匹配文本行的范围.修饰符包括了星号.括号和反斜杠符号
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\\' 匹配 "\" 而 "\(" 则匹配 "("。 |
^ | 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。 |
. | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。 |
(pattern) | 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '\(' 或 '\)'。 |
(?:pattern) | 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。 |
(?=pattern) | 正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
x|y | 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
\B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
\cx | 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于 \x09 和 \cI。 |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
\w | 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。 |
\W | 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。 |
\xn | 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,'\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。. |
\num | 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。 |
\nml | 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。 |
\un | 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。 |
尖角号必须被转义,因为不这样做的话它们就表示单纯的字面意思而已.
"\<the\>" 匹配单词"the",但不匹配"them", "there", "other",等等
echo a111b | sed -ne '/a1\+b/p'
echo a111b | grep '/a1\+b/p'
echo a111b | gawk -ne '/a1\+b/p'
转义"大括号" -- \{ \} -- 指示前面正则表达式匹配的次数.
"[0-9]\{5\}" #精确匹配 5 个数字 (从 0 到 9 的数字).
第 20 章 子 shell(Subshells)
父子Shell是相对的,它描述了两个Shell进程的fork关系,父Shell指在控制终端或xterm窗口给出提示符的进程,子Shell是由父Shell创建的进程。父Shell创建子Shell调用的是fork函数。
Shell命令可以分为内建命令(Shell本身执行的命令)和外部命令(fork创建出来的子shell执行的命名),内建命令不创建子Shell而外部命令创建子Shell。因此,内建命令比起外部的等价命令执行起来更快.
(1)圆括号结构
能强制将其中的命令运行在子shell中,在子 shell 里的变量不能被这段子 shell 代码块之外外面的脚本访问.这些变量是不能被
产生这个子 shell 的父进程(parent process)存取的,实际上它们是局部变量
( 命令 1; 命令 2; 命令 3; ... )
Example 20-1 子 shell 中的变量作用域
BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(subshell)嵌套深度的累加器。
#!/bin/bash
echo "subshell level OUTSIDE subshell = $BASH_SUBSHELL"
outer_variable=Outer
(
echo "subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner
echo "Subshell:"
echo "innrer_variable = $inner_variable"
echo "outer_variable = $outer_variable"
)
if [ -z "$inner_variable" ]
then
echo "inner_variable undefined in main body"
else
echo "inner_variable defined in main body"
fi
echo "Main body:"
echo "subshell level OUTSIDE subshell = $BASH_SUBSHELL"
exit 0
在子 shell 中的目录更改不会影响到父 shell
Example 20-2 列出用户的配置文件
#!/bin/bash
FILE=.bashrc
for home in `awk -F: '{print $6}' /etc/passwd`
do
# echo $home
[ -d "$home" ] || continue
[ -r "$home" ] || continue
(cd $home; [ -e $FILE ] && less $FILE )
done
exit 0
子 shell 可用于为一组命令设定临时的环境变量.父 shell 不受影响,变量值没有更改
第 23 章 函数
重定向函数的标准输入函数本质上是一个代码块(code block), 这样意思着它的标准输入可以被重定向
Example 23-11 用户名的真实名
#!/bin/bash
ARGCOUNT=1
E_WRONGARGS=65
file=/etc/passwd
pattern=$1
if [ $# -ne "$ARGCOUNT" ]
then
echo "Usage: `basename $0` USENRAME "
exit $E_WRONGARGS
fi
file_excerpt()
{
while read line
do
echo "$line" | grep $1 | awk -F: '{print $5}'
done
}<$file # 重定向函数的标准输入.
file_excerpt $pattern
exit 0
return 命令是 Bash 内建(builtin)的.
局部变量
如果变量用 local 来声明,那么它只能在该变量声明的代码块(block of code)中可见.这个代码块就是局部"范围". 在一个函数内,局部变量意味着只能在函数代码块内它才有意义.
Example 23-12 局部变量的可见范围
#!/bin/bash
func()
{
local loc_val=23
echo "in func:"
echo "loc_val = $loc_val"
global_val=999
echo "global_val = $global_val"
}
func
echo "outside func"
echo "loc_val = $loc_val"
global_val=999
echo "global_val = $global_val"
23.2.1. 局部变量使递归变得可能.
Example 23-13 用局部变量来递归
##!/bin/bash
MAX_ARG=5
E_WRONGARGS=65
E_RANGE_ERR=66
if [ -z "$1" ]
then
echo "Usage `basename $0` number"
exit $E_WRONGARGS
fi
if [ "$1" -gt $MAX_ARG ]
then
echo "Out of range(5 is maximum)"
exit $E_RANGE_ERR
fi
fact()
{
local number=$1
if [ $number -eq 0 ]
then
ans=1
else
let "decrnum=$number-1"
fact $decrnum
let "ans=$number * $?"
fi
return $ans
}
fact $1
echo "factorial of $1 is $?"
#!/bin/bash
func()
{
echo $1
(($1 < $2)) && func $(($1 + 1)) $2
}
func 1 50000 #这种深度的递归甚至可能由于耗尽栈的内存大小而引起 C 程序的段错误.
23.3. 不使用局部变量的递归
E_NOPARAM=66
E_BADPARAM=67
moves=
hanoi()
{
case $1 in
0)
;;
*)
hanoi "$(($1-1))" $2 $4 $3
echo move $2 "-->" $3
let "moves += 1"
hanoi "$(($1-1))" $4 $3 $2
;;
esac
}
hanoi $1 1 3 2
echo "Total moves = $moves"
exit 0
第 24 章 别名(Aliases)
Bash 别名本质上是一个简称, 缩写, 这可避免键入过长的命令序列
alias lm="ls -l | more" #输入lm将会自动被替换成 ls -l | more.
unalias lm # 删除别名.
第 26 章 数组
较新的 Bash 版本支持一维数组. 数组元素可以用符号 variable[xx]来初始化. 另外,脚本可以用 declare -a variable 语句来清楚地指定一个数组. 要访问一个数组元素,可以使用花括号来访问,即${variable[xx]}
稀疏
数组成员不必一定要连贯或连续的.
Example 26数组成员不必一定要连贯或连续的.-1 简单的数组用法
#!/bin/bash
a[11]=23
a[13]=37
a[51]=UFOs
echo "a[11]=${a[11]}"
echo "a[13]=${a[13]}"
echo "a[43]=${a[43]}"
a[5]=`expr ${a[11]} + ${a[13]}`
echo "a[5] = a[11] + a[13] = ${a[5]}"
a[6]=`expr ${a[11]}+${a[51]}`
echo "a[6] = a[11] + a[11] = ${a[6]}"
其他指定数组元素的值的办法
#!/bin/bash
a2=( zero one two three four )
echo "a2[0] = ${a2[0]}"
a3=([17]=seventeen [24]=twenty-four)
echo "a3[17] = ${a3[17]}"
Example 26-5 将脚本的内容传给数组
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
#!/bin/bash
s=( $(cat "$0") )
echo ${#s[@]}
for element in $(seq $((${#s[@]} - 1)))
do
echo "${s[$element]}"
done
exit 0
"unset"命令删除一个数组元素或是整个数组
第 27 章 /dev 和 /proc
27.1. /dev
在 /dev 目录内包含以或不以硬件形式出现的物理设备条目.
27.2. /proc
/proc 目录实际上是一个伪文件系统 . 在 /proc 目录里的文件是当前运行系统和内核进程及它们的相关信息和统计.
第 29 章 调试
设置选项 -n -v -x
sh -n scriptname 不会实际运行脚本,而只是检查脚本的语法错误.
sh -v scriptname 在实际执行一个命令前打印出这个命令,这也等同于在脚本里设置set -v 或 set -o verbose.
#!/bin/bash
stringZ=abcABC123ABCabc
echo ${stringZ/abc/XYZ}
echo ${stringZ//abc/XYZ}
echo ${stringZ/#abc/XYZ}
echo ${stringZ/%abc/XYZ}
sh -x scriptname 打印每个命令的执行结果, 但只用在某些小的方面. 它等同于脚本中插入 set -x 或 set -o xtrace.
##!/bin/bash
MAX_ARG=5
E_WRONGARGS=65
E_RANGE_ERR=66
if [ -z "$1" ]
then
echo "Usage `basename $0` number"
exit $E_WRONGARGS
fi
if [ "$1" -gt $MAX_ARG ]
then
echo "Out of range(5 is maximum)"
exit $E_RANGE_ERR
fi
fact()
{
local number=$1
if [ $number -eq 0 ]
then
ans=1
else
let "decrnum=$number-1"
fact $decrnum
let "ans=$number * $?"
fi
return $ans
}
fact $1
echo "factorial of $1 is $?"
把 set -u 或 set -o nounset 插入到脚本里并运行它, 就会在每个试图使用没有申明过的变量的地方打印出一个错误信息.