SHELL编程

时间:Jan. 8, 2017 分类:

目录:

shell介绍

Shell 是一个 C 语言编写的脚本语言,它是用户与 Linux 的桥梁,用户输入命令交给 Shell 处理,Shell 将相应的操作传递给内核(Kernel),内核把处理的结果输出给用户。

shell以及其他脚本语言

shell适合用处理操作系统底层业务,因为有大量的系统命令支撑,特别是grep,sed和awk,例如一键安装,优化和监控报警等,这样比较符合运维的大原则,高效。 而php和python优势在于运维和开发,web管理工具以及web业务开发,但是开发效率和复杂度都会过高。

shell编程的基础

  1. vi/vim编辑器的使用,ssh终端和.vimrc的设置
  2. linux常用命令熟练使用
  3. linux场景服务的部署和排错

shell分类

  • Bourne shell
  • sh,ksh,bash和sh
  • C shell
  • csh和tcsh

shell三种执行方式

  1. bash和sh
  2. 全路径和./
  3. source和.
[root@why sh]# cat test.sh 
user=`whoami`
[root@why sh]# cat ex.sh 
sh /root/sh/test.sh
echo $user
[root@why sh]# sh ex.sh 

[root@why sh]# vi ex.sh 
[root@why sh]# cat ex.sh 
. /root/sh/test.sh
echo $user
[root@why sh]# sh ex.sh 
root

一个shell脚本调用另一个脚本的参数需要引入,即source和.

shell注释

单行注释

#!/bin/bash是指定执行脚本的时候通过那个程序来解析脚本的内容,'#!'称为幻数,这一行必须在脚本的顶端第一行,如果不是就内核就会认为是注释。其他行就可用#注释。

多行注释

: '
语句1
语句2
'

if false; then
语句1
语句2
fi

: << 字符
语句1
语句2
字符(在这个字符与上一个字符相同) 

((0)) & {
语句1
语句2
}

清空日志的三种方式

echo > access.log
> access.log
cat /dev/null > access.log

有关单引号,双引号和无引号的区别

单引号:将单引号中的内容,原封不动的进行输出 双引号:把双引号中的内容输出,如果内容中有反引号命令,变量,特殊转义符等,会把这些解析出来再输出 无引号:把内容输出,当含有空格就无法进行输出

shell脚本

清空一个message文件

cd  /var/log
cat /dev/null > message
echo 'Message cleaned up'

这样一个简单的脚本就完成了,但是会有很多很多的问题,例如需要root权限才能删除

[why@why root]$ > /var/log/messages
bash: /var/log/messages: Permission denied

我们就需要对此进行判断

LOG_DIR=/var/log
ROOT_UID=0
if [ "$UID" -ne "ROOT_UID" ]
then
    echo 'Must be root to run this script'
    exit 1
if
cd $LOG_DIR || {
    echo 'Cannot to change dir' > &2
    exit 1
}
cat /dev/null > message && echo 'Message clean up'

shell规范和习惯

  1. 开头表明解释器
  2. 添加一些创建时间,创建者信息,版权,描述等
  3. 关键步骤添加注释,但是不要用中文,防止乱码
  4. 脚本以sh为扩展名
  5. 脚本成对出现的括号流程控制一次性写完
  6. 缩进提高可读性

    Shell变量

系统变量

在命令行提示符直接执行env、set查看系统变量。env是显示用户环境变量,set是显示Shell 变量。set变量可以通过export导入到env系统变量。 shell的环境变量可以在命令行生成,但是用户登出这些变量就会消失,一般在家目录下的.bash_profile,/etc/profile和/etc/profile.d/中定义,当用户登录时会自动加载,一般环境变量均为大写,并且用export命令导出(也可以用declare -x),这些一般被称为全局变量,写在脚本的即为局部变量,取消环境变量用unset环境变量。

