您的位置:首页 > 运维架构 > Shell

转载《Linux Shell Bash 各种小技巧》

2009-06-26 17:48 351 查看
http://www.cnscn.org/htm_data/679/0905/46738.html

http://www.cnscn.org/thread.php?fid=679

为了记录在某个(或某些)特定会话中用户脚本的运行状态, 可以将下面的代码添加到你想要跟踪记录的脚本中. 添加的这段代码会将脚本名和调用次数记录到一个连续的文件中.

1 # 添加(>>)下面的代码, 到你想跟踪记录的脚本末尾.

2

3 whoami>> $SAVE_FILE    # 记录调用脚本的用户.

4 echo $0>> $SAVE_FILE   # 脚本名.

5 date>> $SAVE_FILE      # 记录日期和时间.

6 echo>> $SAVE_FILE      # 空行作为分隔符.

7

8 #  当然, 我们应该在~/.bashrc中定义并导出变量SAVE_FILE.

9 #+ (看起来有点像~/.scripts-run)

>>操作符可以在文件末尾添加内容. 如果你想在文件的头部添加内容怎么办, 难道要粘贴到文件头?

1 file=data.txt

2 title="***This is the title line of data text file***"

3

4 echo $title | cat - $file >$file.new

5 # "cat -" 将stdout连接到$file.

6 #  最后的结果就是生成了一新文件,

7 #+ 并且成功的将$title的内容附加到了文件的*开头*.

这是之前的例子 17-13脚本的简化版本. 当然, sed也能做到.

shell脚本也可以象一个内嵌到脚本的命令那样被调用, 比如Tcl或wish脚本, 甚至是Makefile. 在C语言中, 它们可以作为一个外部的shell命令被system()函数调用, 比如, system("script_name");.

将一个内嵌sed或awk的脚本内容赋值给一个变量, 能够提高shell包装脚本的可读性. 请参考例子 A-1例子 11-19.

将你最喜欢的变量定义和函数实现都放到一个文件中. 在你需要的时候, 通过使用(.)命令, 或者source命令, 来将这些"库文件""包含"到脚本中.

1 # 脚本库

2 # ------ -------

3

4 # 注:

5 # 这里没有"#!".

6 # 也没有"真正需要执行的代码".

7

8

9 # 有用的变量定义

10

11 ROOT_UID=0             # root用户的$UID为0.

12 E_NOTROOT=101          # 非root用户的出错代码.

13 MAXRETVAL=255          # 函数最大的返回值(正值).

14 SUCCESS=0

15 FAILURE=-1

16

17

18

19 # Functions

20

21 Usage ()               # "Usage:"信息. (译者注: 即帮助信息)

22 {

23   if [ -z "$1" ]       # 没有参数传递进来.

24   then

25     msg=filename

26   else

27     msg=$@

28   fi

29

30   echo "Usage: `basename $0` "$msg""

31 }

32

33

34 Check_if_root ()       # 检查运行脚本的用户是否为root.

35 {                      # 摘自"ex39.sh".

36   if [ "$UID" -ne "$ROOT_UID" ]

37   then

38     echo "Must be root to run this script."

39     exit $E_NOTROOT

40   fi

41 }

42

43

44 CreateTempfileName ()  # 创建"唯一"的临时文件.

45 {                      # 摘自"ex51.sh".

46   prefix=temp

47   suffix=`eval date +%s`

48   Tempfilename=$prefix.$suffix

49 }

50

51

52 isalpha2 ()            # 测试*整个字符串*是否都是由字母组成的.

53 {                      # 摘自"isalpha.sh".

54   [ $# -eq 1 ] || return $FAILURE

55

56   case $1 in

57   *[!a-zA-Z]*|"") return $FAILURE;;

58   *) return $SUCCESS;;

59   esac                 # 感谢, S.C.

60 }

61

62

63 abs ()                           # 绝对值.

64 {                                # 注意: 最大的返回值 = 255.

65   E_ARGERR=-999999

66

67   if [ -z "$1" ]                 # 需要传递参数.

68   then

69     return $E_ARGERR             # 返回错误.

70   fi

71

72   if [ "$1" -ge 0 ]              # 如果是非负值,

73   then                           #

74     absval=$1                    # 那就是绝对值本身.

75   else                           # 否则,

76     let "absval = (( 0 - $1 ))"  # 改变符号.

77   fi

78

79   return $absval

80 }

