第二部分 基本
第三章 特殊字符
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
^ 行首,正则表达式中表示行首。