$SHELL 默认Shell
$HOME 当前用户家目录
$IFS 内部字段分隔符
$LANG 默认语言
$PATH 默认可执行程序路径
$PWD 当前目录
$UID 当前用户ID
$USER 当前用户
$HISTSIZE 历史命令大小,可通过HISTTIMEFORMAT变量设置命令执行时间
$RANDOM 随机生成一个0至32767的整数
$HOSTNAME 主机名

shell特殊变量

$SHELL 描述
$0 获取当前shell脚本的文件名,如果全路径执行的话包括脚本路径
$n shell脚本的第n个参数,如果n大于9就要括起来,例如${10}
$* 获取shell的所有参数,所有的位置参数被看做一个字符串,$1$2$3$4...
$# 获取参数的总个数,这个一般用于判断参数个数
$@ 获取shell的所有参数,每个位置参数被看做独立的字符串,$1 $2 $3 $4...
$$ 当前shell进程的pid,一般用于一个脚本只能运行一个进程
$! 上一个指令的pid
$_ 在此之前执行的命令或脚本的最后一个参数
$? 上一个命令的返回值

shell变量子串

man bash查找Parameter Expansion

$SHELL 描述
${#parameter} $parameter字符串长度
${parameter:offset} 在$parameter中, 从位置$offset开始提取子串
${parameter:offset:length} 在$parameter中, 从位置$offset开始提取长度为$length的子串
${parameter#word} 从变量$parameter的开头, 删除最短匹配$word的子串
${parameter##word} 从变量$parameter的开头, 删除最长匹配$sword的子串${parameter%word} 从变量$parameter的结尾, 删除最短匹配$word的子串
${parameter%%word} 从变量$parameter的结尾, 删除最长匹配$word的子串
${parameter/pattern/replacement} 使用$replacement, 来代替第一个匹配的$pattern
${parameter//pattern/replacement} 使用$replacement, 代替所有匹配的$pattern
${parameter/#pattern/replacement} 如果$parameter的前缀匹配$pattern, 那么就用$replacement来代替匹配到的$pattern
${parameter/%pattern/replacement} 如果$parameter的后缀匹配$pattern, 那么就用$replacement来代替匹配到的$substring
[root@why ~]# WHY="I am whywhy"
[root@why ~]# echo $WHY
I am whywhy
[root@why ~]# echo ${#WHY}
11
[root@why ~]# echo ${WHY:2}
am whywhy
[root@why ~]# echo ${WHY:2:8}
am whywh
[root@why ~]# echo ${WHY/why/wanghongyu}
I am wanghongyuwhy
[root@why ~]# echo ${WHY/%why/wanghongyu}
I am whywanghongyu
[root@why ~]# echo ${WHY#w*y}
I am whywhy
[root@why ~]# echo ${WHY##w*y}
I am whywhy
[root@why ~]# echo ${WHY%%w*y}
I am
[root@why ~]# echo ${WHY%w*y}
I am why

${parameter/%pattern/replacement}可用于后缀转换mv $f ${f%jpg/png}

常用变量状态赋值

$SHELL 描述
${value:-word} 如果value不存在用word代替,在yum安装httpd的时候,/etc/init.d/httpd中就有
${value:=word} 用word替换value
${value:?word} 用于捕捉变量为定义的错误,如果value没有,会提示word
${value:+word} 用于测试变量是否存在,返回值为word
[root@why pythontest]# VAR=
[root@why pythontest]# echo ${VAR:-'hello world!'}
hello world!
[root@why pythontest]# VAR="hello"
[root@why pythontest]# echo ${VAR:+'hello world!'}
hello world!
[root@why pythontest]# VAR=
[root@why pythontest]# echo ${VAR:=hello}
hello
[root@why pythontest]# echo $VAR
hello
[root@why pythontest]# VAR=
[root@why pythontest]# echo ${VAR:?value is null}
-bash: VAR: value is null

对变量的路径进行操作时,最好先判断路径是否为非空,如下path变量没有定义,则取/tmp,防止变量没定义误删除: $ find ${path-/tmp} -type f -name *.tar.gz -mtime +7 | xargs rm -f 后边这些与上述部分重复,仅供参考

表达式 含义
${var} 变量var的值, 与$var相同
${var-DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值
${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值,判断var变量是否没有定义
${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值
${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 ,判断var变量是否没有定义,并确保变量始终有值
${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串
${var:+OTHER} 如果var被设置了, 那么其值就是$OTHER, 否则就为null字符串
${var?ERR_MSG} 如果var没被声明, 那么就打印$ERR_MSG
${var:?ERR_MSG} 如果var没被设置, 那么就打印$ERR_MSG
${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量
${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量

字符串颜色

字符串输出颜色,有时候关键地方需要醒目,颜色是最好的方式 格式: \033[1;31;40m # 1 是显示方式,可选。31 是字体颜色。40m 是字体背景颜色。 \033[0m # 恢复终端默认颜色,即取消颜色设置

代号 字体颜色
30
31
32 绿
33
34 蓝色
35 紫色
36 深绿
37 白色
代号 字体背景颜色
40
41 深红
42 绿
43 黄色
44 蓝色
45 紫色
46 深绿
47 白色
代号 显示方式
0 终端默认设置
1 高亮显示
4 下划线
5 闪烁
7 反白显示
8 隐藏
[root@why ~]# cat color.sh 
#!/bin/bash
# 字体颜色
for i in {31..37}; do
echo -e "\033[$i;40mHello world!\033[0m"
done
# 背景颜色
for i in {41..47}; do
echo -e "\033[47;${i}mHello world!\033[0m"
done
# 显示方式
for i in {1..8}; do
echo -e "\033[$i;31;40mHello world!\033[0m"
done

表达式和运算符

条件表达式

表达式 示例
[ expression ] [ 1 -eq 1 ]
[[ expression ]] [[ 1 -eq 1 ]]
test expression test 1 -eq 1 ,等同于[]

整数比较符

比较符 描述 示例
-eq,equal 等于 [ 1 -eq 1 ]为 true
-ne,not equal 不等于 [ 1 -ne 1 ]为 false
-gt,greater than 大于 [ 2 -gt 1 ]为 true
-lt,lesser than 小于 [ 2 -lt 1 ]为 false
-ge,greater or equal 大于或等于 [ 2 -ge 1 ]为 true
-le,lesser or equal 小于或等于 [ 2 -le 1 ]为 false

字符串比较符

运算符号 描述 示例
== 等于 [ "a" == "a" ]为 true
!= 不等于 [ "a" != "a" ]为 false
> 大于,判断字符串时根据ASCII码表顺序,不常用 在[]表达式中:[ 2 > 1 ]为true,在[[]]表达式中:[[ 2 > 1 ]]为true,在(())表达式中:(( 3 > 2 ))为true
< 小于,判断字符串时根据ASCII码表顺序,不常用 在[]表达式中:[ 2 \< 1 ]为 false,在[[]]表达式中:[[ 2 < 1 ]]为false,在(())表达式中:(( 3 < 2 ))为false
>= 大于等于 在(())表达式中:(( 3 >= 2 ))为true
<= 小于等于 在(())表达式中:(( 3 <= 2 ))为false
-n 字符串长度不等于0为真 VAR1=1;VAR2="" [ -n "$VAR1" ]为true,[ -n "$VAR2" ]为false
-z 字符串长度等于0为真 VAR1=1;VAR2=""[ -z "$VAR1" ]为false[ -z "$VAR2" ]为true
str 字符串存在为真 VAR1=1;VAR2=""[ $VAR1 ]为true,[ $VAR2 ]为false

文件测试

测试符 描述 示例
-e 文件或目录存在为真 [ -e path ] path 存在为 true
-f 文件存在为真 [ -f file_path ] 文件存在为 true
-d 目录存在为真 [ -d dir_path ] 目录存在为 true
-r 有读权限为真 [ -r file_path ] file_path 有读权限为 true
-w 有写权限为真 [ -w file_path ] file_path 有写权限为 true
-x 有执行权限为真 [ -x file_path ] file_path 有执行权限为 true
-s 文件存在并且大小大于0为真 [ -s file_path ] file_path 存在并且大小大于 0为true

布尔运算符

运算符 描述 示例
! 非关系,条件结果取反 [ ! 1 -eq 2 ]为 true
-a 和关系,在[]表达式中使用 [ 1 -eq 1 -a 2 -eq 2 ]为 true
-o 或关系,在[]表达式中使用 [ 1 -eq 1 -o 2 -eq 1 ]为 true

逻辑运算符

判断符 描述 示例
&& 逻辑和在[[]]和(())表达式中或判断表达式是否为真时使用 [[ 1 -eq 1 && 2 -eq 2 ]]为 true(( 1 == 1 && 2 == 2 ))为true[ 1 -eq 1 ] && echo yes 如果&&前面表达式为 true 则执行后面的

||为逻辑或,在[[]]和(())表达式中或判断表达式是否为真时使用,[[ 1 -eq 1 || 2 -eq 1 ]]为true,(( 1 == 1 || 2 == 2 ))为true,[ 1 -eq 2 ] || echo yes如果||前面表达式为false则执行后面的

整数运算

运算符 描述
+ 加法
- 减法
* 乘法
/ 除法
% 取余
运算表达式 示例
$(()) $((1+1))
$[] $[1+1]

两个都不支持浮点运算,$(())表达式还有一个用途,三目运算 如果条件为真默认返回0,否则返回1

[root@why linux]# echo $((1<0))
0
[root@why linux]# echo $((1>0))
1
[root@why linux]# echo $((1>0?1:2))
1
[root@why linux]# echo $((1<0?1:2))
2

注意:自定义输出不支持字符串,只能指定数字

其他运算工具

命令 描述 示例
let 赋值并运算,支持++、-- let i=i+1
expr 乘法*需要加反斜杠转义* expr ( 1 + 2) expr
bc 计算器,支持浮点运算、平方等 `echo "1.2+2" bc`

shell括号用途总结

括号 用途
( ) 用途1:在运算中,先计算小括号里面的内容;用途2:数组;用途3:匹配分组
(( )) 用途1:表达式,不支持-eq这类的运算符。不支持-a和-o,支持<=、>=、<、>这类比较符和&&、 ;用途2:C 语言风格的 for(())表达式
$( ) 执行shell命令,与漂号(``)等效
$(( )) 用途1:简单算数运算;用途2:支持三目运算符$(( 表达式?数字:数字 ))
[ ] 条件表达式,里面不支持逻辑判断符
[[ ]] 条件表达式,里面不支持-a和-o,不支持<=和>=比较符,支持-eq、<、>这类比较符。支持=~模式匹配,也可以不用双引号也不会影响原意,比[]更加通用
$[ ] 简单算数运算
{ } 对逗号(,)和点点(...)起作用,比如touch{1,2}创建1和2文件,touch{1..3}创建1、2和3文件${ } 用途1:引用变量;用途2:字符串处理

流程控制

if

if list; then list; [ elif list; then list; ] ... [ else list; ] fi

单分支

if 条件表达式; then
    命令
fi

双分支

if 条件表达式; then
    命令
else
    命令
fi

多分支

if 条件表达式; then
    命令
elif 条件表达式; then
    命令
else
    命令
fi

for

正常风格

for 变量名 in 取值列表; do
命令
done

默认for循环的取值列表是以空白符分隔,如果想指定分隔符,可以重新赋值$IFS变量

#!/bin/bash
OLD_IFS=$IFS
IFS=":"
STR="12:34:45"
for i in $STR; do
    echo $i
done
IFS=$OLD_IFS # 恢复默认值

c语言风格

for (( expr1 ; expr2 ; expr3 )) ; do list ; done 常用于计数、打印数字序列

#!/bin/bash
for ((i=1;i<=5;i++)); do # 也可以 i--
echo $i
done

while

while list; do list; done

while 条件表达式; do
    命令
done

条件表达式为false会产生死循环

#!/bin/bash
while [ 1 -eq 1 ]; do
echo "yes"

与where类似的还有until,不过until为表达式为false时才循环,只需要在where判断的时候加个!即可达到until的效果

break和continue

break是终止循环,continue是跳出当前循环这两个只能通过循环使用

#!/bin/bash
N=0
while true; do
    let N++
    if [ $N -eq 5 ]; then
        break
    fi
echo $N
done
#!/bin/bash
N=0
while [ $N -lt 5 ]; do
    let N++
    if [ $N -eq 3 ]; then
        continue
    fi
echo $N
done

case

case语句一般用于选择性来执行对应部分块命令

case 模式名 in
    模式 1)
        命令
        ;;
    模式 2)
        命令
        ;;
    *)
        不符合以上模式执行的命令
esac

根据位置参数匹配不同的模式

#!/bin/bash
case $1 in
    start)
        echo "start."
        ;;
    stop)
        echo "stop."
        ;;
    restart)
        echo "restart."
        ;;
    *)
    echo "Usage: $0 {start|stop|restart}"
esac

很多的服务启动脚本就是这么实现的。 通过正则匹配

#!/bin/bash
case $1 in
    [0-9])
        echo "match number."
        ;;
    [a-z])
        echo "match letter."
        ;;
    '-h'|'--help')
        echo "help"
    *)
        echo "Input error!"
        exit
esac

select

select是一个类似for循环的语句,一般用于菜单的制作

select 变量 in 选项 1 选项 2; do
break
done

示例

#!/bin/bash
select mysql_version in 5.1 5.6; do
    echo $mysql_version
done
[root@why linux]# sh version.sh 
1) 5.1
2) 5.6
#? 1        
5.1
#? 2
5.6
#? ^C

