keke

~
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

Posted on 2009-06-26 17:48  可可  阅读(691)  评论(0)    收藏  举报
  • http://www.cnscn.org/htm_data/679/0905/46738.html
  • http://www.cnscn.org/thread.php?fid=679
    为了记录在某个(或某些)特定会话中用户脚本的运行状态, 可以将下面的代码添加到你想要跟踪记录的脚本中. 添加的这段代码会将脚本名和调用次数记录到一个连续的文件中.
      1 # 添加(>>)下面的代码, 到你想跟踪记录的脚本末尾. 

    3 whoami>> $SAVE_FILE # 记录调用脚本的用户.
    4 echo $0>> $SAVE_FILE # 脚本名.
    5 date>> $SAVE_FILE # 记录日期和时间.
    6 echo>> $SAVE_FILE # 空行作为分隔符.

    8 # 当然, 我们应该在~/.bashrc中定义并导出变量SAVE_FILE.
    9 #+ (看起来有点像~/.scripts-run)

     

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

     

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

    4 echo $title | cat - $file >$file.new
    5 # "cat -" 将stdout连接到$file.
    6 # 最后的结果就是生成了一新文件,
    7 #+ 并且成功的将$title的内容附加到了文件的*开头*.

     

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

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

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

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

     

      1 # 脚本库
    2 # ------ -------

    4 # 注:
    5 # 这里没有"#!".
    6 # 也没有"真正需要执行的代码".


    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  ##+ 尤其对通配符, 就更危险.

    5 #+ 表示继续上一行.
    6 # 这是多行注释的第一行,
    7 #+
    8 #+ 这是最后一行.

    10 #* 表示标注.
    11 
    12 #o 表示列表项.
    13 
    14 #> 表示另一种观点.
    15 while [ "$var1" != "end" ] #> while test "$var1" != "end"

     

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

     

      1 #!/bin/bash

    3 COMMENT_BLOCK=
    4 # 如果给上面的变量赋值,
    5 #+ 就会出现令人不快的结果.

    7 if [ $COMMENT_BLOCK ]; then

    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

    3 SUCCESS=0
    4 E_BADINPUT=65

    6 test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
    7 # 整数要不就是0, 要不就是非0值. (译者注: 感觉像废话 . . .)
    8 # 2>/dev/null禁止输出错误信息.

    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

    4 multiply () # 将乘数作为参数传递进来.
    5 { # 可以接受多个参数.

    7  local product=1

    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 { #+ 首字母转换为大写.

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

    6  firstchar=${string0:0:1} # 首字母.
    7  string1=${string0:1} # 余下的字符.

    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 # 可以"返回"超过一个值的函数.

    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 # 从函数中"返回"一个数组


    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的话, 就可以增加脚本的"可移植性" -- 即使在那些被用户将$PATHumask弄糟了的机器上, 也可以运行.

      1 #!/bin/bash
    2 PATH=/bin:/usr/bin:/usr/local/bin ; export PATH
    3 umask 022 # 脚本创建的文件所具有的权限是755.

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

     

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

     

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

    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来玩游戏.

    4 # 寻找anagram...
    5 LETTERSET=etaoinshrdlu
    6 FILTER='.......' # 最少有多少个字母?
    7 # 1234567

    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 # 如果第一选择不存在就选用这个.

    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

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

     

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

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

    Caution

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

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


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

      1 #!/bin/bash
    2 # dialog.sh: 使用'gdialog'窗口部件.
    3 # 必须在你的系统上安装'gdialog'才能运行这个脚本.
    4 # 版本1.1 (04/05/05最后修正)

    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 $?

    其他在脚本中使用窗口部件的工具, 比如Tkwish (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 $