笔记高级Bash脚本编程指南

这本电子书在电脑里已经有段时间了,虽然平时写点shell没问题,但是感觉还是不精,就拿这本书查漏补缺吧.

注: 脚本例子大部分引用原书

就从12章开始吧

cat , tac , rev

cat test                   tac test                 rev test

123                            789                       321
456                            456                       654
789                            123                       987

expr

通用求值表达式: 通过给定的操作(参数必须以空格分开)连接参数, 并对参数求值. 可以使算术操作, 比较操作, 字符串操作或者是逻辑操作.

expr 3 + 5
返回8

expr 5 % 3
返回2

expr 1 / 0
返回错误消息, expr: division by zero

不允许非法的算术操作.

expr 5 \* 3
返回15

在算术表达式expr中使用乘法操作时, 乘法符号必须被转义.

y=`expr $y + 1`
增加变量的值, 与let y=y+1和y=$(($y+1))的效果相同. 这是使用算术表达式的一个例子.

z=`expr substr $string $position $length`
在位置$position上提取$length长度的子串.

date

# date --date='1 days ago'   
2011年 05月 06日 星期五 21:04:58 CST
# date --date='1 days ago' +%Y%m%d
20110506

join

join命令只能够操作两个文件. 它可以将那些具有特定标记域(通常是一个数字标签)的行合并起来, 并且将结果输出到stdout. 被加入的文件应该事先根据标记域进行排序以便于能够正确的匹配.

  1 File: 1.data
  2 
  3 100 Shoes
  4 200 Laces
  5 300 Socks
------------------------------
  1 File: 2.data
  2 
  3 100 $40.00
  4 200 $1.00
  5 300 $2.00
------------------------------

 bash$ join 1.data 2.data
 File: 1.data 2.data

 100 Shoes $40.00
 200 Laces $1.00
 300 Socks $2.00
mktemp
使用一个"唯一"的文件名来创建一个临时文件. 如果不带参数的在命令行下调用这个命令时, 将会在/tmp目录下产生一个零长度的文件.
[root@bash]# mktemp -p /home/ abc.XXXXX
/home/abc.V2113
seq
[root@bash ~]# for num in $(seq 10);do echo $num;done 
1
2
3
4
5
6
7
8
9
10
wall
bash$ wall System going down for maintenance in 5 minutes!
Broadcast message from bozo (pts/1) Sun Jul  8 13:53:27 2001...

 System going down for maintenance in 5 minutes!
	      
命令替换 算术扩展
使用后置引用的算术扩展(通常都是和expr一起使用)
 z=`expr $z + 3`          # 'expr'命令将会执行这个扩展.

后置引用形式的算术扩展已经被双括号形式所替代了 -- ((...))和$((...)) -- 当然也可以使用非常方便的let结构.

1 z=$(($z+3))
  2 z=$((z+3))                                  #  也正确. 
  3                                             #  使用双括号的形式, 
  4                                             #+ 参数解引用
  5                                             #+ 是可选的. 
  6 
  7 # $((EXPRESSION))是算数表达式.              #  不要与命令替换
  8                                             #+ 相混淆. 
  9 
 10 
 11 
 12 # 使用双括号的形式也可以不用给变量赋值. 
 13 
 14   n=0
 15   echo "n = $n"                             # n = 0
 16 
 17   (( n += 1 ))                              # 递增. 
 18 # (( $n += 1 )) is incorrect!
 19   echo "n = $n"                             # n = 1
 20 
 21 
 22 let z=z+3
 23 let "z += 3"  #  使用引用的形式, 允许在变量赋值的时候存在空格. 
 24               #  'let'命令事实上执行得的是算术赋值, 
 25               #+ 而不是算术扩展. 