写的更复杂一些

#!/bin/bash
PS3="Select a number: "
while true; do
    select mysql_version in 5.1 5.6 quit; do
        case $mysql_version in
            5.1)
                echo "mysql 5.1"
                break
                ;;
            5.6)
                echo "mysql 5.6"
                break
                ;;
            quit)
                exit
                ;;
            *)
                echo "Input error, Please enter again!"
                break
        esac
    done
done

在外面加个死循环,每次执行一次select就break一次,这样就能每次显示菜单了,PS3代表提示符

函数与数组

函数

func() {
command
}

示例

#!/bin/bash
func() {
echo "This is a function."
}
func
[root@why ~]# bash test.sh
This is a function

函数返回值

#!/bin/bash
func() {
VAR=$((1+1))
return $VAR
echo "This is a function."
}
func
echo $?
[root@why ~]# bash test.sh
2

函数传参

#!/bin/bash
func() {
echo "Hello $1"
}
func world
[root@why ~]# bash test.sh
Hello world

数组

数组是相同类型的元素按一定顺序排列的集合,用小括号初始化数组,元素之间用空格分隔。 格式:array=(元素1 元素2 元素3 ...)

初始化数组

[root@why pythontest]# array=(a b c)
[root@why pythontest]# echo ${array[*]}
a b c