81

82

83 tolower ()             #  将传递进来的参数字符串

84 {                      #+ 转换为小写.

85

86   if [ -z "$1" ]       #  如果没有参数传递进来.

87   then                 #+ 打印错误消息

88     echo "(null)"      #+ (C风格的void指针错误消息)

89     return             #+ 并且从函数中返回.

90   fi

91

92   echo "$@" | tr A-Z a-z

93   # 转换所有传递进来的参数($@).

94

95   return

96

97 # 使用命令替换, 将函数的输出赋值给变量.

98 # 举例:

99 #    oldvar="A seT of miXed-caSe LEtTerS"

100 #    newvar=`tolower "$oldvar"`

101 #    echo "$newvar"    # 一串混合大小写的字符全部转换为小写

102 #

103 # 练习: 重写这个函数,

104 #           将传递进来的参数全部转换为大写[容易].

105 }

使用特殊目的注释头来增加脚本的条理性和可读性.

1 ## 表示注意.

2 rm -rf *.zzy   ##  "rm"命令的"-rf"选项非常的危险.

3                ##+ 尤其对通配符, 就更危险.

4

5 #+ 表示继续上一行.

6 #  这是多行注释的第一行,

7 #+

8 #+ 这是最后一行.

9

10 #* 表示标注.

11

12 #o 表示列表项.

13

14 #> 表示另一种观点.

15 while [ "$var1" != "end" ]    #> while test "$var1" != "end"

if-test结构有一种聪明的用法, 用来注释代码块.

1 #!/bin/bash

2

3 COMMENT_BLOCK=

4 #  如果给上面的变量赋值,

5 #+ 就会出现令人不快的结果.

6

7 if [ $COMMENT_BLOCK ]; then

8

9 Comment block --

10 =================================

11 This is a comment line.

12 This is another comment line.

13 This is yet another comment line.

14 =================================

15

16 echo "This will not echo."

17

18 Comment blocks are error-free! Whee!

19

20 fi

21

22 echo "No more comments, please."

23

24 exit 0

比较这种用法, 和使用here document注释代码块之间的区别.

使用$?退出状态变量, 因为脚本可能需要测试一个参数是否都是数字, 以便于后边可以把它当作一个整数来处理.

1 #!/bin/bash

2

3 SUCCESS=0

4 E_BADINPUT=65

5

6 test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null

7 # 整数要不就是0, 要不就是非0值. (译者注: 感觉像废话 . . .)

8 # 2>/dev/null禁止输出错误信息.

9

10 if [ $? -ne "$SUCCESS" ]

11 then

12   echo "Usage: `basename $0` integer-input"

13   exit $E_BADINPUT

14 fi

15

16 let "sum = $1 + 25"             # 如果$1不是整数, 就会产生错误.

17 echo "Sum = $sum"

18

19 # 任何变量都可以使用这种方法来测试, 而不仅仅适用于命令行参数.

20

21 exit 0

函数的返回值严格限制在0 - 255之间. 使用全局变量或者其他方法来代替函数返回值, 通常都很容易产生问题. 从函数中, 返回一个值到脚本主体的另一个办法是, 将这个"返回值"写入到stdout(通常都使用echo命令), 然后将其赋值给一个变量. 这种做法其实就是命令替换的一个变种.

例子 33-15. 返回值小技巧

1 #!/bin/bash

2 # multiplication.sh

3

4 multiply ()                     # 将乘数作为参数传递进来.

5 {                               # 可以接受多个参数.

6

7   local product=1

8

9   until [ -z "$1" ]             # 直到处理完所有的参数...

10   do

11     let "product *= $1"

12     shift

13   done

14

15   echo $product                 #  不会echo到stdout,

16 }                               #+ 因为要把它赋值给一个变量.

17

18 mult1=15383; mult2=25211

19 val1=`multiply $mult1 $mult2`

20 echo "$mult1 X $mult2 = $val1"

21                                 # 387820813

22

23 mult1=25; mult2=5; mult3=20

