shell脚本基本处理

Posted ttlxihuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shell脚本基本处理相关的知识,希望对你有一定的参考价值。

Shell是一个用C语言编写的程序,并且提供了专用命令语言。shell是linux系统必备工具(其它有些系统也有类似或相同的替代工具),在linux系统里打开终端或者使用ssh连接时都是使用命令语言作为交互支撑。


shell版本很多各有优缺点,列出几个了解下:

  1. Bourne Shell(/usr/bin/sh或/bin/sh)
  2. Bourne Again Shell(/bin/bash)
  3. C Shell(/usr/bin/csh)
  4. K Shell(/usr/bin/ksh)
  5. Shell for Root(/sbin/sh)
    ...

shell脚本可以辅助完成很多功能,通常把一些常用操作写成一个脚本方便后续使用。
shell脚本有一套简单灵活的命令语法规则,在使用时需要注意细节,否则容易掉沟里。


脚本声明

sehll脚本规定在有效代码前需要指定执行下面代码工具名,通过 #! 进行标记,后面追加写脚本位置,如果是bash工具,则:

#!/bin/bash

注意: /bin/bash 是bash命令所在的目录


注释

合理的注释方便理解,上面的命令标记也是注释,类似python2指定编码一样。
shell只有单行注释,没有专用的多行注释。单行注释以 # 作为起始,只要 # 不在有效命令或字符串内均会解析为注释,注释不会参与和影响脚本执行。例如:

#注释要说明的内容写在这里

在实际使用中如果想多行注释一般建议通过多个单行注释拼接。


执行脚本

脚本运行需要有对应的解析执行工具,调用工具去解析执行shell脚本即为执行脚本(即使用bash工具执行指定的shell脚本)。脚本的启动方式会影响到脚本执行环境的差异。启动shell脚本也是一条命令,在脚本内也可以使用(但不要死循环启动)。
以执行脚本 test.sh 为例列举部分调用方式:

bash

bash(或sh)方式调用是创建一个新的执行环境执行shell脚本,不能共享脚本外面的变量(除环境变量外),这种方式调用脚本内的exit命令不会影响外部(外部就是执行bash test.sh的环境,也可以理解为bash是创建了一个子进程,子进程的操作不影响父进程)。
执行命令:

bash test.sh

source

source方式调用是在当前环境下执行shell脚本(即不创建一个新的执行环境),共享当前环境可见变量。
执行命令:

source test.sh

./

这种方式调用处理方式与bash一样,但要求脚本文件有可执行权限且能匹配脚本声明寻找对应脚本再执行。
修改权限:

chmod +x test.sh

执行命令:

./test.sh

调用命令

调用命令是shell脚本最基本语句操作。一条完整的语句代码是由换行或分号隔开(并不是有换行或分号就代表语句结束),一条语句中可以包含一个及以上的命令执行。了解基本调用对编写脚本有很大帮助。
shell脚本有多种调用命令方式,一般有:反引号、$()、命令调用、nohup调用、直接调用、串联调用。

反引号

这种调用方式主要用于提取命令结果,其调用的命令处理方式与bash一样。反引号就相当于一个在新创建的执行环境中增加了一个标准输出接收暂存器(只暂存标准输出,具体的请了解重定向)。反引号最终返回一个字符串值一般需要指定变量接收赋值或者当作命令参数。
反引号内可以包含多个完整的执行命令,不同的命令可以使用 ; 或换行分隔,不可嵌套,比如:

# 执行反引号内命令,并将命令内的标准输出再打印出来
echo -e `echo \'012\'; echo \'123\'
echo \'345\'
`
# 执行反引号内命令,并将命令内的标准输出内容赋值给变量SHELL_STR
SHELL_STR=`echo \'012\'`

反引号会将提取到的结果中的进行转义处理,也就是说如果结果中有转义的字符会再次转义,比如:

echo `echo \'\\\\\'`  #打印输出 \\
echo \'\\\\\'    #打印输出 \\\\

$()

这种调用方式与反引号一样,但不会转义内部命令标准输出内容。
执行命令:

