惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

博客园 - 可可

广东电信最新DNS更新了 分布式缓存失效处理 序列化 suse中计划任务工具cron的使用 转载《.NET框架设计之五------------异常设计(异常与返回值)》 《高级Bash脚本编程指南》在线阅读 Linux多线程例子 转载《Adding a Second IP Address to an Existing Network Adapter on Linux》 SuSE下chkconfig的启动脚本参考例子"/etc/init.d/skeleton” - 可可 - 博客园 转载《Starting and Stoping Services: insserv》 linux下c++程序编译错误--理解typename 一个公网IP情况下实现LVS VS/DR vmware中suse10不能上网解决方法 Using PHP with mod_fcgid Apache Configuration PHP在Apache中两种工作方式的区别(CGI模式、Apache 模块DLL) SSO 转贴《负载均衡技术介绍》 CDN
转载《Linux Shell Bash 各种小技巧》
可可 · 2009-06-26 · via 博客园 - 可可

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