第九章 操作字符串
字符串长度
  1 stringZ=abcABC123ABCabc
  2 
  3 echo ${#stringZ}                 # 15
  4 echo `expr length $stringZ`      # 15
  5 echo `expr "$stringZ" : '.*'`    # 15
提取子串
${string:position:length} 或者 expr substr $string $position $length
  1 stringZ=abcABC123ABCabc
  2 #       0123456789.....
  3 #       0-based indexing.
  4 
  5 echo ${stringZ:0}                            # abcABC123ABCabc
  6 echo ${stringZ:1}                            # bcABC123ABCabc
  7 echo ${stringZ:7}                            # 23ABCabc
  8 
  9 echo ${stringZ:7:3}                          # 23A
 10                                              # 提取子串长度为3.
 11 
 12 
 13 
 14 # 能不能从字符串的右边(也就是结尾)部分开始提取子串? 
 15     
 16 echo ${stringZ:-4}                           # abcABC123ABCabc
 17 # 默认是提取整个字符串, 就象${parameter:-default}一样.
 18 # 然而 . . .
 19 
 20 echo ${stringZ:(-4)}                         # Cabc 
 21 echo ${stringZ: -4}                          # Cabc
 22 # 这样, 它就可以工作了.
 23 # 使用圆括号或者添加一个空格可以"转义"这个位置参数.
子串削除
${string#substring}    从$string开头位置截掉最短匹配的$substring.
${string##substring}   从$string开头位置截掉最长匹配的$substring.
  1 stringZ=abcABC123ABCabc
  2 #       |----|
  3 #       |----------|
  4 
  5 echo ${stringZ#a*C}      # 123ABCabc
  6 # 截掉'a'到'C'之间最短的匹配字符串.
  7 
  8 echo ${stringZ##a*C}     # abc
  9 # 截掉'a'到'C'之间最长的匹配字符串.

${string%substring}    从$string结尾位置截掉最短匹配的$substring.
${string%%substring}  从$string结尾位置截掉最长匹配的$substring.
  1 stringZ=abcABC123ABCabc
  2 #                    ||
  3 #        |------------|
  4 
  5 echo ${stringZ%b*c}      # abcABC123ABCa
  6 # 从$stringZ的结尾位置截掉'b'到'c'之间最短的匹配.
  7 
  8 echo ${stringZ%%b*c}     # a
  9 # 从$stringZ的结尾位置截掉'b'到'c'之间最长的匹配. 
子串替换
${string/substring/replacement}  使用$replacement来替换第一个匹配的$substring.
${string//substring/replacement}  使用$replacement来替换所有匹配的$substring.
  1 stringZ=abcABC123ABCabc
  2 
  3 echo ${stringZ/abc/xyz}           # xyzABC123ABCabc
  4                                   # 使用'xyz'来替换第一个匹配的'abc'.
  5 
  6 echo ${stringZ//abc/xyz}          # xyzABC123ABCxyz
  7                                   # 用'xyz'来替换所有匹配的'abc'.
${string/#substring/replacement}  如果$substring匹配$string开头部分, 那么就用$replacement来替换$substring.
${string/%substring/replacement}  如果$substring匹配$string结尾部分, 那么就用$replacement来替换$substring.
  1 stringZ=abcABC123ABCabc
  2 
  3 echo ${stringZ/#abc/XYZ}          # XYZABC123ABCabc
  4                                   # 用'XYZ'替换开头的'abc'.
  5 
  6 echo ${stringZ/%abc/XYZ}          # abcABC123ABCXYZ
  7                                   # 用'XYZ'替换结尾的'abc'.

printf

[root@x1 ~]# for num in $(seq 10);do printf '%02d\n' $num ;done
01
02
03
04
05
06
07
08
09
10

注意: printf '%02d\n' (这之间必须有空格)$num 

正则表达式 re

grep -E '\<22\>|\<15\>' secure

转义的"尖括号" -- \<...\> -- 用于匹配单词边界.

尖括号必须被转义才含有特殊的含义, 否则它就表示尖括号的字面含义.

"\<the\>" 完整匹配单词"the", 不会匹配"them""there""other", 等等.

扩展的正则表达式

  • 问号 -- ? -- 匹配它前面的字符, 但是只能匹配1次或0次. 通常用来匹配单个字符.

  • 加号 -- + -- 匹配它前面的字符, 能够匹配一次或多次. 与前面讲的*号作用类似, 但是不能匹配0个字符的情况.

  • 转义"大括号" -- \{ \} -- 在转义后的大括号中加上一个数字, 这个数字就是它前面的RE所能匹配的次数.大括号必须经过转义, 否则, 大括号仅仅表示字面含意.
  • 圆括号 -- ( ) -- 括起一组正则表达式. 当你想使用expr进行子字符串提取(substring extraction)的时候, 圆括号就有用了. 如果和下面要讲的"|"操作符结合使用, 也非常有用.

  • 竖线 -- | -- 就是RE中的"或"操作符, 使用它能够匹配一组可选字符中的任意一个.

POSIX字符类. [:class:]

  • [:alnum:] 匹配字母和数字. 等价于A-Za-z0-9.

  • [:alpha:] 匹配字母. 等价于A-Za-z.

  • [:blank:] 匹配一个空格或是一个制表符(tab).

  • [:cntrl:] 匹配控制字符.

  • [:digit:] 匹配(十进制)数字. 等价于0-9.

  • [:graph:] (可打印的图形字符). 匹配ASCII码值范围在33 - 126之间的字符. 与下面所提到的[:print:]类似, 但是不包括空格字符(空格字符的ASCII码是32).

  • [:lower:] 匹配小写字母. 等价于a-z.

  • [:print:] (可打印的图形字符). 匹配ASCII码值范围在32 - 126之间的字符. 与上边的[:graph:]类似, 但是包含空格.

  • [:space:] 匹配空白字符(空格和水平制表符).

  • [:upper:] 匹配大写字母. 等价于A-Z.

  • [:xdigit:] 匹配16进制数字. 等价于0-9A-Fa-f.

22. 进程替换
用圆括号扩起来的命令

>(command)

<(command)

启动进程替换. 它使用/dev/fd/<n>文件将圆括号中的进程处理结果发送给另一个进程. [1] (译者注: 实际上现代的UNIX类操作系统提供的/dev/fd/n文件是与文件描述符相关的, 整数n指的就是进程运行时对应数字的文件描述符)

进程替换可以比较两个不同命令的输出, 甚至能够比较同一个命令不同选项情况下的输出.

bash$ comm <(ls -l) <(ls -al)
total 12
-rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
-rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
-rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh
        total 20
        drwxrwxrwx    2 bozo bozo     4096 Mar 10 18:10 .
        drwx------   72 bozo bozo     4096 Mar 10 17:58 ..
        -rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
        -rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
        -rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh

为了让函数可以返回字符串或是数组, 可以使用一个在函数外可见的专用全局变量.

在函数被调用之前, 所有在函数中声明的变量, 在函数体外都是不可见的, 当然也包括那些被明确声明为local的变量.

  1 count_lines_in_etc_passwd()
  2 {
  3   [[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))
  4   #  如果/etc/passwd是可读的, 那么就把REPLY设置为文件的行数. 
  5   #  这样就可以同时返回参数值与状态信息. 
  6   #  'echo'看上去没什么用, 可是 . . .
  7   #+ 它的作用是删除输出中的多余空白字符. 
  8 }
  9 
 10 if count_lines_in_etc_passwd
 11 then
 12   echo "There are $REPLY lines in /etc/passwd."
 13 else
 14   echo "Cannot count lines in /etc/passwd."
 15 fi  

26. 数组

Bash支持一维数组. 

声明数组

array[10]=     #初始化一个空数组元素

array=(1 2 3 4 5)     #有5个元素
declare -a array     #也是声明一个数组
echo ${array[1]}     #调用数组元素,下标从0开始计算
array_name=([xx]=XXX [yy]=YYY ...)
area3=([17]=seventeen [24]=twenty-four)   #另一种初始化并赋值的方法
echo ${array[@]}  #打印数组所有元素
echo ${array[*]}   #同上

打印一首小诗

Line[1]="I do not know which to prefer,"
Line[2]="The beauty of inflections"
Line[3]="Or the beauty of innuendoes,"
Line[4]="The blackbird whistling"
Line[5]="Or just after."

for index in 1 2 3 4 5    # 5行. 
 do
   printf "     %s\n" "${Line[index]}"
 done

数组操作

array=( one two three four five five )

echo ${#array[0]}    和 echo ${#array}    #第一个数组元素长度

echo ${#array[*]}    和 echo ${#array[@]}    #数组元素个数

#提取数组子串

echo ${arrayZ[@]:0}     # one two three four five five
                                      # 所有元素. 
echo ${arrayZ[@]:1}     # two three four five five
                        # element[0]后边的所有元素. 
echo ${arrayZ[@]:1:2}   # two three
                         # 只提取element[0]后边的两个元素. 

bash使用C语言语法的for循环

#!/bin/bash
for ((i=0;i<6;i++));
do
  echo $i
done

#结果
0
1
2
3
4
5

29. 调试

sh -n scriptname不会运行脚本, 只会检查脚本的语法错误. 

sh -v scriptname将会在运行脚本之前, 打印出每一个命令.

sh -x scriptname会打印出每个命令执行的结果, 但只使用缩写形式.

  #在大括号包含的代码块中, 最后一条命令没有以分号结尾.
  1 { ls -l; df; echo "Done." }
  2 # bash: syntax error: unexpected end of file
  3 
  4 { ls -l; df; echo "Done."; }
  5 #                        ^     ### 最后的这条命令必须以分号结尾. 

原文地址:https://www.cnblogs.com/txwsqk/p/2040123.html