24 val2=`multiply $mult1 $mult2 $mult3`

25 echo "$mult1 X $mult2 X $mult3 = $val2"

26                                 # 2500

27

28 mult1=188; mult2=37; mult3=25; mult4=47

29 val3=`multiply $mult1 $mult2 $mult3 $mult4`

30 echo "$mult1 X $mult2 X $mult3 X $mult4 = $val3"

31                                 # 8173300

32

33 exit 0

相同的技术也可以用在字符串上. 这意味着函数可以"返回"非数字的值.

1 capitalize_ichar ()          #  将传递进来的字符串的

2 {                            #+ 首字母转换为大写.

3

4   string0="$@"               # 能够接受多个参数.

5

6   firstchar=${string0:0:1}   # 首字母.

7   string1=${string0:1}       # 余下的字符.

8

9   FirstChar=`echo "$firstchar" | tr a-z A-Z`

10                              # 将首字母转换为大写.

11

12   echo "$FirstChar$string1"  # 输出到stdout.

13

14 }

15

16 newstring=`capitalize_ichar "every sentence should start with a capital letter."`

17 echo "$newstring"          # Every sentence should start with a capital letter.

使用这种办法甚至能够"返回"多个值.

例子 33-16. 返回多个值的技巧

1 #!/bin/bash

2 # sum-product.sh

3 # 可以"返回"超过一个值的函数.

4

5 sum_and_product ()   # 计算所有传递进来的参数的总和, 与总乘积.

6 {

7   echo $(( $1 + $2 )) $(( $1 * $2 ))

8 # 将每个计算出来的结果输出到stdout, 并以空格分隔.

9 }

10

11 echo

12 echo "Enter first number "

13 read first

14

15 echo

16 echo "Enter second number "

17 read second

18 echo

19

20 retval=`sum_and_product $first $second`      # 将函数的输出赋值给变量.

21 sum=`echo "$retval" | awk '{print $1}'`      # 赋值第一个域.

22 product=`echo "$retval" | awk '{print $2}'`  # 赋值第二个域.

23

24 echo "$first + $second = $sum"

25 echo "$first * $second = $product"

26 echo

27

28 exit 0

下一个技巧, 是将数组传递给函数的技术, 然后"返回"一个数组给脚本的主体.

使用命令替换将数组中的所有元素(元素之间用空格分隔)赋值给一个变量, 这样就可以将数组传递到函数中了. 我们之前提到过一种返回值的策略, 就是将要从函数中返回的内容, 用echo命令输出出来, 然后使用命令替换或者( ... )操作符, 将函数的输出(也就是我们想要得返回值)保存到一个变量中. 如果我们想让函数"返回"数组, 当然也可以使用这种策略.

例子 33-17. 传递数组到函数, 从函数中返回数组

1 #!/bin/bash

2 # array-function.sh: 将数组传递到函数中与...

3 #                   从函数中"返回"一个数组

4

5

6 Pass_Array ()

7 {

8   local passed_array   # 局部变量.

9   passed_array=( `echo "$1"` )

10   echo "${passed_array[@]}"

11   #  列出这个新数组中的所有元素,

12   #+ 这个新数组是在函数内声明的, 也是在函数内赋值的.

13 }

14

15

16 original_array=( element1 element2 element3 element4 element5 )

17

18 echo

19 echo "original_array = ${original_array[@]}"

20 #                      列出原始数组的所有元素.

21

22

23 # 下面是关于如何将数组传递给函数的技巧.

24 # **********************************

25 argument=`echo ${original_array[@]}`

26 # **********************************

27 #  将原始数组中所有的元素都用空格进行分隔,

28 #+ 然后合并成一个字符串, 最后赋值给一个变量.

29 #

30 # 注意, 如果只把数组传递给函数, 那是不行的.

31

32

33 # 下面是让数组作为"返回值"的技巧.

34 # *****************************************

35 returned_array=( `Pass_Array "$argument"` )

36 # *****************************************

37 # 将函数中'echo'出来的输出赋值给数组变量.

38

39 echo "returned_array = ${returned_array[@]}"

40

41 echo "============================================================="

42

43 #  现在, 再试一次,