新建数组并添加元素

[root@why pythontest]# array[3]=d
[root@why pythontest]# echo ${array[*]}
a b c d

将命令输出作为数组元素

array=($(command))

获取元素下标

[root@why pythontest]# echo ${!array[@]}
0 1 2 3

获取数组长度

[root@why pythontest]# echo ${#array[*]}
4

添加多个元素

[root@why pythontest]# array+=(e f g)
[root@why pythontest]# echo ${array[*]}
a b c d e f g

删除第一个元素

[root@why pythontest]# unset array[0]
[root@why pythontest]# echo ${array[*]}
b c d e f g

删除数组

[root@why pythontest]# unset array

seq生成的数字序列循环放到数组里面

#!/bin/bash
for i in $(seq 1 10); do
array[a]=$i
let a++
done
echo ${array[*]}

遍历数组

#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for ((i=0;i<${#IP[*]};i++)); do
echo ${IP[$i]}
done

shell正则表达式

正则表达式在每种语言中都会有,目的就是匹配符合你预期要求的字符串,正则表达式又分为基础正则表达式:BRE(basic regular express)和扩展正则表达式:ERE(extend regular express),扩展的表达式有+、?、|和()

符号 描述 示例
. 匹配除换行符(\n)之外的任意单个字符 匹配 123:```echo -e "123\n456" grep -E '1.3'```
^ 匹配前面字符串开头 匹配以abc开头的行:`echo -e "abc\nxyz" grep -E ^abc`
$ 匹配前面字符串结尾 匹配以xyz结尾的行:`echo -e "abc\nxyz" grep -E xyz$`
* 匹配前一个字符零个或多个 匹配x、xo和xoo:`echo -e "x\nxo\nxoo\no\noo" grep "xo*"`,x是必须的,批量了0零个或多个
+ 匹配前面字符1个或多个 匹配 abc和abcc:`echo -e "abc\nabcc\nadd" grep -E 'ab+'匹配单个数字:echo "113" grep -E -o '[0-9]'连续匹配多个数字:echo "113" grep -E -o '[0-9]+'`
匹配前面字符0个或1个匹配ac或abc: `echo -e "ac\nabc\nadd" grep -E 'a?c'`
[ ] 匹配中括号之中的任意一个字符 匹配a或c:`echo -e "a\nb\nc" grep -E '[ac]'`
[ .-.] 匹配中括号中范围内的任意一个字符 匹配所有字母:`echo -e "a\nb\nc" grep -E '[a-z]'`
[^] 匹配[^字符]之外的任意一个字符 匹配a或b:`echo -e "a\nb\nc" grep -E '[^c-z]'匹配末尾数字:echo "abc:cde;123" grep -E'[^;]+$'`
{n}或{n,} 匹配花括号前面字符至少n个字符 匹配abc字符串(至少三个字符以上字符串):`echo -e "a\nabc\nc" grep -E '[a-z]{3}'`
{n,m} 匹配花括号前面字符至少n个字符,最多m个字符 匹配12和123(不加边界符会匹配单个字符):`echo -e "1\n12\n123\n1234" grep -E -w -o '[0-9]{2,3}'`
\< 边界符,匹配字符串开始匹配 开始是 123 和 1234:`echo -e "1\n12\n123\n1234" grep -E -w '\<123'`
> 边界符,匹配字符串结束 匹配结束是 1234:`echo -e "1\n12\n123\n1234" grep -E '4>'`
( ) 单元或组合:将小括号里面作为一个组合 分组:匹配小括号中正则表达式或字符。\n反向引用,n是数字,从1开始编号,表示引用第n个分组匹配的内容 单元:匹配 123a 字符串`echo "123abc" grep -E -o '([0-9a-z]){4}'分组:匹配 11echo "113abc" grep -E -o '(1)\1'匹配出现xo出现零次或多次:echo -e "x\nxo\nxoo\no\noo" egrep "(xo)*"`
` ` 匹配竖杠两边的任意一个 匹配12和123:`echo -e "1\n12\n123\n1234" grep -E '12> 123>'`
\ 转义符,将特殊符号转成原有意义 1.2,匹配 1.2:1.2,否则 112 也会匹配到
Posix字符 描述
[:alnum:] 等效[a-zA-Z0-9]
[:alpha:] 等效[a-zA-Z]
[:lower:] 等效[a-z]
[:upper:] 等效[A-Z]
[:digit:] 等效[0-9]
[:space:] 匹配任意空白字符,等效[\t\n\r\f\v]
[:graph:] 非空白字符
[:blank:] 空格与定位字符
[:cntrl:] 控制字符
[:print:] 可显示的字符
[:punct:] 标点符号字符
[:xdigit:] 十六进制
空白符 描述
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\0 空值符
\b 退后一格