Bash-shell

来源:互联网 发布:windows仿mac软件 编辑:程序博客网 时间:2024/05/16 19:30

第2章 #

eg 2-1 清除/var/log下的log文件

#/bin/bash#以root身份运行cd /var/logcat /dev/null > messages  #清空messages这个文件cat /dev/null > wtmpecho "Logs cleaned up"

eg2-2 2-1的改良版本

#以root身份运行LOGDIR=/var/logcd $LOGDIRcat /dev/null > messagescat /dev/null > wtmpecho "Logs cleaned up"exit #正确且合适的退场脚本的方法~                                     
eg2-3的改良版本
#!/bin/bash#以root身份运行LOG_DIR=/var/logROOT_UID=0  #当UID为0的时候,用户才具有根用户权限LINES=50    #默认的保存行数E_XCD=66E_NOTROOT=67   #商量行都是错误的退出码if [  $UID -ne $ROOT_UID ]then    echo "Must be root to run this script"   exit $E_NOTROOTfiif [ -n "$1" ]   #判断字符串不为空then    lines=$1else   lines=$LINESfi#更好的检测命令行参数的方法#E_WRONGARGS=65#case $1 in#"")#   lines=$LINES#;;#!*[0-9]*)#   echo "Usage: `basename $0` file-to-cleanup"#   exit $E_WRONGARGS#;;#*)#   lines=$1#esaccd $LOG_DIRif [ `pwd` != "$LOG_DIR" ] #当前目录不在LOG_DIR中then   echo "Can't change to $LOG_DIR"   exit $EXCDfiecho "2"tail -$lines messages > mesg.temp # 保存 log file 消息的最后部分.mv mesg.temp messagescat /dev/null > wtmpecho "Logs cleaned up"exit 0

开头的#!意味着执行一个特定的解释器,第一行的内容指定了shell脚本解释器的路径,而且这个指定路径只能放在文件的第一行。第一行写错或者不写时,系统会有一个默认的解释器进行解释。

shell脚本第一行:#!/bin/bash的含义

basename命令

检查参数个数

#!/bin/bashE_WRONG_ARGS=65script_parameters="-a -h -m -z"NUMBER_OF_ARGS=4echo $NUMBER_OF_ARGSecho $#if [ $# -ne $NUMBER_OF_ARGS ]then   echo "Usage : `basename $0` $script_parameters "  #basename  显示脚本名称   exit $E_WRONG_ARGSfi

2-1调用一个脚本

chmod 555 scriptname (允许任何人都具有 可读和执行权限) [6]
chmod +rx scriptname (允许任何人都具有 可读和执行权限)
chmod u+rx scriptname (只给脚本的所有者 可读和执行权限)

脚本具有可执行权限,可以使用./scriptname

可以把脚本移动到/usr/local/bin(以 root 身份)让脚本对所有用户都有用


第3章 特殊字符

#可以出现特定的参数特定结构或者数字常量表达式中

 echo "The # here does not begin a comment." echo 'The # here does not begin a comment.' echo The \# here does not begin a comment. echo The # 这里开始一个注释 echo ${PATH#*:} # 参数替换,不是一个注释 echo $(( 2#101011 )) # 数制转换,不是一个注释,2进制


.命令等价于 source 命令,是一个 bash 的内建命令,表示在当前shell执行(在linux实验,./test.sh子shell的变量不会传到父shell)

.作为文件名的一部分.如果作为文件名的前缀的话,那么这个文件将成为隐藏文件.将不被 ls 命令列出.

.命令如果作为目录名的一部分的话,那么.表达的是当前目录.".."表示上一级目录


eg3-1 代码块和I/O重定向

#!/bin/bashFILE=/etc/passwd{   read line1   read line2}<$FILEecho "first line is $line1"echo "second line is $line2"exit 0

eg 3-2 将一个代码块保存到文件中