44 #+ 尝试一下, 在函数外面访问(列出)数组.

45 Pass_Array "$argument"

46

47 # 函数自身可以列出数组, 但是...

48 #+ 从函数外部访问数组是被禁止的.

49 echo "Passed array (within function) = ${passed_array[@]}"

50 # NULL值, 因为这个变量是函数内部的局部变量.

51

52 echo

53

54 exit 0

如果想更加了解如何将数组传递到函数中, 请参考例子 A-10, 这是一个精心制作的例子.

利用双括号结构, 就可以让我们使用C风格的语法, 在for循环和while循环中, 设置或者增加变量. 请参考例子 10-12例子 10-17.

如果在脚本的开头设置pathumask的话, 就可以增加脚本的"可移植性" -- 即使在那些被用户将
$PATH
umask弄糟了的机器上, 也可以运行.

1 #!/bin/bash

2 PATH=/bin:/usr/bin:/usr/local/bin ; export PATH

3 umask 022   # 脚本创建的文件所具有的权限是755.

4

5 # 感谢Ian D. Allen提出这个技巧.

一项很有用的技术是, 重复地将一个过滤器的输出(通过管道)传递给这个相同的过滤器, 但是这两次使用不同的参数和选项. 尤其是trgrep, 非常适合于这种情况.

1 # 摘自例子"wstrings.sh".

2

3 wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \

4 tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

例子 33-18. anagram游戏

1 #!/bin/bash

2 # agram.sh: 使用anagram来玩游戏.

3

4 # 寻找anagram...

5 LETTERSET=etaoinshrdlu

6 FILTER='.......'       # 最少有多少个字母?

7 #       1234567

8

9 anagram "$LETTERSET" | # 找出这个字符串中所有的anagram...

10 grep "$FILTER" |       # 至少需要7个字符,

11 grep '^is' |           # 以'is'开头

12 grep -v 's$' |         # 不是复数(指英文单词的复数)

13 grep -v 'ed$'          # 不是过去时(也指英文单词)

14 # 可以添加许多种组合条件和过滤器.

15

16 #  使用"anagram"工具,

17 #+ 这是作者的"yawl"文字表软件包中的一部分.

18 #  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz

19 #  http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz

20

21 exit 0                 # 代码结束.

22

23

24 bash$ sh agram.sh

25 islander

26 isolate

27 isolead

28 isotheral

29

30

31

32 #  练习:

33 #  -----

34 #  修改这个脚本, 使其能够让LETTERSET作为命令行参数.

35 #  将第11 - 13行的过滤器参数化(比如, 可以使用变量$FILTER),

36 #+ 这样我们就可以根据传递的参数来指定功能.

37

38 #  可以参考脚本agram2.sh,

39 #+ 与这个例子稍微有些不同.

也请参考例子 27-3, 例子 12-22, 和例子 A-9.

使用"匿名的here document"来注释代码块, 这样就不用在每个注释行前面都加上#了. 请参考例子 17-11.

如果一个脚本的运行依赖于某个命令, 而且这个命令没被安装到运行这个脚本的机器上, 那么在运行的时候就会产生错误. 我们可以使用whatis命令来避免这种可能产生的问题.

1 CMD=command1                 # 第一选择.

2 PlanB=command2               # 如果第一选择不存在就选用这个.

3

4 command_test=$(whatis "$CMD" | grep 'nothing appropriate')

5 #  如果在系统中没找到'command1',

6 #+ 那么'whatis'将返回"command1: nothing appropriate."

7 #

8 #  另一种更安全的做法是:

9 #     command_test=$(whereis "$CMD" | grep \/)

10 #  但是下面的测试条件应该反过来,

11 #+ 因为变量$command_test只有在$CMD存在于系统上的时候,

12 #+ 才会有内容.

13 #     (感谢, bojster.)

14

15

16 if [[ -z "$command_test" ]]  # 检查命令是否存在.

17 then

18   $CMD option1 option2       #  使用选项来调用command1.

19 else                         #  否则,

20   $PlanB                     #+ 运行command2.

21 fi

在错误的情况下, if-grep test可能不会返回期望的结果, 因为出错文本是输出到stderr上, 而不是stdout.

