shell高级编程笔记(第三章 特殊字符)

    技术2022-07-11  83

    第二部分 基本

    第三章 特殊字符

    1

    “#” 注释 行首以#开头为注释(#!是个例外)。

    注释也可以存在与本行命令的后边。

    echo "A comment will follow" #注释在这里

    在echo命令中被转义的#是不能作为注释的。同样的,#也可以出现在特定的参数替换结构中或者是数字常量表达式中。 标准的引用和转义字符("’)可以用来转义#。

    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 ${PATH#*:} #参数替换 echo $(( 2#101011 )) #数制转换

    2

    “;” 命令分隔符,可以用来在一行中写多个命令。

    echo hello;echo there if [ -x "$filename" ];then #注意:if和then需要分隔 echo "File $filename exists.";cp $filename $filename.bak else echo "File $filename not found";touch $filename fi;echo "File test complete."

    3

    “;;” 终止case选项。

    case "$variable" in abc) echo "\$variable = abc";; xyz) echo "\$variable = xyz";; esac

    4

    4.1

    “.” .命令等价于source命令(见Example 11.20)。这是一个bash的内建命令

    4.2

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

    [root@localhost shell]# ls -l total 0 -rw-r--r--. 1 root root 0 Jun 30 20:05 test.sh [root@localhost shell]# ls -al total 8 drwxr-xr-x. 2 root root 4096 Jun 30 20:05 . drwxr-xr-x. 4 root root 4096 Jun 30 20:04 .. -rw-r--r--. 1 root root 0 Jun 30 20:05 .doc.txt -rw-r--r--. 1 root root 0 Jun 30 20:05 test.sh

    4.3

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

    [root@localhost shell]# pwd /opt/shell [root@localhost shell]# cd . [root@localhost shell]# pwd /opt/shell [root@localhost shell]# cd .. [root@localhost opt]# pwd /opt

    4.4

    . .字符匹配,这是作为正则表达式的一部分,用来匹配任何单个字符。

    5

    " 部分引用。"字符串"阻止了一部分特殊字符,具体见第五章。

    6

    ’ 全引用。'字符串’阻止了全部特殊字符,具体见第五章。

    7

    , 逗号连接了一系列的算术操作,虽然里边所有的内容都被运行了,但只有最后一项被返回。如:

    let "t2=((a=9,15/3))" #set "a=9" and "t2=15/3" echo $t2 #5

    8

    \ 转义字符,如\X等价于"X"或’X’,具体见第五章。

    9

    / 文件名路径分隔符或用来做出发操作。

    10

    ` 后置引用,命令替换,具体见第十四章。

    11

    : 空命令,等价于"NOP"(no op,一个什么也不干的命令)。也可以被认为与shell的内建命令(true)作用相同。

    11.1

    “:” 命令是一个bash的内建命令,它的返回值为0,就是shell返回的true。 如:

    echo $? #返回值为0

    在死循环中:

    while : do operation-1 operation-2 ... operation-n done

    在if/then中的占位符:

    if 条件 then : #什么都不做,引出分支 else take-some-action fi

    在一个2元命令中提供一个占位符,如:

    : ${username=`whoami`} # ${username=`whoami`} 如果没有":"的话,将给出一个错误,除非username是个命令

    使用"参数替换"来评估字符串变量,如:

    : {HOSTNAME?}${USER?}${MAIL?} #如果一个或多个必要的环境变量没被设置的话,就打印错误信息

    11.2

    变量扩展/子串替换 在和>(重定向)结合使用的时候,把一个文件截断到0长度,没有修改它的权限;如果文件在之前不存在,那么就创建它。如:

    : > data.txt #文件data.txt现在被清空 #与cat /dev/null > data.txt的作用相同,但是不会产生一个新的进程,因为":"是一个内建命令

    在和>>重定向操作符结合使用时,将不会对想要附加的文件产生任何影响;如果文件不存在,将创建。

    注意:这只是用于正规文件,而不是管道、符号连接和某些特殊文件。也可能作为注释行,虽然不推荐这么做。使用#来注释的话,将关闭剩余行的错误检查,所以可以在注释行中写任何东西。然而使用:的话不会这样。如:

    : This is a comment thar generates an error,(if [ $x -eq 3 ])

    11.3

    : 还用来在/etc/passwd和$PATH变量中用来做分隔符。

    [root@localhost opt]# echo $PATH /usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/root/bin

    12

    ! 取反操作符,将反转"退出状态"结果,(见Example 6.2)。也会反转test操作符的意义。比如修改=为!=。!=操作是Bash的一个关键字。

    在一个不同的上下文中,!也会出现在"间接变量引用",见"Example 9.22"。

    13

    13.1

    万能匹配字符,用于文件名匹配(这个东西有个专有名词叫file globbing),或者是正则表达式中。 注意:在正则表达式匹配中的作用和在文件名匹配中的作用是不同的。 [root@localhost shell]# ls -al total 8 drwxr-xr-x. 2 root root 4096 Jun 30 20:05 . drwxr-xr-x. 4 root root 4096 Jun 30 20:04 .. -rw-r--r--. 1 root root 0 Jun 30 20:05 .doc.txt -rw-r--r--. 1 root root 0 Jun 30 20:05 test.sh [root@localhost shell]# echo * test.sh

    13.2

    数学乘法

    14

    ? 测试操作。在一个确定的表达式中,用?来测试结果。

    (())结构可以用来做数学计算或者写C代码,那?就是C语言的3元操作符的一个。

    ? 在"参数替换"中,?测试一个变量是否被set了

    ? 在文件扩展匹配(file globbing)中和正则表达式中匹配任意的单个字符

    15

    $ 变量替换

    var1=5 var2=23skidoo echo $var1 echo $var2

    $ 在正则表达式中作为行结束符 ${} 参数替换,见9.3节 ∗ , *, ,@ 位置参数 ? 退 出 状 态 。 ? 退出状态。 ?退?保存一个命令或一个函数或脚本本身的退出状态。 进 程 I D 变 量 。 这 个 进程ID变量。这个 ID变量保存运行脚本进程ID。

    16

    () 命令组

    (a=hello;echo $a)

    注意:在()中的命令列表将作为一个子shell来运行。 在()中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的。 如:

    a=123 (a=321;) echo "a = $a" #a=123

    在()内,a变量更像是一个局部变量

    用在数组初始化,如:

    Array=(element1,element2,element3)

    17

    {xxx,yyy,zzz…} 大括号扩展 如:

    cat {file1,file2,file3} > combined_file #把file1,file2,file3连接在一起,并重定向到combined_file中 cp file22.{txt,backup} #拷贝file22.txt到file22.backup中

    一个命令可能会对大括号中的以逗号分隔的文件列表起作用 注意:在大括号中,不允许有空白,除非这个空白是有意义的

    echo {file1,file2}\:{\A,"B",'C'} file1:A file1:B file1:C file2:A file:B file:C

    18

    {} 代码块 又被称为内部组。事实上,这个结构创建了一个匿名的函数;但是与函数不同的是,在其中声明的变量,对于脚本的其它部分代码来说还是可见的。 注意:与()中的命令不同的是,{}中的代码块将不能正常地开启一个新shell 如:

    [root@localhost shell]# { > local a; > a=1; > } bash: local: can only be used in a function #bash中local申请的变量只能够用在函数中。 a=123 {a=321;} echo "a=$a" #a=321.说明在代码块中对变量a所做的修改,影响了外边的变量a

    下面的代码展示了在{}结构中代码的I/O重定向 Example 3.1 代码块和I/O重定向

    #!/bin/bash #从/etc/passwd中读行 File=/etc/passwd { read line1 read line2 } < $File echo "First line in $File is:" echo "$line1" echo echo "Second line in $File is:" echo "$line2" exit 0

    Example 3.2 将一个代码块的结果保存到文件(检测rpm包的信息)

    #!/bin/bash #rpm-check.sh #这个脚本的目的是为了描述列表和确定是否可以安装一个rpm包 #在一个文件中保存输出 #这个脚本使用一个代码块来展示 SUCCESS=0 E_NOARGS=65 if [ -z "$1" ];then echo "Usage:`basename $0` rpm-file" exit $E_NOARGS fi { 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 "Results of rpm test in file $1.test" exit 0

    之前的IMEI监控脚本改进版(其中的"###"分割线不适用IMEI平台的报表)

    #!/bin/bash #获取当前时间(2020-06-22 16:01:39) data=`date +'%Y-%m-%d %H:%M:%S'` #文件名时间(20200622) data2=`date +%Y%m%d` #存放文件 LOG_FILE=/data/data/outtable/DIMEI_JK_9001$data2.txt ROOT_UID=0 E_NOROOT=11 #判断是否是根用户 if [ $UID -ne $ROOT_UID ];then echo "Root user is required to run this script" >> $LOG_FILE exit $E_NOROOT fi #判断存放数据的文件是否存在 [ -f $LOG_FILE ] || touch $LOG_FILE { echo "########## start ##########" #磁盘监控( 根分区 ) #disk_use=`df -h | grep "/dev/mapper/vg_zhhs-lv_root" | awk '{printf $5}' | cut -d '%' -f 1` disk_use=`df -h | sed -n 3p | awk '{printf $4}' | cut -d '%' -f 1` echo "$data:root_partion:$disk_use%" #磁盘监控( boot分区 ) disk_use2=`df -h | grep "/dev/sda1" | awk '{printf $5}' | cut -d '%' -f 1` echo "$data:boot_partion:$disk_use2%" #cpu监控 cpu_average=`top -b -n 1 | grep "load" | awk '{printf $12}' | cut -d "." -f 1` echo "$data:CPU:$cpu_average%" #内存监控 men_use=`free | awk '/Mem/{printf("%.2f\n"), $3/$2*100}' | cut -d "." -f 1` echo "$data:Memory:$men_use%" echo "########## end ##########" echo } >> $LOG_FILE exit 0

    19

    {} ; 路径名。一般都在find命令中使用。这不是一个shell内建命令。 注意:;号用来结束find命令序列的-exec选项。

    20

    20.1

    [] test。test的表达式在[]中。 值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令的一个连接。

    20.2

    [] 数组元素

    Array[1]=slot_1 echo${Array[1]}

    20.3

    [] 字符范围。在正则表达式中使用,作为字符匹配的一个范围。

    20.4

    [[]] test表达式放在[[]]中。(shell关键字)。具体查看[[]]结构的讨论。

    21

    (()) 数学计算的扩展。 在(())结构中可以使用一些数字计算。具体参阅((…))结构。

    22

    “>&>>&>><” 重定向

    scriptname > filename #重定向脚本的输出到文件中。覆盖文件原有的内容 command &> filename #重定向stdout(标准输出)和stderr(标准错误)到文件中 command >&2 #重定向command的stdout到stderr scriptname >> filename #重定向脚本的输出到文件中。添加到文件末尾,如果没有文件就新创建。

    23

    | 管道。分析前边命令的输出,并将输出作为后边命令的输入。这是一种产生命令链的好方法。

    cat *.lst | sort | uniq #合并和排序所有的.lst文件,然后删除所有重复的行

    输出的命令传递到脚本中。如:

    #!/bin/bash #uppercase.sh : 修改输出,全部转换为大写 tr 'a-z' 'A-Z' #字符范围必须被""引用起来,来阻止产生单字符的文件名 exit 0

    输送ls -l 的输出到uppercase.sh脚本中

    ]# ls -l | sh uppercase.sh 总用量 156736 -RW-R--R-- 1 ROOT ROOT 160480880 6月 19 15:32 MYSQL-COMMUNITY-SERVER-5.7.23-1.EL6.X86_64.RPM -RW-R--R-- 1 ROOT ROOT 7814 6月 19 15:35 MYSQL-COMMUNITY-SERVER-5.7.23-1.EL6.X86_64.RPM.TEST -RWXR-XR-X 1 ROOT ROOT 702 6月 19 15:35 TEST.SH -RW-R--R-- 1 ROOT ROOT 166 6月 19 15:58 UPPERCASE.SH

    24

    || 或-逻辑操作。

    25

    & 后台运行。一个命令后边跟一个&,将表示在后台运行。

    Example 3.3 在后台运行一个循环

    #!/bin/bash #background-loop.sh for i in 1 2 3 4 5 6 7 8 9 10 do echo -n "$i" done& echo #这个'echo'某些时候将不会显示 for i in 11 12 13 14 15 16 17 18 19 20 do echo -n "$i" done echo #这个'echo'某些时候将不会显示 #期望的输出应该是 #1 2 3 4 5 6 7 8 9 10 #11 12 13 14 15 16 17 18 19 20 #然而实际的结果可能是(我的是这个结果) #11 12 13 14 15 16 17 18 19 20 #1 2 3 4 5 6 7 8 9 10 #(第2个'echo'没执行,为什么?) #也可能是 #1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #(第1个'echo'没执行,为什么?) #非常少见的执行结果,也有可能是: #11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20 #前台的循环先于后台的执行 exit 0 #试着在echo -n "$i"之后加sleep 1;将看到一些乐趣

    注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键响应。后文中有这个问题的解决办法

    26

    && 与-逻辑操作

    27

    27.1

    “-” 选项,前缀。在所有的命令内如果想使用选项参数的话,前边都要加上"-"。

    COMMAND -[Option1][Option2][...]

    27.2

    “-” 用于重定向stdin和stdout

    (cd /source/directory && tar -cf - .) | (cd /dest/directory && tar -xpvf -) #从一个目录移动整个目录树到另一个目录(此脚本只是为了演示'-'重定向的用法,便于理解) #cd /source/directory 源目录 #&& 与操作,如果cd车成功了,那么就执行下面的命令 #tar -cf - . 'c':创建一个新文档;'f'后面跟'-'指定文件作为stdout;'-'后面的file选项,指明作为stdout的目标文件,并且在当前目录('.')执行 #| 管道 #(...) 一个子shell #cd /dest/direstory 改变当前目录到目标目录 #&& #tar -xpvf - 'x'解档,'p'保证所有权和文件属性,'v'发完整消息到stdout,'f'后面跟'-',从stdin读取数据 #注意:'x'是一个命令,'p','v','f'是选项 #更优雅的写法应该是 #cd /dest/direstory #tar -cf - . |(cd ../dest/direstory;tar -xpvf -) #当然也可以这样写: #cp -a /source/directory/* /dest/directory # 或者: #cp -a /source/directory/* /source/directory/.[^.]* /dest/directory #如果/source/directory中有隐藏文件的话。 bunzip2 linux-2.6.13.tar.bz2 | tar -xvf - #bunzip2 linux-2.6.13.tar.bz2 = linux-2.6.13.tar #tar -xvf linux-2.6.13.tar

    Example 3.4 备份最后一天所有修改的文件

    #!/bin/bash #在一个"tarball"中(经过tar和gzip处理过的文件)备份最后24小时当前目录下所有修改的文件 BACKUPFILE=backup-$(date +%m-%d-%Y) #在备份文件中嵌入时间 archive=${1:-$BACKUPFILE} #如果在命令行中没有指定备份文件的文件名,那么将默认使用"backup-MM-DD-YYY.tar.gz" #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #这里我给出一个示例,便于理解${1:-$BACKUPFILE} #b=${a:-456} #如果a未定义, 则将456做为默认值, a仍保持未定义。 #echo $b 456 #echo $a 空 #b=${a:=789} #如果a未定义, 则将789做为默认值, 并且将a的值设置为789 #echo $b 789 #echo $a 789 #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tar cvf - `find . -mtime -1 -type f -print` > $archive.tar gzip $archive.tar echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"." #如果在发现太多文件的时候,或者是文件名包括空格的时候,将执行失败 #Stephane Chazelas建议使用下面的两种代码之一 #-------------------------------------------------------------------- #find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar" #使用gnu版本的find # #find . -mtime -1 -type f -exec tar rvf "$archive.tar" {} \; #对于其他风格的UNIX便于移植,但是比较慢 #-------------------------------------------------------------------- exit 0

    27.3

    “-” 之前工作的目录。"cd -“将回到之前的工作目录,具体参考”$OLDPWD"环境变量。

    27.4

    “-” 算术减号

    28

    “=” 算术等号

    29

    29.1

    “+” 算术加号,也用在正则表达式中。

    29.2

    “+” 选项,对于特定的命令来说,使用"+“来打开特定的选项,用”-"关闭特定的选项。

    30

    “%” 算术取模运算。也用在正则表达式中。

    31

    31.1

    ~ home目录。相当于$HOME变量。~bozo是bozo的home目录。

    31.2

    ~+ 当前工作目录,相当于$PWD变量。

    31.3

    ~- 之前的工作目录,相当于$OLDPWD内部变量。

    31.4

    =~ 用于正则表达式,这个操作将在正则表达式匹配部分讲解,只有versio3才支持

    32

    ^ 行首,正则表达式中表示行首。

    Processed: 0.012, SQL: 9