#!/bin/bash#描述,列表和确定是否可以安装一个Rpm包SUCCESS=0E_NOARGS=65if [ -z "$1" ]  #空字符串then   echo "Usage:`basename $1` rpm-file"   exit $E_NOARGSfi{   echo    echo "Archive Description"   rpm -qpi $1 #查询说明   echo   echo "Archive Listing:"   rpm -qpl $1 #查询列表   echo   rpm -i --test $1 #查询rpm包是否可以被安装   if [ "$?" -eq $SUCCESS  ]   then      echo "$1 can be installed"   else      echo "$1 cannot be installed"   fi   echo}>"$1.test"echo "Result of rpm test in file $1.test"exit 0

{} \; 路径名.一般都在 find 命令中使用.这不是一个 shell 内建命令.注意: ";"用来结束 find 命令序列的-exec 选项.[] test.test 的表达式将在[]中.值得注意的是[是 shell 内建 test 命令的一部分,并不是/usr/bin/test 中的扩展命令的一个连接.[[]] test.test 表达式放在[[]]中.(shell 关键字)具体查看[[]]结构的讨论.[] 数组元素Array[1]=slot_1echo ${Array[1]}[] 字符范围在正则表达式中使用,作为字符匹配的一个范围(()) 数学计算的扩展在(())结构中可以使用一些数字计算.具体参阅((...))结构.>&>>&>><重定向.scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.command &>filename 重定向 stdout 和 stderr 到文件中command >&2 重定向 command 的 stdout 到 stderrscriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,则创建这个文件.进程替换,具体见"进程替换部分",跟命令替换极其类似.(command)><(command)<和> 可用来做字符串比较<和> 可用在数学计算比较<< 重定向,用在"here document"<<< 重定向,用在"here string"<,> ASCII 比较1 veg1=carrots2 veg2=tomatoes34 if [[ "$veg1" < "$veg2" ]]5 then6 echo "Although $veg1 precede $veg2 in the dictionary,"7 echo "this implies nothing about my culinary preferences."8 else9 echo "What kind of dictionary are you using, anyhow?"10 fi\<,\> 正则表达式中的单词边界.如:bash$grep '\<the\>' textfile| 管道.分析前边命令的输出,并将输出作为后边命令的输入.这是一种产生命令链的好方法.
输出的命令传到脚本中

#!/bin/bashtr 'a-z' 'A-Z'exit 0#调用 ls -l | sh uppercase.sh


eg3-3 在后台运行一个循环

#!/bin/bashfor i in 1 2 3 4 5 6 7 8 9 10do  echo -n "$i "done&echofor i in 11 12 13 14 15 16 17 18 19 20do  echo -n "$i "doneechoexit 0~        
在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键的响应


先执行第2个循环,再执行第一个循环,第一个循环被挂起

&&操作

(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)#cd /source/directory 源目录#tar cf - .  'c'创建一个新文档,'f'后边跟'-'指定目标文件作为 stdout,解压文件至标准输出#(cd /dest/directory && tar xpvf -) 子shell# 'x'解档,'p'保证所有权和文件属性,
第 4 章 变量和参数的介绍
4.1 变量替换

$ 变量替换操作符

注意:$var 与${var}的区别,不加{},在某些上下文将引起错误,为了安全,使用 2.

a=375hello=$a
echo hello # 没有变量引用,不过是个 hello 字符串echo $helloecho ${hello} # 同上
#!/bin/bash hello="A B                    C D"echo $hello # A B C Decho "$hello" # A B C Decho '$hello'hello= # 设置为空值echo "\$hello (null value) = $hello"                                      


注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题。但是还是可以用来做算术运算

echo "$uninitialized" # (blank line)let "uninitialized += 5" # Add 5 to it.echo "$uninitialized"

对于一个空值变量在做算术操作的时候,就好像它的值为 0 一样.

4.2 变量赋值

#!/bin/basha=879echo "The value of \"a\" is $a."# 使用 let 赋值let a=16+5echo "The value of \"a\" is now $a."#在for循环中echo -n "Values of \"a\" in the loop are: "for a in `seq  7 11`do  echo -n "$a "doneecho#在read中echo -n "Enter \"a\""read aecho "The value of \"a\" is now $a."exit 0~        


使用``

a=`echo Hello!` # 把 echo 命令的结果传给变量 aa=`ls -l` # 把 ls -l 的结果给 a

使用$(...)

R=$(cat /etc/passwd)


4.3 Bash 变量是不分类型的
        Bash 并不对变量区分"类型".本质上,Bash 变量都是字符串.

 

#!/bin/basha=2334let "a += 1"echo "a = $a"echo #替换成字母b=${a/23/BB} echo "b = $b" declare -i b # b = BB35echo "b = $b"# b = BB35let  "b += 1"  #b = 1echo "b =$b"echo #替换成数字c=BB34echo "c = $c"d=${c/BB/23} echo "d = $d"# 这使得$d 变为一个整形let "d += 1"echo "d = $d"echo# 为空值 e="" echo "e = $e"let e+=1echo "e = $e" # 为nullecho echo "f = $f"let "f += 1" echo "f = $f"echo


所以说 Bash 中的变量都是无类型

4.4 特殊的变量类型

位置参数

args=$#lastarg=${!args}# 或: lastarg=${!#} 有错??# 注意 lastarg=${!$#} 将报错

#调用bash scriptname.sh 1 2 3 4 5 10

#!/bin/bashargsnum=$#echo $#echo ${argsnum}while [ -n "$1" ]do  echo -n  "$1 "  shiftdone



判断位置参数是否存在

if [ -z $1 ]thenexit $E_MISSING_POS_PARAMfi

另一种表示

如果$1存在且不为空,m就是$1
如果$1不存在或为空,那么m就是DefaultVal

${1:-$DefaultVal}#如果不存在则赋值为DefaultVal
eg 4-7 shift

#!/bin/bashuntil [ -z "$1" ]    #直到所有参数用光do  echo -n "$1 "  shiftdoneechoexit 0

第 5 章 引号

引号的特殊效果就是,保护字符串中的特殊字符不被 shell 或者是 shell 脚本重新解释或者扩展,保护它的字面含义.

cat first.txt


grep '[Ff]irst' *.txt  #在bash下的行为是正则表达式

5.1 引用变量

在一个双引号中直接使用变量名,阻止了所有在引号中的特殊字符的重新解释--包括变量名。但是$,`和\除外.保留$,作为特殊字符的意义,是为了能够在双
引号中也能够正常地引用变量("$var"),这样可以使用便利所表达的值

variable1="a variable containing five words"COMMAND This is $variable1 # COMMAND 将以 7 个参数来执行COMMAND "This is $variable1" # COMMAND 将以 1 个参数来执行variable2="" # 空值COMMAND $variable2 $variable2 $variable2 # COMMAND 将不带参数执行COMMAND "$variable2" "$variable2" "$variable2" # COMMAND 将以 3 个空参数来执行COMMAND "$variable2 $variable2 $variable2" # COMMAND 将以 1 个参数来执行(2 空格)

第 6 章 退出和退出状态

exit 命令被用来结束脚本,就像 C 语言一样.他也会返回一个值来传给父进程,父进程会判断是否可用.

echo $?

第 7 章 Tests

[[ ]]

     ①[[是 bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
    ②支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号
    ③使用[[ ... ]]条件判断结构,而不是[ ... ],能够防止脚本中的许多逻辑错误。比如,&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。比如可以直接使用if [[ $a != 1 && $a != 2 ]], 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]或者if [ $a -ne 1 -a $a != 2 ]。
    ④bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。

Bash 把[[ $a -lt $b ]]看作一个单独的元素,并且返回一个退出码.
((...))和 let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非 0 的时候,他们的退出码将返回 0.

let "1<2" #返回0,在if中使用返回码的值echo $?((0 && 1)) #返回1,但在if中正常使用echo $?

#!/bin/bashecho '(( 0 && 1 )) 'if (( 0 && 1 )) ;then   echo "true"else   echo "false"fiechoecho '[[ 0 && 1 ]]'if [[ 0 && 1 ]] ;then   echo "true"else   echo "false"fiexit 0


#!/bin/bashif cmp $1 $2 &> /dev/null  #可以组织输出then   echo "Files a and b are indentical"else   echo "files a and b are different"fi
if命令能够测试任何命令,并不仅仅是中括号中的条件。
if echo "Next *if* is part of the comparison for the first *if*."if [[ $comparison = "integer" ]]then (( a < b ))#整数else[[ $a < $b ]] #模式匹配fithenecho '$a is less than $b'fi
#!/bin/bashword=Linuxletter_sequence=inuif echo "$word" | grep -q "$letter_sequence"# "-q" 选项是用来禁止输出的.then    echo "$letter_sequence found in $word"else    echo "$letter_sequence not found in $word"fi

条件判断主要判断的是条件是否为真或者假。那么,在什么情况下才为真呢?

#!/bin/bashif  [ 0 ]then    echo "0 is true."else    echo "0 is false."fi# 1 为真if [ 1 ]then    echo "1 is true."else    echo "1 is false."fi# -1 为真if [ -1 ]then    echo "-1 is true."else    echo "-1 is false."fi# NULL 为假if [  ]      then    echo "NULL is true."else    echo "NULL is false."fi# 随便的一串字符为真if [ xyz ]      then    echo "Random string is true."else    echo "Random string is false."fi# 未初始化的变量为假if [ $xyz ]      then    echo "Uninitialized variable is true."else    echo "Uninitialized variable is false."fi# 更加正规的条件检查if [ -n "$xyz" ] #-n来判断一个string不是NULL值     then    echo "Uninitialized variable is true."else    echo "Uninitialized variable is false."fixyz=     # 初始化了, 但是赋null值# null变量为假if [ -n "$xyz" ]      then    echo "Null variable is true."else    echo "Null variable is false."fi# "false" 为真if [  "false" ]      then    echo "\"false\" is true." else    echo "\"false\" is false." fi# "$false" 为假if [  "$false" ]      then    echo "\"\$false\" is true." else    echo "\"\$false\" is false." fi


test 命令是 Bash 的内建命令,用来测试文件类型和比较字符串.因此,在 Bash 脚本中,test并不调用/usr/bin/test 的二进制版本(这是 sh-utils 工具包的一部分).同样的,[并不调用/usr/bin/[,被连接到/usr/bin/test.

#!/bin.bashtype testtype '['type ']'type '[['type ']]'       


test命令

test、/usr/bin/test、[ ]和/usr/bin/[都是等价命令。

#!/bin.bashif test -z "$1"then    echo "No command-line arguments."else    echo "First command-line argument is $1."fiif /usr/bin/test -z "$1"then    echo "No command-line arguments."else    echo "First command-line argument is $1."fi~  

[ 这个命令与test命令等价,把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码(0 表示真,1表示假),如果参数为确定的一个值或者有初始化,则退出状态码为0;否者为空值或者未初始化,则为1。

if [ -z "$1" ] #   if [ -z "$1"                应该能够运行,但是Bash报错, 提示缺少关闭条件测试的右中括号then    echo "No command-line arguments."else    echo "First command-line argument is $1."fiif /usr/bin/[ -z "$1" ]  # if /usr/bin/[ -z "$1"       # 能够工作,但是还是给出一个错误消息。注意:在版本3.x的Bash中, 这个bug已经被修正了then    echo "No command-line arguments."else    echo "First command-line argument is $1."fi

#不用if结构v1=20v2=22[ "$v1" -ne "$v2" ] && echo "$v1 != $v2"



[[ ]]结构比[ ]结构更加通用。这是一个扩展的test命令,是从ksh88中引进的。在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
file=/etc/passwdif [[ -e $file ]]then    echo "Password file exists."fi

使用[[ ... ]]条件判断结构而不是[ ... ],能够防止脚本中的许多逻辑错误。比如&&、||、<和>操作符能够正常存在于[[ ]]条件判断结构中,,但是如果出现在[ ]结构中的话,会报错。


(())结构扩展并计算一个算术表达式的结果.如果表达式的结果为 0,它将返回 1 作为退出码,或
者是"false".而一个非 0 表达式的结果将返回 0 作为退出码,或者是"true".

#!/bin/bash# 算术测试.# (( ... ))结构可以用来计算并测试算术表达式的结果. # 退出状态将会与[ ... ]结构完全相反!(( 0 ))echo "Exit status of \"(( 0 ))\" is $?."         # 1(( 1 ))echo "Exit status of \"(( 1 ))\" is $?."         # 0(( 5 > 4 ))                                      # 真echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0  (( 5 > 9 ))                                      # 假echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1(( 5 - 5 ))                                      # 0echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1(( 5 / 4 ))                                      # 除法也可以.echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0(( 1 / 2 ))                                      # 除法的计算结果 < 1.echo "Exit status of \"(( 1 / 2 ))\" is $?."     # 截取之后的结果为 0.                                                    # 1(( 1 / 0 )) 2>/dev/null                          # 除数为0, 非法计算. #            ^^^^^^^^^^^echo "Exit status of \"(( 1 / 0 ))\>\/dev\/null\" is $?."     # 1# "2>/dev/null"起了什么作用?# 如果这句被删除会怎样?(( 1 / 0 )) echo "Exit status of \"(( 1 / 0 ))\" is $?."     #1 exit 0



7.2文件测试操作

-e 文件存在-a 文件存在,这个选项的效果与-e相同. 但是它已经被”弃用”了, 并且不鼓励使用.-f 表示这个文件是一个一般文件(并不是目录或者设备文件)-s 文件大小不为零-d 表示这是一个目录-b 表示这是一个块设备(软盘、光驱等。)-c 表示这是一个字符设备(键盘, modem, 声卡, 等等.)-p 这个文件是一个管道-h 这是一个符号链接-L 这是一个符号链接-S 表示这是一个socket-t 文件(描述符)被关联到一个终端设备上。这个测试选项一般被用来检测脚本中的stdin([ -t 0 ]) 或者stdout([ -t 1 ])是否来自于一个终端.-r 文件是否具有可读权限(指的是正在运行这个测试命令的用户是否具有读权限)-w 文件是否具有可写权限(指的是正在运行这个测试命令的用户是否具有写权限)-x 文件是否具有可执行权限(指的是正在运行这个测试命令的用户是否具有可执行权限)-g set-group-id(sgid)标记被设置到文件或目录上。如果目录具有sgid标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组,而不必是创建这个文件的用户组. 这个特性对于在一个工作组中共享目录非常有用。-u set-user-id (suid)标记被设置到文件上。如果一个root用户所拥有的二进制可执行文件设置了set-user-id标记位的话,那么普通用户也会以root权限来运行这个文件。这对于需要访问系统硬件的执行程序非常有用。如果没有suid标志的话,这些二进制执行程序是不能够被非root用户调用的。对于设置了suid标志的文件,在它的权限列中将会以s表示。-k 设置粘贴位。粘贴位设置在目录中,它将限制写权限,在它们的权限标记列中将会显示t。如果用户并不拥有这个设置了粘贴位的目录,但是他在这个目录下具有写权限,那么这个用户只能在这个目录下删除自己所拥有的文件。这将有效的防止用户在一个公共目录中不慎覆盖或者删除别人的文件,比如说/tmp目录。-O 判断你是否是文件的拥有者-G 文件的group-id是否与你的相同-N 从文件上一次被读取到现在为止,文件是否被修改过f1 -nt f2 文件f1比文件f2新f1 -ot f2 文件f1比文件f2旧f1 -ef f2 文件f1和文件f2是相同文件的硬链接! 反转上边所有测试的结果(如果没给出条件,那么返回真)。

Example 7-4 test 死的链接文件

#!/bin/bash[ $# -eq 0 ] && directorys=`pwd` || directorys=$@linkchk(){   for element in $1/*   do     [ -h "$element" -a ! -e "$element" ] && echo \"$element\"     [ -d "$element" ] && linkchk $element   done}for directory in $directorys;do    if [ -d $directory ]    then      linkchk $directory  #递归函数    else      echo "$directory is not a directory"    fidoneexit 0

二元比较操作符,用来比较两个变量或数字:-eq 等于 if [ "$a" -eq "$b" ]-ne 不等于 if [ "$a" -ne "$b" ]-gt 大于 if [ "$a" -gt "$b" ]-ge 大于等于 if [ "$a" -ge "$b" ]-lt 小于 if [ "$a" -lt "$b" ]-le 小于等于 if [ "$a" -le "$b" ]< 小于(在双括号中使用) (("$a" < "$b"))<= 小于等于(在双括号中使用) (("$a" <= "$b"))> 大于(在双括号中使用) (("$a" > "$b"))>= 大于等于(在双括号中使用) (("$a" >= "$b"))-a 逻辑与,一般都是和test命令或者是单中括号结构一起使用的 if [ "$exp1" -a "$exp2" ]-o 逻辑或,一般都是和test命令或者是单中括号结构一起使用的 if [ "$exp1" -o "$exp2" ]字符串比较:= 等于 if [ "$a" = "$b" ]==等于 if [ "$a" == "$b" ] 与=等价!= 不等号 if [ "$a" != "$b" ] 这个操作符将在[[ ... ]]结构中使用模式匹配< 小于,按照ASCII字符进行排序 if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ] 注意使用在[ ]结构中的时候需要被转义。> 大于,按照ASCII字符进行排序 if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ] 注意使用在[ ]结构中的时候需要被转义。-z 字符串为”null”,意思就是字符串长度为零-n 字符串不为”null”,当-n使用在中括号中进行条件测试的时候,必须要把字符串用双引号引用起来。

注意:==比较操作符在双中括号对和单中括号对中的行为是不同的。

#!/bin/basha=4b=5if [ "$a" -ne "$b" ]# "4" != "5"then   echo "$a is not equal to $b"   echo "(arithmetic comparison)"fiechoif [ "$a" != "$b" ]# ASCII 52 != ASCII 53then   echo "$a is not equal to $b"   echo "(arithmetic comparison)"fi   

Example 7-6 测试字符串是否为 null

如果一个字符串没被初始化,那么它就没有定义的值(像这种话,总感觉像屁话)
这种状态叫做"null"(与 zero 不同)

#!/bin/bash#  str-test.sh: 检查null字符串和未引用的字符串if [ -n $string1 ]    # $string1 没有被声明和初始化,这里相当于[-n],所以为truethen    echo "String \"string1\" is not null."else      echo "String \"string1\" is null."fiif [ -n "$string1" ]  # 这次$string1被引号扩起来了.then    echo "String \"string1\" is not null."else      echo "String \"string1\" is null."fiif [ $string1 ]       # 这次,就一个$string1,什么都不加then    echo "String \"string1\" is not null."else      echo "String \"string1\" is null."fi# [ ] 测试操作符能够独立检查string是否为null,然而,使用("$string1")是一种非常好的习惯# if [ $string1 ]    只有一个参数 "]"# if [ "$string1" ]  有两个参数,一个是空的"$string1",另一个是"]" string1=initializedif [ $string1 ]        # 再来,还是只有$string1,什么都不加then    echo "String \"string1\" is not null."else      echo "String \"string1\" is null."fi# 这个例子运行还是给出了正确的结果,但是使用引用的("$string1")还是更好一些string1="a = b"if [ $string1 ]        # 再来,还是只有$string1,什么都不加,a=b的退出码为0then    echo "String \"string1\" is not null."else      echo "String \"string1\" is null."fi# 未引用的"$string1",这回给出了错误的结果exit 0



0 0