echo $(echo \'\\\\\')  #打印输出 \\\\
SHELL_STR=$(echo \'012\') #把打印结果赋值给变量SHELL_STR

命令调用

命令调用命令多用于拼装命令执行(比如:初始化变量,动态获取外部不同变量、复杂命令等)。
执行命令:(注意前面几个是在当前执行环境内执行并影响,最后两个示例非当前环境执行)

# 通过eval调用ps命令查看mysql服务是否开启
# eval无自带参数命令,其后面所有内容均当要执行的命令及参数处理,也就是说后面的命令可以不使用引号(但多条组合命令需要使用引号)
eval "ps -C mysql"

# 通过exec调用ps命令查看mysql服务是否开启
# exec自带有参数命令,其后面指定要执行的命令如果有带参数就必需使用引号
exec "ps -C mysql"

# 通过xargs调用echo打印run.log文件内容
# xargs一般使用管道模式居多,但也可以指定文件参数传入后面的命令
xargs -a run.log echo

# 通过bash调用ps命令查看mysql服务是否开启
bash -c "ps -C mysql"

# 通过ssh登录127.0.0.1服务器,调用ps命令查看服务器里mysql服务是否开启
ssh root@127.0.0.1 "ps -C mysql"

# 通过bash调用ps命令查看mysql服务是否开启
# 这种用法一般推荐在脚本中使用,可以定义很多命令(如果执行一个shell脚本一样)
# 代码里的<<是重写向内容,具体查看下面介绍
bash <<CMD
# 这里好比一个shell脚本,可以写任何shell代码(但不能有结束符,即最后的CMD)
# 这里会在创建新的执行环境下运行代码
ps -C mysql
CMD

nohup调用

nohup用于在系统后台不挂断地运行命令,退出终端不会影响程序的运行(很多用户态shell脚本或服务需要长期运行均使用nohup启动),nohup会创建一个新执行环境去调用后面的命令。
基本使用语法

# command 是要执行的命令
# [argv ...] 是要执行的命令参数集
# & 是切换到后台执行(非必需)
# 注意:默认nohup命令会在当前目录下创建一个nohup.out文件用于保存要执行的命令所有标准输出内容(可以通过重定向修改保存的文件)
nohup command [argv ...] [&]

示例:

# 后台执行ps命令,nohup会提示以下内容,ps所有标准输出内容都保存在当前目录下nohup.out文件中
# nohup: ignoring input and appending output to `nohup.out\'
nohup ps aux

# 切换到后台执行nohup,nohup将在后台执行ps命令,ps所有标准输出内容都保存在当前目录下nohup.out文件
nohup ps aux&

# 后台执行ps命令,nohup会提示以下内容,ps所有标准输出内容都保存在/var/nohup.log文件中
# nohup: ignoring input and redirecting stderr to stdout
nohup ps aux >/var/nohup.log

直接调用

直接调用最常用的调用方式是:命令 参数集 ...
示例:

# find 是命令,后面的全部是参数集
find ./ -name \'*.sh\'

当一句代码会产生字符串值时未被接收给变量赋值或当作命令参数使用时就会被当作一个命令执行,一般所有字符串都需要有显示指定使用,否则就会被解析为命令执行。
示例:

# 赋值操作
SHELL_TEMP_STR="find ./ -name \'*.sh\'" # 赋值操作,不会执行find命令
COMMAND_PARAM=\'-las\' # 定义命令参数变量
ls $COMMAND_PARAM  #执行命令并指定变量参数

# 字符串命令执行
"find ./ -name \'*.sh\'"      # 定义的字符串未显示指定使用,将以命令代码执行
`echo "find ./ -name \'*.sh\'"`  # 定义的字符串未显示指定使用,将以把echo打印的内容当命令代码执行
\'ls -a\'    # 定义的字符串未显示指定使用,将以命令代码执行find命令
COMMAND_NAME=\'ls\' # 定义命令变量
$COMMAND_NAME -las  # 执行命令

实际写shell脚本时需要格外注意上面这种执行方式。实际还有很多花样方式,这里就不全部列出。

串联调用

串联调用是在以上调用命令的基础上扩展的一种,这种调用方式可以把多个独立的语句串联为一条语句执行。串联调用是通过 ||&&|& 运算符把多条语句连接在一起。串联调用可以混合使用运算符,同时也能使用括号(运算符是特殊字符在串联命令时要求必需使用空格隔开,分开便于阅读)。

#去掉字符串内所有空格
STRING_TEST=\'a b c\' ;
echo $STRING_TEST|sed -r \'s/\\s+//g\'

#提取字符串内script
STRING_TEST=\'shell script\' ;
echo $STRING_TEST|grep -oP \'script\'

> 注意:管道符之后的命令是在一个新执行环境中运行命令(并将最后一个命令的退出值返回出来),也就是管道符后面的命令不影响当前环境变量。
#### &
切入后台运行调用。在一条完整语句或命令后面增加&后,这条语句或命令将切换到后台执行(即在另外一个进程里执行这条语句或命令)。由于不在一个执行环境内执行所以互不影响(切换到后台执行后当前终端关掉将自动退出,如果不希望退出则需要配合nohup命令)
注意:&在shell脚本中类型是单运算符,即在&只会切换前面的命令或语句到后台不影响后面的命令或语句。
示例:
```bash
# 在后台关闭sshd服务
service sshd stop&

# 在后台同时关闭sshd和nginx两个服务
service sshd stop& service sshd nginx&

! 调用

! 是一个特殊命令,实际上不具备调用命令功能,它只是一个 \\$? 值取反处理命令(即 \\$? 值为0时 ! 命令退出1,反之退出0),但 ! 命令必需与其它语句组合成一个取反语句(可以 ! 命令多个并列使用),即 ! 命令不能单独使用。

示例:

# 当在/var/log/messages文件中搜索不到shell字符时,输出0,否则输出1
! grep -qP \'shell\' /var/log/messages && echo \'不包含shell字符\' || echo \'包含shell字符\'

# 判断sshd服务是否启动
# 注意:在用特殊命令[]或[[]]时!需要放在中括号内
[ ! -z "`ps aux|grep \'sshd\'`" ] && echo \'sshd已经启动\' || echo \'sshd未启动\'

# 以下使用!命令会报错,因为!后面的命令是引号字符串形式
! "grep -qP \'shell\' /var/log/messages";  # 命令将报错

变量

shell变量不需要提前声明,除非是局部变量,在使用未定义变量是获取的值是空的。变量的命名一般使用大写加下划线方式,变量名必需是下划线或大小写字母开头后面可以是下划线、大小写字母、数值。
shell常用有三种数据类型:字符串、数值、数组。多数是以字符串为主。

变量作用域

shell变量作用域主要分为:全局变量、局部变量、局部特殊shell变量、环境变量。
shell脚本不同的调用方式会影响与上级环境的全局变量互通,具体可以了解下:执行脚本。

全局变量

全局变量作用域是在当前脚本执行环境内可见可修改(脚本执行环境参考:执行脚本)。并且在任何当前环境内任何地方创建或赋值均可被后面执行的命令使用。
例如:

SHELL_TEST_VAL=0  #定义全局变量并赋值

局部变量

局部变量只能用于函数内,当函数执行完后内部的局部变量全部释放。局部变量需要显示声明为局部且以local关键字为标识。
例如:

local SHELL_TEST_VAL=0  #定义局部变量并赋值

环境变量

环境变量是通过export命令注册,注册后环境变量会在当前作用域内及当前作用域下调用的shell脚本内有效。它会当前作用域下每个新创建的shell执行环境内重新复制环境变量(只有export才可以修改下次复制这个变量的值)。
注册示例:

export SHELL_TEST_VAL1=\'123\'  # 注册并赋值变量SHELL_TEST_VAL1
export SHELL_TEST_VAL2  # 注册变量SHELL_TEST_VAL2

局部shell环境变量

局部shell环境变量是脚本在执行过程中自动生成的一种特殊局部环境变量,这些变量只在脚本内相同执行环境下可见有效(同一个脚本\\$\\$、\\$0任何时刻不变,函数内部分变量除外)。当脚本运行后就会自动生成这些参数。
具体的变量表:
参数名 使用说明
\\$* 脚本或函数所有参数集,从$1开始,各参数集中,如:"$1 $2 $3 ..."
\\$@ 脚本或函数所有参数集,从$1开始,各参数分开,如:"$1" "$2" "$3" "..."
\\$# 传递到脚本或函数的参数个数
\\$\\$ 当前脚本运行的进程ID号
\\$? 显示最近一个命令的退出或函数返回状态。0表示没有错误,其他任何值表明有错误。
\\$0 当前脚本或函数文件名
\\$n n>0是,获取指定序号的脚本或函数参数值
\\${n} 与$n一个意思,当n>9时必需使用这种方式

变量声明

全局变量

全局变量在脚本执行的当前作用域内任何地方有效。全局变量可在以脚本下或函数内声明并赋值。
示例:

BASH_VAL=0  #创建并赋值全局变量 BASH_VAL
BASH_VAL=\'0\'  #创建并赋值全局变量 BASH_VAL
BASH_VAL="0"  #创建并赋值全局变量 BASH_VAL
BASH_VAL=(0 1)  #创建并赋值全局变量 BASH_VAL

局部变量

局部变量是提供给脚本内定义的函数使用临时变量,函数执行完后变量释放且不影响(当有外部的全局变量与内部的局部变量同名时将以局部变量处理),函数的使用参考下面。

local BASH_VAL1 BASH_VAL2 #声明局部变量 BASH_VAL1 BASH_VAL2
BASH_VAL1=0  #给局部变量BASH_VAL1赋值

local BASH_VAL1 BASH_VAL2=0 #声明局部变量并给BASH_VAL2赋值

字符串

字符串定义方式有:单引号、双引号、无引号、上面的反引号等。定义的符串必需显示指定使用,只定义没有显示指定为命令参数或赋值操作会解析为命令进行执行。

单引号字符串

单引号不会转义任何字符,也就是转义符 \\ 在单引号内没有转义功能。在单引号字符串内定义是什么样的就是什么样的,所以单引号字符串不能出现不成对的单引号。
示例:

STR_TEMP1=\'\'  #创建一个空字符串变量STR_TEMP1
echo \'\\\\\'  #输出 \\\\

双引号字符串

双引号字符串支持:转义、变量、嵌套反引号。
示例:

STR_TEMP0=""  #创建一个空字符串变量STR_TEMP0
STR_TEMP1="${PATH}"  #创建一个字符串变量STR_TEMP1,并赋值环境变量PATH
STR_TEMP2="\\\\"  #创建一个字符串变量STR_TEMP2,并赋值 \\
STR_TEMP3="test `echo \'ok\'` ${STR_TEMP0} $STR_TEMP2"  #创建一个字符串变量STR_TEMP3,并赋值 test ok1 \\

无引号字符串

无引号字符串类似双引号字符串(特殊字符需要转义:|&空格换行;括号引号 等)。这个应该是命令调用格式的原因保留下来的一种特殊字符串语法,一般不推荐使用(容易出错和理解误差),尤其是有特殊字符时。
示例:

STR_TEMP0=  #创建一个空字符串变量STR_TEMP0
STR_TEMP1=${PATH}  #创建一个字符串变量STR_TEMP1,并赋值环境变量PATH
STR_TEMP2=\\\\  #创建一个字符串变量STR_TEMP2,并赋值 \\
STR_TEMP3=test`echo \'ok\'`${STR_TEMP0}$STR_TEMP2  #创建一个字符串变量STR_TEMP3,并赋值 testok1\\

字符串使用

字符串有三种基本使用语法作用一样都是提取字符串内容。
示例及说明:

# 打印字符串
# 标准写法,当变量未定义输出空
echo $STR_TEMP

# 打印字符串,这种写法比较适合字符串连接
# 标准写法,当变量未定义输出空
echo ${STR_TEMP}
# 连接字符串是隔开与变量名与其它字符以免造成变量名变动
echo "string: ${STR_TEMP}value"

# 打印字符串,这种写法适合函数内或使用未知定义变量时
# 询问写法,当变量STR_TEMP不存在时返回123
echo ${STR_TEMP-\'123\'}

字符串连接

字符串连接很简单,支持多个字符串并排连接(并排处不能有空格或换行隔开),并排的字符串形式有:字符串变量、双引号字符串、单引号字符串、反引号字符串,它们之间可以任意组合。
示例:

STR_TEMP="11 $PATH"\'22\'`echo "ok"`$PATH  #创建一个空字符串变量STR_TEMP,并赋值

字符串提取

字符串提取有多种便捷语法,包括下面数据提取中介绍的,除了这些方式也可以通过命令来提取。字符串还提供了匹配提取语法如下表:
语法 使用说明
${name#*chars} 从字符串name第一次出现 chars 的位置开始,截取 chars 右边的所有字符
${name##*chars} 从字符串name最后一次出现 chars 的位置开始,截取 chars 右边的所有字符
${name%chars*} 从字符串name第一次出现 chars 的位置开始,截取 chars 左边的所有字符
${name%%chars*} 从字符串name最后一次出现 chars 的位置开始,截取 chars 左边的所有字符
${name/pattern/pattern} 将字符串name的第一个匹配的pattern替换为另一个pattern
${name//pattern/pattern} 将字符串name的所有匹配的pattern替换为另一个pattern

示例:

STRING_VAL=" This is a shell script。"
echo ${STRING_VAL#*a}   #输出shell script。
echo ${STRING_VAL##*a}   #输出shell script。
echo ${STRING_VAL%a*}   #输出This is 
echo ${STRING_VAL%%a*}   #输出This is 
echo ${STRING_VAL/This/}   #输出is a shell script。
echo ${STRING_VAL//s/S}   #输出ThiS iS a Shell Script。

# 以下提取方式在下面的数据提取中会介绍更多
echo ${STRING_VAL:10}   #从序号10开始截取字符串,输出shell script。
echo ${STRING_VAL-\'empty\'}   #如果字符串STRING_VAL不存在则返回empty,输出 This is a shell script。
echo ${STRING_VAL:11:5}   #从序号11开始截取5个字符串,输出shell
echo ${STRING_VAL:0-13}   #如果开始值为负数则从右向左定位再取,输出shell script。
echo ${STRING_VAL:0-13:5}   #如果开始值为负数则从右向左定位再取,输出shell script。

常用命令提取:(以上面的变量STRING_VAL为例)
sed命令去掉首尾空格

STRING_VAL=`echo $STRING_VAL|sed -r \'s/(^\\s+)|(\\s+$)//g\'`

grep命令提取is之前的内容

STRING_VAL=`echo $STRING_VAL|grep -oP \'.*?is\'`

awk命令提取第四段内容 shell

STRING_VAL=`echo $STRING_VAL|awk \'{print $4}\'`

数值

shell本身没有数值处理功能,计算需要依赖命令:awk、expr、bc 等,但shell提供了另一种内置整数计算语法(具体参考下面双小括号使用)。造成这种原因应该是命令调用解析字符串的关系。数值计算在shell脚本中时常有使用到,一般小数据使用expr来计算,高精度计算使用bc。
计算示例:

expr 2 \'+\' 2  #输出4
echo \'2 + 2\'|bc  #输出4

数组

数组可以方便收集配置或者临时缓存集合数据很实用,数组的使用也很简单。数组的 [] 非常灵活可用:@或*、数值、基本运算表达式、字符串值或表达式等,只要保证最终内容是数组要求的即可。唯一不足的是数组只有一维,如果需要使用多维则需要多个数组组合使用,不怎么方便。
以下均为数组名array_name为例做示范。
shell数组声明赋值三种语法:

# 直接创建数组变量并赋值所有选项
# 注意:空格代表后面是下一个数组元素,
# 如果数组元素内有空格则需要使用单或双引号括起来以免被拆分
# 只有一个元素时不可使用这种语法,具体原因可参考下面的小括号命令组
array_name=(value1 value2 ... valuen)

# 创建数组再逐个赋值
array_name[0]=value1
array_name[1]=value2

# 创建数据再追加赋值
# 注意:${#array_name[@]}是获取数组array_name的长度,长度是从0开始,存入序号也是从0开始,刚好错开一个能一直追加数组元素数据
array_name[${#array_name[@]}]=value1
array_name[${#array_name[@]}]=value2

数组使用语法:

# 打印数组第一个元素数据
# 数组序号是以0开始
echo ${array_name[0]}
# 判断取值,当序号为3的元素不存在时返回default,其它判断取值使用一样
echo ${array_name[3]-\'default\'}

# 打印数组所有数据
# * 和 @ 类似上面的局部特殊shell变量,作用类似。两种语法功能一样,但一般使用@
echo ${array_name[*]}
echo ${array_name[@]}

# 打印数据元素个数
# 以下两种语法都可以获取到相同的数组长度
echo ${#array_name[*]}
echo ${#array_name[@]}

数组拼接

# 把数组 array_name1 和 array_name2 合并为一个数组 array_name3
# 如果有很多数组参与合并直接在后面追加即可,但每个合并数组需要使用空格分开
array_name3=(${array_name1[@]} ${array_name2[@]})

数组截取

# 把数组 array_name1 从第序号第二个开始所有元素赋值给数组array_name2
array_name2=${array_name1[@]:2}

# 把数组 array_name1 从第序号第二个开始截取3个赋值给数组array_name2
array_name2=${array_name1[@]:2:3}

删除数组或元素

# 删除数组内序号为2的元素
# 这个元素删除后不影响数组其它元素序号,即这个序号再次获取是空的
unset ARRAY_TESTS[2]
# 删除整个数组,删除后就是一个空数组
unset ARRAY_TESTS

关联数组

关联数据只有在新版中存在,通过命令declare创建,与上面的数据主要区别在于序号不再是数值而是字符串键名。仅以创建关联数组为例说明下:

# 创建数组再赋值
declare -A array_name
array_name["key1"]="value1"
array_name["key2"]="value2"

#创建数组并赋值
declare -A array_name=(["key1"]="value1" ["key2"]="value2")

数据提取

shell提供了一些便捷数据提取语法,在以上各种变量中非常实用。
提取语法 局部shell环境变量 数组 字符串或环境变量 使用说明
\\${name:start} \\${*:start} 或 \\${@:start} \\${array_name[*]:start} 或 \\${array_name[@]:start} \\${string_name:start} 获取数据从start开始的所有数据
\\${name:start:length} \\${*:start:length} 或 \\${@:start:length} \\${array_name[*]:start:length} 或 \\${array_name[@]:start:length} \\${string_name:start:length} 获取数据从start开始length个数据
\\${name-default} \\${n-default} \\${array_name[n]-default} \\${string_name-default} 当\\$name存在时返回\\$name,否则返回default;
\\${name:-default} \\${n:-default} \\${array_name[n]:-default} \\${string_name:-default} 当\\$name存在且非空时返回\\$name,否则返回default
\\${name+default} \\${n+default} \\${array_name[n]+default} \\${string_name+default} 当\\$name存在时返回空,否则返回default
\\${name:+default} \\${n:+default} \\${array_name[n]:+default} \\${string_name:+default} 当\\$name存在且非空时返回空,否则返回default
\\${name=default} \\${n=default} \\${array_name[n]=default} \\${string_name=default} 当\\$name存在时返回\\$n,否则返回default且$n=default
\\${name:=default} \\${n:=default} \\${array_name[n]:=default} \\${string_name:=default} 当\\$name存在且非空时返回\\$name,否则返回default且$n=default
\\${name?default} \\${n?default} \\${array_name[n]?default} \\${string_name?default} 当\\$name存在时返回\\$name,否则将default输出到stderr产生错误提示
\\${name:?default} \\${n:?default} \\${array_name[n]:?default} \\${string_name:?default} 当\\$name存在且非空时返回\\$name,否则将default输出到stderr产生错误提示

示例:(以下代码脚本名为test.sh,调用命令:bash test.sh \'\' 2 3 )

# 特殊shell变量特殊取值处理,其它相关用法一样,仅仅需要注意用意。
# 取一段参数集
echo ${*: 2}  # 输出2 3
echo ${@: 3:1}   # 输出3

# 判断某个参数存在或空再取对应值
echo ${1-2}   # 输出空
echo ${1:-2}  # 输出2

括号()、[]、{}的作用

shell中支持小、中、大括号语法,灵活使用这些语法可以很方便完成一些特殊操作。

小括号:()

单小括号

小括号在前面数组声明和命令调用中提到过两种语法。但在命令调用时并没有说全,小括号内部的命令或语法是允许多个并且这些命令是另一个新创建的执行环境内执行,也就是不影响外面变量(可以嵌套使用)。小括号内的命令或语法使用分号隔开(就相当于一个内置shell脚本块,支持shell脚本的所有语法)。如果需要提取小括号内的标准输出则在小括号左边增加个$或者使用反引号。
小括号命令组语法:

# 单小括号基本语法
(command1; command2; command3; ...)

# 执行小括号内代码段,并提取标准输出内容赋值给变量STRING_TEST语法
STRING_TEST=$(command1; command2; command3; ...)

# 执行小括号内代码段,并提取标准输出内容赋值给变量STRING_TEST语法
# 不推荐使用,因为反引号也是一种内置shell脚本块且会转义标准输出内容
STRING_TEST=`(command1; command2; command3; ...)`

示例:

STRING_TEST=\'123\'
# 执行内置代码块
(STRING_TEST=\'456\'; echo $STRING_TEST) # 输出 456
echo $STRING_TEST  # 输出 123
# 执行内置代码块
STRING_TEST=$(STRING_TEST=\'456\'; echo $STRING_TEST) # 标准输出赋值给变量STRING_TEST
echo $STRING_TEST  # 输出 456

双小括号

双小括号是shell内置高效整数运算命令,支持逻辑运算、位运算、算术运算、比较运算、赋值运算、自增减运算(使用可以参考下C语言对应的运算符和优先级),表达式可以是一连串复杂的计算公式和赋值操作,同时也允许嵌套小括号。
运算数不允许有非数值参与计算(即标准数值且不能使用引号),运算符也不允许使用引号(除赋值运算外其它运算符建议与运算数之间增加一个以上空格分开),可以嵌套反引号(只要命令输出值满足表达式结构即可),赋值运算左边允许是变量名。

双小括号命令信息:

# 通过help命令查看双小括号命令信息
help \'((\'

# 整数运算命令语法,一般这种语法用于if判断居多
((expression))

# 提取整数运算语法
$((expression))

示例:

STRING_TEST=2
echo $((STRING_TEST=5+5))  # 输出10
echo $STRING_TEST  # 输出10

STRING_TEST1=`echo $((STRING_TEST=15+15))`  # 输出30,且不影响当前环境
echo $STRING_TEST  # 输出10
echo $STRING_TEST1 # 输出30

((`echo STRING_TEST`=5*5)) # 内部嵌套反引号参数表达式计算,注意反引号也可以用来生成运算符
echo $STRING_TEST  # 输出25

中括号:[]

单中括号

中括号在前面数组使用中已经介绍部分语法。单中括号还可以当test命令使用,中括号内部左右两边需要留一个及以上空格否则不会以test命令执行(单中括号一般用在if条件判断居多)。可以理解单中括号是test命令的简写语法。

命令信息

# 通过help命令查看单中括号命令信息
# 注意:这条命令会把双中括号的信息也介绍掉
help \'[\'

# 查看test命令信息
help test

示例:

[ -n "1" ]; echo $? # 字符串不为空,输出0
[ ! -z "1" ]; echo $? # 字符串不为空,输出0
[ -z "" ]; echo $? # 字符串为空,输出0
[ -e "/var/log/messages" ]; echo $? # /var/log/messages文件存在,输出0
[ -d "/var/log" ]; echo $? # /var/log目录存在,输出0
[ -5 \'>\' -2 ]; echo $?  # 字符串数值比较输出1,注意:运算符号最好使用引号或转义符号处理

# 中括号内部的字符串或参数项也可以使用其它语法生成拼接成更复杂灵活的语句
[ \'-n\' "`find ./ -name \'*.log\'`" ]; echo $? # 当前目录下是否存在*.log文件,有输出0,无输出1

双中括号

双中括号就相当于test命令增强版(去掉了test命令的-o和-a两个参数,即或和与),增加模式匹配,内部语句将在新执行环境下运行。双中括号命令模式匹配是在使用运算符 ==!= 时右侧字符串作为模式匹配处理(具体语法查看下面的模式匹配介绍 ),使用运算符 =~ 时右侧字符串作为正则表达匹配。同时支持 || 和 && 运算连接多个test命令处理。

双中括号的命令信息:

# 通过help命令查看双中括号命令信息,注意结合test命令信息使用
help \'[[\'

示例:

[[ -5 > -2 ]]; echo $?  # 字符串数值比较输出1,注意:双中括号不需要处理运算符号
[[ ! -n `find ./ -name *.log` ]]; echo $? # 当前目录不存在*.log文件,输出0或1
[[ \'shell script\' == shell* ]];echo $? # 左边字符串包含模式匹配串 shell* ,输出0
[[ \'shell script\' =~ [a-z]+ ]];echo $? # 左边字符串符合正则表达式 [\\w\\s]+ ,输出0
[[ \'shell script\' == shell* && \'shell script\' =~ [a-z]+ ]];echo $? # 两个字符串运命令处理通过 ,输出0

大括号:{}

大括号没有单双之分,其中在前面变量使用中已经有介绍部分语法。大括号实际上是一个当前执行环境下执行的命令组块,在大括号内执行的操作会影响到当前执行环境。大括号内两侧需要留一个及以上空格否则不会以大括号命令执行,一般大括号用于一段代码重定向。

双小括号命令信息:

# 通过help命令查看 {} 命令信息
help \'{\'

# 通常语法
{ command; ... }

示例:

{ echo \'ok\'; echo \'error\' >&2; } 2> /dev/null # 屏蔽错误输出,实际输出 ok

流程控制

shell脚本有多种流程控制命令,基本上每个流程控制命令是由多个命令组成,在使用时以换行或分号隔开,且允许嵌套。

条件控制

当需要按条件选择执行区域代码时条件控制是必经之路,条件控制多样化,但总体来说需要使用到判断。shell条件判断提供了多种方式,包括前面串联命令调用提到的||和&&运算符,还有下面要说的条件判断命令。

if

if判断非常灵活,它是通过提取if至then命令间最后一个命令或函数的退出或返回值来判断(即$?的值来判断,0代表true,1~255代表false)。if由5个命令组成,其中elif、else、then、fi是依赖性子命令(这4个命令必需在if之后且有对应层关系和顺序)。
if命令信息:

# 通过help命令查看if命令信息
help if

# 通常语法
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi

示例:

# 启动sshd服务处理
if ps aux|grep \'sshd\';then 
   echo \'sshd ok\';
else
   service sshd start
fi

# 简单整数计算脚本处理
# 注意:因为$1参数可能不存在,所以需要使用双引号,不然参数代入后[]命令将丢失数据为[ -n ]导致命令错误
if [ -z "$1" ];then
   echo \'参数未传入!\';
elif [ "$1" = \'help\' ];then
   echo \'这个是一个简单的整数计算脚本,参数中请输入标准的整数计算公式即可得出结果。\';
elif [[ "$1" =~ [0-9]+[\\+\\*/-][0-9]+([\\+\\*/-][0-9]+)* ]];then
   echo \'计算结果是:\'
   echo $(($1));
else
   echo $1\'未知参数!\';
fi

case

case命令类似其它开发语言的switch。case支持使用模式匹配(模式匹配参考下面的介绍),且支持并列可选命令 | ,非常方便。可选命令 | 前后允许空格且这些格式不会进入模式匹配(如果需要进入则需要增加转义)。
case命令信息:

# 通过help命令查看case命令信息
help case

# 通常语法
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac

示例:

case \'test.py\' in
   *.js|*.h5)
      echo \'前端H5\';
   ;;
   *.php | *.java | *.py)
      echo \'后端\';
   ;;
   *.sh)
      echo \'shell脚本\';
   ;;
   *)
      echo \'其它\';
   ;;
esac
# 输出:
#后端

循环控制

shell提供了三种循环命令:for、while、until。这三种请求语法多样且功能多,基本上每个循环命令都有多种用法,熟悉这些用法将会大提升编写效率。

通常语法

C语言风格for循环

for ((exp1; exp2; exp3)); do COMMANDS; done

Python风格for in循环

for NAME [in WORDS ... ] ; do COMMANDS; done

C语言风格for循环
1. exp1、exp2、exp3分别是三个算术表达式:初始赋值、循环判断、循环变化。
2. exp1 只在循环进入时执行,exp2是循环体进入判断,exp3是单次循环结束后调用
3. 单次循环结束后再判断exp2,成功进入循环体,直到exp2条件不成立结束循环
> exp1、exp2、exp3这三个表达式是可选表达式,如果不写可留空但中间的两个分隔分号必需有。exp2不指定则是死循环

Python风格for in循环
1. [in WORDS ... ]是要循环的字符串(可以是任何字符串及形式),以空格分隔(可以使用引号或转义使循环字符包含空格)
2. [in WORDS ... ]是可选的,当不指定则默认类似循环shell环境变量$@
3. [in WORDS ... ]可以是范围值语句{start..end},类似正则的字符范围
> NAME 是每次进入循环体前写入当前所在循环字符串值的变量名,在循环体内可直接使用。

示例:
```bash
# C语言风格for循环
for ((NUM=0; NUM<2; NUM++)); do 
   echo $NUM; 
done;
# 输出:
#0
#1

# Python风格for in循环
# 循环字符串
SHOW_STR=\'456\'
for ITEM in abc $SHOW_STR `echo \'shell\'` $((5+6)) "& &"; do 
   echo $ITEM; 
done;
# 输出:
#abc
#456
#shell
#11
#& &

# 循环0-3和a到c,这里可以指定单独一个范围
for ITEM in {1..3} {a..c}; do 
   echo $ITEM; 
done;
# 输出:
#1
#2
#3
#a
#b
#c

# 循环数组变量
ARRAY_VALS=(123 456 789)
for ITEM in ${ARRAY_VALS[@]}; do 
   echo $ITEM; 
done;
# 输出:
#123
#456
#789

# 循环$@变量
for ITEM in $@; do 
   echo $ITEM; 
done;

{start..end}是一个生成命令,这个命令可以指定范围的数值(范围是正负2的15次方)或字母(26个大小写字母)集并以空格分开,在for循环中非常实用。命令中{}和..是语法关键字,start和end是范围首尾数字或字母(首尾指定是生成的区间且允许正和逆向生成,比如:数值从小到大或从大到小、字母从a到z或从z到a均可以)。

{start..end}生成命令还有一个强大特性即交叉生成,命令前后如果有相连字符串或{start..end}生成命令就会生成交叉或重复内容。
示例:

# 交叉生成
echo {z..r}{1..5}
# 输出
#z1 z2 z3 z4 z5 y1 y2 y3 y4 y5 x1 x2 x3 x4 x5 w1 w2 w3 w4 w5 v1 v2 v3 v4 v5 u1 u2 u3 u4 u5 t1 t2 t3 t4 t5 s1 s2 s3 s4 s5 r1 r2 r3 r4 r5

# 顺序生成
echo shell-{1..5}.sh
# 输出
#shell-1.sh shell-2.sh shell-3.sh shell-4.sh shell-5.sh

while命令信息:

# 通过help命令查看while命令信息
help while

# 通常语法
while COMMANDS; do COMMANDS; done

示例:

# 一般固定次数循环语法
NUM=3
while ((NUM-- > 0));do
    echo $NUM
done
#输出:
#2
#1
#0

# 结合read命令读取文件
# 循环体在新执行环境中运行
SHOW_STR=\'\'
echo -e "123\\n456"|while read -r ITEM;do
    SHOW_STR=$SHOW_STR$ITEM"\\n"
done
echo -e $SHOW_STR  #这里输出是空

# 循环体在当前执行环境中运行
SHOW_STR=\'\'
while read -r ITEM;do
    SHOW_STR=$SHOW_STR$ITEM"\\n"
done <<EOF
123
456
EOF
echo -e $SHOW_STR
#输出:
#123
#456

通常语法

until COMMANDS; do COMMANDS; done

示例:
```bash
NUM=0
until ((NUM++ > 2));do
    echo $NUM
done
#输出:
#1
#2
#3

通常语法

break [n]

示例:
```bash
ARRAY_VALS=(123 456 789 abc)
for ITEM in ${ARRAY_VALS[@]};do
   if [ ${ARRAY_VALS[2]} = $ITEM ];then
      break;
   else
      echo $ITEM
   fi
done
#输出:
#123
#456

continue

continue进入指定层数循环的下一次循环,即跳到指定层循环的单次循环结束位置。
continue命令信息:

# 通过help命令查看continue命令信息
help continue

# 通常语法
continue [n]

示例:

ARRAY_VALS=(123 456 789 abc)
for ITEM in ${ARRAY_VALS[@]};do
   if [ ${ARRAY_VALS[2]} = $ITEM ];then
      continue;
   else
      echo $ITEM
   fi
done
#输出:
#123
#456
#abc

参数传递

在shell脚本中的所有代码都可以理解为不同命令的调用,多数命令必需结合传入的参数进行工作,受不同命令或语法影响参数传递形式多样化。可以理解shell脚本就是各种命令和对应参数的组合。
参数传递形式有:无引号字符串、有引号字符串、变量、反引号、$()、$(())、$[]、管道符、标准输入、正则表达式、模式匹配串、及组合嵌套等。命令在调用前会依次解析后面所有对应参数(带命令的参数执行命令并提取,变量参数直接取值等),最终将所有参数解析成字符串并替换对应的表达式(有引号字符串解析替换后依然保留引号),然后传递给命令进行执行处理。


函数

函数是shell脚本中生成自定义局部命令的命令,函数内是编写好局部命令的shell脚本代码,函数名即是命令名可按命令调用方式调用,调用后将执行函数内的代码。函数定义是通过function命令,该命令会生成局部命令且只在当前执行环境下有效(函数定义必需要调用前,否则局部命令没有生成就调用会引起报错),脚本结束后立即释放。

通常语法,以下两种语法功能和作用均一样

标准函数语法

function name [()]{ COMMANDS ; }

简写函数语法

name () { COMMANDS ; }

> 注意:函数体内最少有一条命令语句,否则语法报错。函数名与左大括号必需有隔开符(一对小括号、空格、换行均可),函数体中首尾大括号内必需与内部语句使用空格或换行隔开,函数结束大括号必需使用换行或分号与后面的语句分开(函数的申明和函数体都属于函数命令部分,命令的结束需要符合语法)。
示例:
```bash
# 定义函数func_show1
function func_show1(){
   echo "func_show1-params: $@"
}
# 定义函数func_show2
function func_show2 {
   echo "func_show2-params: $@"
}
# 定义函数func_show3
func_show3(){
   echo "func_show3-params: $@"
}
# 调用定义的函数
func_show1 test1 && func_show2 test2 && func_show3 test3
# 输出
#func_show1-params: test1
#func_show2-params: test2
#func_show3-params: test3

return命令

函数是一个自定义命令,那命令就会有退出值。函数需要通过return命令来自定义退出值,如果不指定return命令则函数会把函数体内最后一个命令的执行退出值返回出来。return命令默认返回值是0(即函数调用成功)

return命令信息:

# 通过help命令查看
help return 

# 通常语法
return [n]

示例:

func_test(){
   echo "current-\\$?:$?"
   if [ -n "$1" ];then
      if [[ $1 =~ ^[0-9]+$ ]];then
         echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1
         return $1 # 指定函数退出值
      else
         echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1
         return # 相当于调用 return $?
      fi
   fi
   echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1
}
echo \'shell srcipt\'|grep -qP \'python\'  # grep命令匹配失败,即:$?=1

## 不传参数则函数不会调用return命令语句,直接返回前一个命令的退出值(也可以说不修改$?,因为$?在当前shell脚本执行环境下共享)
func_test  
echo "func-default-\\$?:$?"

# 传入一个非数值参数,函数会执行 return 命令语句
# 相当于执行 return $?
func_test a
echo "func-return-\\$?:$?"

# 传入一个数值参数,函数内会执行 return $1 命令语句
# 修改$?值为传入的整数值
func_test 0
echo "func-return-\\$?:$?"
# 输出:
# current-$?:1
# func-default-$?:1
# current-$?:0
# func-return-$?:1
# current-$?:0
# func-return-$?:0

exit命令

exit命令是用来停止当前执行环境继续向下执行(一般用于退出shell脚本继续执行)并设置退出值。exit命令在脚本执行错误或不合理时调用非常实用,exit会在上层执行环境中修改$?值(比如:脚本调用脚本就可以获取子脚本的执行情况)。

exit命令信息:

# 通过help命令查看
help exit

# 通常语法
exit [n]

示例:
脚本文件 test1.sh

#!/bin/bash
if [ -n "$1" ];then
   if [[ $1 =~ ^[0-9]+$ ]];then
      echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1
      exit $1 # 指定脚本退出值
   else
      echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1
      exit # 相当于调用 exit $?
   fi
fi
echo \'shell srcipt\'|grep -qP \'python\' # grep命令匹配失败,即:$?=1

脚本文件 test2.sh

#!/bin/bash
# 不传参数则函数不会调用 exit 命令语句
bash test1.sh 
echo "shell-exit-\\$?:$?"

# 传入一个非数值参数,函数会执行 exit 命令语句
# 相当于执行 exit $?
bash test1.sh a
echo "shell-exit-\\$?:$?"

# 传入一个数值参数,函数内会执行 exit $1 命令语句
# 修改$?值为传入的整数值
bash test1.sh 0
echo "shell-exit-\\$?:$?"

调用

bash test2.sh
# 输出:
# shell-exit-$?:1
# shell-exit-$?:1
# shell-exit-$?:0

正则表达式

shell正则支持功能支持正则通用语法。如果熟悉其它语言正则会发现shell正则不支持通用特定转义(比如:\\w 、\\W 、\\d 、\\D),仅支持空格换行之类的转义符。
shell正则在不同的命令中会有差异,比如sed -r和grep -P或[[]]正则就不尽相同。
shell正则主要语法有:

元字符

^ $ \\ [ ] | ( ) . * ? { } + -
元字符几乎所有语言正则通用。由于不同语言使用的正则处理器(或正则引擎)有差异(主要是考虑到性能和实用性)。能灵活使用元字符那正则表达式基本上就很好编写了,并且基本上能在其它语言中通用。

示例:

# 正则表达式 abc[1-9]
# 可匹配abs后面是1到9间任意一个数字
[[ \'abcdabc123abs\' =~ abc[1-3] ]];echo $?  # 判断是否匹配,输出0
echo \'abcdabc123abs\'|sed -r \'s/abc[1-3]//g\'  # 替换掉匹配内容,输出abcd23abs
echo \'abcdabc123abs\'|grep -oP \'abc[1-3]\'  # 提取匹配内容,输出abc1

特殊字符组

特殊字符组是一些特定字符组的便捷表达式,使用特殊字符组可以直接代表对应的字符组。

字符组 代替说明
[[:alpha:]] 匹配任意字母字符,不管是大写还是小写
[[:alnum:]] 匹配任意字母数字字符0~9、A~Z或a~z
[[:blank:]] 匹配空格或制表符
[[:digit:]] 匹配0~9之间的数字
[[:lower:]] 匹配小写字母字符a~z
[[:print:]] 匹配任意可打印字符
[[:punct:]] 匹配标点符号
[[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT和CR
[[:upper:]] 匹配任意大写字母字符A~Z

示例:

# 正则表达式 abc[[:alpha:][:alnum:]]
# 可匹配abs后面是1到9或大小写字母间任意一个
[[ \'abc123abs\' =~ abc[[:alpha:][:alnum:]] ]];echo $?  # 判断是否匹配,输出0
echo \'abc123abs\'|sed -r \'s/abc[[:alpha:][:alnum:]]//g\'  # 替换掉匹配内容,输出23abs
echo \'abc123abs\'|grep -oP \'abc[[:alpha:][:alnum:]]\'  # 提取匹配内容,输出abc1

排除字符集 ^

当需要匹配指定字符集时就需要使用到在字符类中括号左边增加^,表示除了这些字符除外。

示例:

# 正则表达式 abc[^[:alpha:]4-9]
# 可匹配abs后面不是9到9或大小写字母间任意一个
[[ \'abcdabc123abs\' =~ abc[^[:alpha:]4-9] ]];echo $?  # 判断是否匹配,输出0
echo \'abcdabc123abs\'|sed -r \'s/abc[^[:alpha:]4-9]//g\'  # 替换掉匹配内容,输出abcd23abs
echo \'abcdabc123abs\'|grep -oP \'abc[^[:alpha:]4-9]\'  # 提取匹配内容,输出abc1

任意字符 .

很多时候需要匹配一段字符是未知类型(或者是忽略段),任意字符将是一个必备工具。任意字符是一个点,这个点代表任何一个字符(有的正则点只代表非空格字符,要升级点的功能需要增加修饰)。
示例:

# 正则表达式 ...
# 匹配任意三个字符一组并全部替换为+
echo \'abc abc 123 abs\'|sed -r \'s/.../+/g\'  # 替换掉匹配内容,输出+++++

重复限定

当指定的匹配项会连续重复多次出现就需要使用到重复限定,使用重复限定可以减化匹配语法。重复限定分:0~1次、0次及以上、1次及以上、自定义次数。匹配项是指重复限定符前面的(子模式、字符集、常规一个字符、限定符+前面的匹配项)。

?

? 在正则中表示前面的匹配项需要重复0~1次(即有只匹配一次,没有就跳过)。
示例:

# 正则表达式: [[:alpha:]][0-9]?
# 将匹配所有前面是一个字母后面是一个数字的串并全部替换为+
echo \'abc123-a0-a1\'|sed -r \'s/[[:alpha:]][0-9]?/+/g\'  # 替换掉匹配内容,输出+++23-+-+

*

* 在正则中表示前面的匹配项需要重复0次及以上(即有就匹配所有连续重复一次匹配项,没有就跳过)。
示例:

# 正则表达式: [[:alpha:]][0-9]*
# 将匹配所有字母并全部替换为+
echo \'abc123-a0-a1\'|sed -r \'s/[[:alpha:]][0-9]*/+/g\'  # 替换掉匹配内容,输出+++-+-+

+

+ 在正则中表示前面的匹配项需要重复1次及以上(即有最少匹配一次,且后面连续有多少重复均匹配上)。
示例:

# 正则表达式: [[:alpha:]]+
# 将匹配所有字母并全部替换为+
echo \'abc123-a0-a1\'|sed -r \'s/[[:alpha:]]+/+/g\'  # 替换掉匹配内容,输出+123-+0-+1

{} 自定义

自定义匹配分三种形式:固定次数 {n}、指定次数及以上 {n,}、区间次数 {n, m}。
示例:

# 正则表达式: [[:alpha:]]{2}
# 将匹配所有2个字母一起的全部替换为+
echo \'abc123-a0-a1\'|sed -r \'s/[[:alpha:]]{2}/+/g\'  # 替换掉匹配内容,输出+c123-a0-a1

# 正则表达式: [[:alpha:]]{2,}
# 将匹配所有2个及以上字母一起的全部替换为+
echo \'abc123-a0-a1\'|sed -r \'s/[[:alpha:]]{2,}/+/g\'  # 替换掉匹配内容,输出+123-a0-a1

# 正则表达式: [[:alpha:]]{2,5}
# 将匹配所有2至5个字母一起的全部替换为+
echo \'abc123-abcdef0-a1\'|sed -r \'s/[[:alpha:]]{2,5}/+/g\'  # 替换掉匹配内容,输出+123-+f0-a1

正则表达式: [0-9]+$

找出结尾连续1个及以上数字并替换为+

echo \'abc123-a01\'|sed -r \'s/[0-9]+$/+/g\' # 替换掉匹配内容,输出abc123-a+

### 可选符 |
如果匹配的内容是确定几种固定规则时没有可选符将很难在一个正则中实现(可以在每个选项规则上增加重复限定为0~1次,但这样会匹配所有的可选规则而不是多选一)。
示例:
```bash
# 正则表达式: abc|123|a.
# 将abc或123或a后面任意字符替换为+
echo \'abc123-a0-a1\'|sed -r \'s/abc|123|a./+/g\'  # 替换掉匹配内容,输出++-+-+

转义符 \\

如果需要匹配前面的特殊字符时就必需使用转义,转义后这些特殊字符将没有特殊功能而是一个普通字符串,可以正常进行匹配,转义符可以转义自己。
示例:

# 正则表达式: [\\\\\\*\\-\\$]+
# 把所有\\*-$替换为+
echo \'.***/[](\\\\){$}?$^\'|sed -r \'s/[\\*\\-\\$]+/+/g\'  # 替换掉匹配内容,输出.+/[](+){+}?+^

模式匹配

模式匹配是一种简易的正则表达式,它舍弃了正则是很多高级功能,保留一些高效语法,在使用模式匹配时最好先了解下正则。
模式匹配保留了shell正则语法:字符类、转义符(各种括号和空格通配符等需要转义才能用于匹配这些字符);增加:通配符(相当于正则表达式 . 的简写)。也就是说模式匹配去掉正则:可选符(case支持可选命令)、子模式、锚、任意字符、\\s等转义符、重复符、自定义量词等功能,其它语法均可正常使用。
编写时需要注意与正则表达式区别,在模式匹配中不支持的符合均为普通匹配且特殊符号需要转义。模式匹配主要用于[[]]和case命令中,其中可选任只能用于case命令中。

示例:

# 判断字符串是否符号版本号格式
[[ \'4.50v\' == [0-9].[0-9][0-9][[:alpha:]] ]];echo $? # 输出0

重定向

重定向分输入重定向和输出重定向,它提供一种语法将输入或输出的的信息进行转移,实际应用中重定向很常用。重定向可以理解为其它命令的可选子命令,一般附加在命令的末尾。

一般情况下,每个Unix/Linux命令运行时都会打开三个文件:
文件描述符 文件名 对应硬件 说明
0 stdin 键盘 标准输入文件
1 stdout 显示器 标准输出文件
2 stderr 显示器 标准错误输出文件

示例:

cat <<EOF
shell srcipt!
EOF
# 输出:
# shell srcipt!

输出重定向

重定向输出在shell脚本中使用颇多,并且可以并列多次重定向输出。重定向输出使用 >>> 符号表示。左边是要调用的命令,右边是重定向的文件。 > 是覆盖到输出文件,>> 是追加到输出文件。

重定向语法:

# 重定向输出覆盖到输出文件
COMMAND [1|2]>[&][0|1|2|filename]

# 重定向输出追加到输出文件
COMMAND [1|2]>>[&][0|1|2|filename]

示例:

# 特意输出错误信息
echo \'error\' >&2

# 将命令的错误信息屏蔽
echo \'error\' 2>/dev/null

# 将命令的所有输出信息屏蔽
echo \'error\' 2>&1 >/dev/null

# 置空或创建空文件
> /tmp/log

# 向文件追加内容
echo \'add line\' >>/tmp/log
echo \'add line\' >>/tmp/log
# 此处记得查看下 /tmp/log 文件内容

# 覆盖文件内容
echo \'one line\' >/tmp/log

常用命令

下面列出一些常用命令,对于编写shell脚本很有用,实际应用中命令远不止这些,但它们使用的规则基本相同。命令总体分两类,内建命令和外部扩展命令,不同的命令处理方式略有区别。

查看help命令信息

help help

#### pwd
pwd是输出当前工作目录。shell命令搜索目录分:环境目录、工作目录、根目录。目录正确才能找到对应的文件。环境目录一般用于命令搜索,工作目录和根目录用于命令搜索和文件使用。工作目录下一般使用的是相对路径,使用方便。根目录是绝对路径开始,绝对路径能保证目录完整无误特别是在多个相同命令或文件时很实用。
示例:
```bash
# 展示当前工作目录
pwd

cd

cd是工作目录切换命令
示例:

cd ../  # 将级目录切换为工作目录
cd /use/local  # 指定绝对路径为工作目录

read

read是标准输入文件读取单行数据命令。这个命令可以用来读取键盘输入,当使用重定向的时候,可以读取文件中的一行数据。
示例:

# 读取单行标准输入数据并写入到变量LINE_STR中
read LINE_STR

# 读取/etc/profile文件第一行数据并写入到变量LINE_STR中
read LINE_STR < /etc/profile

kill

kill是有PID或JOBSPEC的进程发送停止信号数值(默认是15,即正常停止一个进程),一般在强制停止服务或shell脚本时很有用。
示例:

kill -l  # 查看kill支持传入信号数值
kill 222 # 正常停止进程ID是222的进程
kill -9 222 # 强制杀掉进程ID是222的进程

shell外部命令

外部命令的有是系统携带有的是另外安装的,特别需要注意的是系统对文件的权限控制(读、取、执行),如果是命令必需有可执行权限否则不能直接调用。下面简单列几个外部命令的基本使用。


以上是关于shell脚本基本处理的主要内容,如果未能解决你的问题,请参考以下文章

shell 脚本 片段

用于确保在任何给定时间仅运行一个 shell 脚本的 shell 片段 [重复]

shell脚本基本处理

C语言能否用在shell脚本

Linux系统编程-Shell脚本基本使用(数组函数字符串处理)

《懒人Shell脚本》之七——格式化处理数据存入数据库实现