bash shell(2)

第 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中执行lsls结束后不返回原来的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 匹配版权符号 (?)。
转义(escape)"尖角号" -- \<...\> -- 用于表示单词的边界.
尖角号必须被转义,因为不这样做的话它们就表示单纯的字面意思而已.
"\<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 插入到脚本里并运行它, 就会在每个试图使用没有申明过的变量的地方打印出一个错误信息.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值