1 if ls -l nonexistent_filename | grep -q 'No such file or directory'

2   then echo "File \"nonexistent_filename\" does not exist."

3 fi

将stderr重定向到stdout上, 就可以解决这个问题.

1 if ls -l nonexistent_filename 2>&1 | grep -q 'No such file or directory'

2 #                             ^^^^

3   then echo "File \"nonexistent_filename\" does not exist."

4 fi

5

6 # 感谢, Chris Martin指出这一点.

run-parts命令可以很方便的依次运行一组命令脚本, 尤其是和cronat组合使用的时候.

如果可以在shell脚本中调用X-Windows的小工具, 那该有多好. 目前已经有一些工具包可以完成这种功能, 比如Xscript, Xmenu, 和widtools. 头两种工具包已经不再被维护了. 幸运的是, 我们还可以从这里下载第三种工具包, widtools.


要想使用widtools(widget tools)工具包, 必须先安装XForms库. 除此之外, 在典型的Linux系统上编译之前, 需要正确的编辑它的Makefile. 最后, 在提供的6个部件中, 有3个不能工作(事实上, 会产生段错误).

dialog工具集提供了一种从shell脚本中调用"对话框"窗口部件的方法. The 原始的dialog工具包只能工作在文本的控制台模式下, 但是后续的类似工具, 比如gdialog, Xdialog, 和kdialog都是基于X-Windows窗口部件集合的.

例子 33-19. [b]从shell脚本中调用窗口部件[/b]

1 #!/bin/bash

2 # dialog.sh: 使用'gdialog'窗口部件.

3 # 必须在你的系统上安装'gdialog'才能运行这个脚本.

4 # 版本1.1 (04/05/05最后修正)

5

6 # 这个脚本的灵感来源于下面的文章.

7 #     "Scripting for X Productivity," by Marco Fioretti,

8 #      LINUX JOURNAL, Issue 113, September 2003, pp. 86-9.

9 # 感谢你们, 所有的LINUX JOURNAL好人.

10

11

12 # 在对话框窗口中的输入错误.

13 E_INPUT=65

14 # 输入窗口的显示尺寸.

15 HEIGHT=50

16 WIDTH=60

17

18 # 输出文件名(由脚本名构造).

19 OUTFILE=$0.output

20

21 # 将脚本的内容显示到文本窗口中.

22 gdialog --title "Displaying: $0" --textbox $0 $HEIGHT $WIDTH

23

24

25

26 # 现在, 我们将输入保存到文件中.

27 echo -n "VARIABLE=" > $OUTFILE

28 gdialog --title "User Input" --inputbox "Enter variable, please:" \

29 $HEIGHT $WIDTH 2>> $OUTFILE

30

31

32 if [ "$?" -eq 0 ]

33 # 检查退出状态码, 是一个好习惯.

34 then

35   echo "Executed \"dialog box\" without errors."

36 else

37   echo "Error(s) in \"dialog box\" execution."

38         # 或者, 点"Cancel"按钮, 而不是"OK".

39   rm $OUTFILE

40   exit $E_INPUT

41 fi

42

43

44

45 # 现在, 我们将重新获得并显示保存的变量.

46 . $OUTFILE   # 'Source'(执行)保存的文件.

47 echo "The variable input in the \"input box\" was: "$VARIABLE""

48

49

50 rm $OUTFILE  # 清除临时文件.

51              # 某些应用可能需要保留这个文件.

52

53 exit $?

其他在脚本中使用窗口部件的工具, 比如Tk或wish (Tcl派生物), PerlTk(带有Tk扩展的Perl), tksh(带有Tk扩展的ksh), XForms4Perl(带有XForms扩展的Perl), Gtk-Perl(带有Gtk扩展的Perl), 或PyQt(带有Qt扩展的Python).

为了对复杂脚本做多次的修正, 可以使用rcs修订控制系统包.

使用这个软件包的好处之一就是可以自动升级ID头标志. rcs包中的co命令可以对特定的保留关键字作参数替换, 比如, 可以使用下面这行代码来替换掉脚本中的
#$Id$
,

1 #$Id: hello-world.sh,v 1.1 2004/10/16 02:43:05 bozo Exp $

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: