Shell 脚本快速入门

分享 青牛 ⋅ 于 2016-12-05 19:18:08 ⋅ 4632 阅读

什么是Shell Script

简单说 shell脚本就是使用shell提供的功能编写的一段程序,是一个文本文件,有点像DOS年代的批处理程序。从程序员的角度将就是把 shell命令 通过变量、条件判断、循环、数据重定向等等粘合起来保存成一个文本文件,用于解决一个命令不好解决的问题。

比如你通过很多命令很多步骤完成了一项工作,当你需要再次执行的时候怎么办?你要丝毫不差的重复,差一步可能就会出问题。最好的办法你把这些过程写到shell脚本里面,shell脚本开发测试完,就可以多次执行,大大提高你的工作效率。

为什么使用Shell Script

shell脚本可以做一些操作系统自动化管理、简单文本分析处理等,它有如下一些优点:

  • 简单 它是一种高级脚本语言,语法简单宽松,只要你会用Linux命令就能开发脚本
  • 可移植 你写完的脚本只要是shell环境一致,用到的命令一致,你的脚本就可以放到各种Linux/Unix 系统执行
  • 开发速度快 shell本是个文本文件,执行不需要编译,修改完立即运行测试
  • 工具丰富 Linux提供了大量的工具,只要你合理使用,就能开发出功能强大的脚本
  • 应用粘合 比如你写个java应用,通过shell启动需要传入好多参数,比较麻烦,容易出错。你可以用shell脚本将执行工作封装起来,伪装成一个命令,这样使用会很方便。hadoop中好多命令都是通过shell封装的。

shell脚本也有一些局限性,不适合大运算量分析和处理,因为shell脚本相对于 C语言开发的应用来讲,运行速度慢,占用CPU资源比较多。所以合理的使用shell脚本,帮你提高工作效率。

Hello Shell Script

Shell脚本就一个文本文件,所以需要使用文本编辑器,考虑使用 Vim,它可以提供一些缩紧、高亮等等功能。shell脚本一般文件后缀为:.sh 。

我们来编写第一个 shell脚本 hello.sh:

#!/bin/bash
#This is my first shell program
echo "Hello Shell Script !"

我们给 hello.sh 赋予执行权限,这样我们可以直接执行了:

 [root@localhost ~]# chmod +x hello.sh
 [root@localhost ~]# ./hello.sh
 Hello Shell Script !

 #如果不赋予执行权限,就执行用shell加载执行
 [root@localhost ~]# sh hello.sh
 Hello Shell Script !

我们的hello.sh 一共有三行:

  • 第一行: #! 表示声明 shell类型,就是要告诉shell你的脚本是按照什么语法写的,用什么命令加载执行。这里声明是bash脚本,一般是用绝对路径,你也可以 这样写:#!env bash 。告诉shell从环境变量里找 bash。 如果是csh,可以写:#!/bin/csh 。awk脚本可写:#!/bin/awk -f
  • 第二行: #开头shell脚本里面表示注释 , 养成良好的注释习惯,这里一般说明脚本功能、作者、修改历史等等
  • 第三行: 脚本的主体内容,我们这里就一个命令

    需要注意的几个点:

    1. ! 这一行必须放在首行,这一行的长度尽量不要超过 64 个字符。
    2. 考虑可移植行,要确认你的绝对路径别的服务器和环境是否存在。解释器不存在会报错:bad interpreter: No such file or directory
    3. 声明的shell 后面不要加任何字符,后面的内容会当做参数传给shell, 看看 awk脚本的声明。
    4. 如果你想你的脚本向使用操作系统命令一样使用,那就需要将脚本的存放路径追加到 PATH环境变量中

    把我们的hello.sh 模拟成系统命令:

    #将脚本存放目录追加到PATH环境变量,我的脚本在/root目录下
    [root@localhost ~]# export PATH=/root:$PATH
    #不用指定目录就可以找到
    [root@localhost ~]# hello.sh
    Hello Shell Script !
    [root@localhost ~]# which hello.sh
    /root/hello.sh
    
    #去掉后缀,是不是更像命令
    [root@localhost ~]# mv hello.sh hello
    [root@localhost ~]# hello
    Hello Shell Script !
    
    #切换当前目录试试,是不是也能成功执行
    [root@localhost ~]# cd /tmp/
    [root@localhost tmp]# hello
    Hello Shell Script !

Bash 变量

什么是变量?

说白了就一个字符串代表不同的值。HOME就是一个字符串,它是变量的名字,它的值是用户的根目录。root用户登录进来,HOME=/root;海牛用户进来HOME=/home/hainiu。如果你想用用户根目录,那直接用HOME变量就行了,它的值已经由bash帮你设置好了。使用的时候bash会帮你把变量替换成相应的值。

#root用户下
[root@localhost ~]# echo "my home dir is:$HOME"
my home dir is:/root

#切换到hainiu用户下
[root@localhost ~]# su - hainiu
[hainiu@localhost ~]$ echo "my home dir is:$HOME"
my home dir is:/home/hainiu

#自己定义一个变量
[root@localhost ~]# MY_VAR="Hello Var"
[root@localhost ~]# echo $MY_VAR
Hello Var

前面我们讲了环境变量,你可以环境变量是特殊的变量,环境变量是在用用户登录环境中全局生效的。HOME是个环境变量;MY_VAR是你自己定义的一般变量,只有在当前会话环境存在。这就是变量的作用域,即 可使用的有效范围。为什么使用变量?变量可以实现应用接口标准定义,实现快速可变,方便用户环境的配置和管理。

变量定义的语法

上面介绍了什么是变量,也体验了变量的使用。变量定义和使用是有一定语法规则的:

  1. 变量无需事先声明,不严格区分类型,默认为字符串类型,为定义时为空串
  2. 变量名首字符必须为字母,名字只能用字母数字下滑线,起名字不要用bash关键字。这是非法的名字:8var、var-8
  3. 变量定义或赋值时,变量名与值之间用等号连接:变量名=值,等号两边不能有空格,值中有空格或特殊字符需要用双引号或单引号引起来。
  4. 可以用加反斜杠()把特殊字符变成一般字符,比如: 变量值里面有空格可以前面加 反斜杠就不用加引号,MY_VAR=Hello\ Var
  5. 使用 $变量名 或 ${变量名} 形式获取变量值。在使用字符串的时候变量和变量直接或变量和其它值直接没有特殊字符分开,那么就用${变量名}形式使用变量。
  6. 用 unset 取消变量
  7. 单引号 和双引号引用有区别,单引号里面的所有字符都是普通字符,如 '$MY_VAR' 就不是输出MY_VAR的值了,而是直接输出 $MY_VAR,因为单引号引起来就不是变量了。双引号会保留变量特性,用值替换。
  8. 将一个命令的结果作为输出值,可以用反单引号(或者$())引起来赋值给变量
  9. declare 定义指定类型的变量,在使用shell脚本的时候会使用。用法:
declare -aAfirx 变量名
-a  定义数组变量
-A  定义map变量
-f 仅仅使用函数名
-i 定义数值类型变量
-r 定义只读变量
-x 将变量设为环境变量

演示一下:

# NO_VAR变量没有定义,可以直接使用不报错
[root@localhost ~]# echo $NO_VAR

#有空格用双引号引起来
[root@localhost ~]# MY_VAR="Hello Var"
[root@localhost ~]# echo $MY_VAR
Hello Var

#定义环境变量
[root@localhost ~]# export ENV_VAR="my env var"
[root@localhost ~]# declare -x ENV_VAR="declare env var"

#${变量名}的形式
[root@localhost ~]# echo ${MY_VAR}
Hello Var

#反斜杠转换特殊字符
[root@localhost ~]# MY_VAR=Hello\ Var

#双引号引用变量
[root@localhost ~]# echo "my var is: $MY_VAR"

#单引号引用变量,$MY_VAR 没有被当做变量
[root@localhost ~]# echo 'my var is: $MY_VAR'

#只读变量
[root@localhost ~]#readonly MY_VAR

#将ls命令的输出 赋值给FILE_LIST 变量
[root@localhost ~]# FILE_LIST=`ls`
[root@localhost ~]# echo $FILE_LIST
anaconda-ks.cfg test.txt

[root@localhost ~]# echo `hostname`

数组变量

数组是一个很重要的数据结构,bash支持三种方法定义数组:

#常量列表的方式
arr1=("a" "ab" "bc")

#直接下标引用,自动定义
arr2[0]="1"
arr2[1]="3"
arr2[2]="4"

#declare 命令定义数组
declare -a arr3
arr3[0]="hello"
arr3[1]="word"

代码示例:

arr1=("a" "ab" "bc")

#获取数组长度,表示数组的所有元素
echo "array length:" ${#arr1[@]}

#按索引访问数组元素,索引从0开始
echo "first element:" ${arr1[0]}
#arr1[@] 将数组转化成字符串形式,元素用空格隔空
echo "all elements:" ${arr1[@]}

#数组删除
unset arr1[1]
echo "all elements:" ${arr1[@]}

arr1[3]="ab"

#数组切片,返回是字符串类型,元素用空格分开
str=${arr1[@]:0:2}
echo $str

#用()将字符串结果转化成数组类型
arr2=(${arr1[@]:1:2})
echo "all elements:" ${arr2[@]}

#数组遍历
for a in ${arr1[@]}
do
    echo $a
done

map变量

key-value 的字典结构我们也可能会用到(bash 4.1.2版本及以上才有map),我简单看看使用方法:

#定义空map,注意是 -A
declare -A map2=()

#定义时初始化数据
declare -A map1=(["k1"]="v1" ["k2"]="v2" ["k3"]="v3")

#输出所有 key
echo ${!map1[@]}

#输出所有 value
echo ${map1[@]}

#增加新值
map1["k4"]="v4"

#获取key的值
echo ${map1["k2"]}

#删除key
unset map1["k1"]

#map遍历
for k in ${!map1[@]}
do
    echo ${map1[$k]}
done

特殊变量

bash 定义了很多内置的变量,在写脚本的时候经常用到:

语法 说明
$0 脚本名字
$n n为从1开始的数字,表示脚本的第几个参数,比如:$1 表示第一个参数, $2 表示第二个
$# 位置参数的个数
$* 所有的位置参数(作为单个字符串),表示:"$1 $2 $3 $4" ,默认分隔符为空格
$@ 所有的位置参数(每个都作为独立的字符串),表示:"$1" "$2" "$3" "$4"
$? 上一个命令的返回值,一般情况:成功执行则$?的值为0,否则为其他非零值
$$ 脚本的进程ID(PID)
$! 运行在后台的最后一个作业的进程ID(PID)

不加双引号("")时, $ 和 $@ 被展开时的行为是一样的,加上双引号 $ 就是一个字符串。

演示一下:

我们写个脚本 special_var.sh

#!/bin/bash

echo "script name:" $0
echo "parameter counts:" $#
echo "parameter list:" $*

echo '$* in for:'
for p in "$*"
do
echo $p
done

echo "parameter list:" $@
echo '$@ in for:'
for p in $@
do
echo $p
done

echo "first:" $1  "second:" $2
echo "ret:" $?
echo "my processid:" $$
echo "last daemon job id:" $!

执行一下:

#有空格或特殊符号想作为一个参数传入,要用双引号引起来
[root@localhost ~]# sh ./special_var.sh "my p1" p2 p3 p4
script name: ./special_var.sh
parameter counts: 4
parameter list: my p1 p2 p3 p4
$* in for:
my p1 p2 p3 p4
parameter list: my p1 p2 p3 p4
$@ in for:
my
p1
p2
p3
p4
first: my p1 second: p2
ret: 0
my processid: 2579
last daemon job id: <===没有启动后台任务

shift

shift 翻译过来是挪动的意思,在脚本里面它挪动是参数,调用一次参数列表会向左移动,就相当于把最左边的参数去掉了,参数的总数也会相应减少。

shift n 表示移动 n 个,不指定数量默认是一个。

编写一个测试脚本:shift_parameter.sh

#!/bin/bash
echo "parameter counts:" $#
echo "parameter list:" $*

shift

echo "parameter counts:" $#
echo "parameter list:" $*

shift 3

echo "parameter counts:" $#
echo "parameter list:" $*
[root@localhost ~]# sh ./shift_parameter.sh p1 p2 p3 p4 p5 p6
parameter counts: 6
parameter list: p1 p2 p3 p4 p5 p6
parameter counts: 5
parameter list: p2 p3 p4 p5 p6
parameter counts: 2
parameter list: p5 p6

终端输入

怎样读入用户的输入?比如一下命令交互提示shell能支持吗? 当然可以很简单,使用read 获取用户输入,在编写shell脚本时经常用到。用法:read 变量,回车后等待用输入,输入完成后,用户的输入已经被放置到变量中。

[root@localhost ~]# read input
hello
[root@localhost ~]# echo $input
hello

# -p 增加提示
[root@localhost ~]# read -p "Please input a str:" str
Please input a str:mystr
[root@localhost ~]# echo $str
mystr

运算符

bash主要提供 算数运算符、关系运算符、布尔运算符、文件测试运算符。我们下面来看看:

算数运算符

假设 a=6 b=3

运算符 说明 示例
+ 加法 $[a+b] 结果为 9
减法 $[a-b] 结果为 3
* 乘法 $[a*b] 结果为 18
/ 除法 $[a/b] 结果为 2
取余 $[a%b] 结果为 0

实现表达是计算常用有多种写法:

  • 双括号法
#!/bin/bash
a=6
b=3
((result=$a + $b))
echo $result
  • 方括号法
#!/bin/bash
a=6
b=3
result=$[$a + $b]
echo $result
  • expr命令
#!/bin/bash
a=6
b=3
result=`expr $a + $b`
echo $result

推荐使用圆括号或方括号方法,使用expr命令略显笨重。上述都是整数运算,要想实现浮点数运算,可以借助 bc计算器:

result=`echo "scale=3; 1/3" | bc`

关系运算

运算符 说明
-eq 检测两个数是否相等,相等返回 true
-ne 检测两个数是否不相等,不相等返回 true
-lt 检测左边的数是否小于右边的,如果是,则返回 true
-le 检测左边的数是否小于等于右边的,如果是,则返回 true
-gt 检测左边的数是否大于右边的,如果是,则返回 true
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true
== 检测两个字符串是否相等,相等返回 true
!= 检测两个字符串是否相等,不相等返回 true
-z 检测字符串长度是否为0,为0返回 true
-n 检测字符串长度是否为0,不为0返回 true
< 小于,字符串比较,按字符序比较; 在[]表达式中,必须转义才能用 \<
> 大于,字符串比较,按字符序比较;在[]表达式中,必须转义才能用 >
str 检测字符串是否为空,不为空返回 true

关系运算一个等号和两个等号效果是一样的,但一般使用双等号,避免与赋值混。

实现关系有几种方式:

  • test 命令

使用方法:test EXPRESSION

[root@localhost ~]# test 3 -gt 1 && echo true || echo false
true

[root@localhost ~]# test -z "" && echo true || echo false
true

[root@localhost ~]# test "" && echo true || echo false
false
  • 表达式[]

使用方法:[ EXPRESSION ] 注意表达式两边要有空格

[root@localhost ~]# [ 3 -gt 1 ] && echo true || echo false
true

[root@localhost ~]# [ -z "" ] && echo true || echo false
true

[root@localhost ~]# [ "" ] && echo true || echo false
false

[root@localhost ~]# [ "b" \> "a" ] && echo true || echo false
true
  • 增强表达式[[]]

[[]] 运算符只是[]运算符的扩充。能够支持<,>符号运算不需要转义符,它还是以字符串比较大小。里面支持逻辑运算符:|| && 。

使用方法:[[ EXPRESSION ]] 注意表达式两边要有空格

[root@localhost ~]# [[ 3 -gt 1 ]] && echo true || echo false
true

[root@localhost ~]# [[ -z "" ]] && echo true || echo false
true

[root@localhost ~]# [[ "" ]] && echo true || echo false
false

[root@localhost ~]# [[ "b" > "a" ]] && echo true || echo false
true

布尔运算

运算符 说明
! 非运算,表达式为 true 则返回 false,否则返回 true
-o 或运算,有一个表达式为 true 则返回 true
-a 与运算,两个表达式都为 true 才返回 true
或运算, 在[]表达式内不能用,两个 方括号表达式之间是可以用的
&& 或运算, 在[]表达式内不能用,两个 方括号表达式之间是可以用的

演示一下:

[root@localhost ~]# [ ! -z "" ] && echo true || echo false
false

[root@localhost ~]# [ 1 -gt 0 -a 4 -gt 1 ] && echo true || echo false
true

[root@localhost ~]# [ 1 -gt 0 ] && [ 4 -gt 1 ] && echo true || echo false
true

# 增强型表达式可以在内部使用
[root@localhost ~]# [[ 1 -gt 0 &&  4 -gt 1 ]] && echo true || echo false
true

&&和|| 支持短路径, 而-a和-o的方式会将两边表达式一起解析计算,而达不到短路径测试的效果。推荐使用 &&和||的写法,这样更灵活,更好理解。

文件测试运算

如果你想判断一个文件是否存在,是否有某种权限或者是什么类型的文件,一个操作符就搞定。我们来看看:

运算符 说明
-b 检测文件是否是块设备文件,如果是,则返回 true
-c 检测文件是否是字符设备文件,如果是,则返回 true
-d 检测文件是否是目录,如果是,则返回 true
-f 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
-g 检测文件是否设置了 SGID 位,如果是,则返回 true
-k 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true
-p 检测文件是否是具名管道,如果是,则返回 true
-u 检测文件是否设置了 SUID 位,如果是,则返回 true
-r 检测文件是否可读,如果是,则返回 true
-w 检测文件是否可写,如果是,则返回 true
-x 检测文件是否可执行,如果是,则返回 true
-s 检测文件是否为空(文件大小是否大于0),不为空返回 true
-e 检测文件(包括目录)是否存在,如果是,则返回 true

[root@localhost ~]# [ -f /etc/passwd ] && echo true
true

[root@localhost ~]# [ -x /etc/passwd ] || echo false
false

[root@localhost ~]# [ -d /etc/passwd ] || echo false
false

[root@localhost ~]# [ -u /bin/passwd ] && echo true
true

条件控制

bash 支持 if、if-else、if-else-if三种条件判断但是写法,和前面学的awk语法有点不太一致。

单分支条件控制

语法:

if [ 条件表达式 ]; then

条件判断为true时执行这里

fi <==将 if 反过来写, 表示if结束

示例代码:

#!/bin/bash
read -p "Please input a number:" num
r=`echo $num | sed 's/[0-9]//g' `

if [ -z "$r" ]; then
   echo $num " is a number"
fi

双分支条件控制

语法:

if [ 条件表达式 ]; then

条件判断为true时执行这里

else

条件判断为false时执行这里

fi

示例代码:

#!/bin/bash
read -p "Please input a number:" num
r=`echo $num | sed 's/[0-9]//g' `

if [ -z "$r" ]; then
   echo $num " is a number"
else
   echo $num " is not a number "
fi

多分支条件控制

语法:

if [ 条件表达式一 ]; then
    条件表达式一判断为true时执行这里
elif [ 条件判断式二 ]; then
    条件表达式二判断为true时执行这里
elif [ 条件判断式三 ]; then
    条件表达式三判断为true时执行这里
else
    以上表达式都不成立时,执行这里
fi

示例代码:

#!/bin/bash
read -p "Please input a number:" num
r=`echo $num | sed 's/[0-9]//g' `

if [ -z "$r" ] && [ "$num" -lt 10 ]; then
     echo $num " smaller than 10"
elif [ -z "$r" ] && [ $num -lt 50 ]; then
      echo $num " smaller than 50"
elif [ -z "$r" ] && [ $num -ge 50 ]; then
      echo $num " bigger than 50"
else
    echo $num " is not a number "
fi

case 多分支条件控制

if-else 实现多条件测试,如果是根据确切的取值集合建立不同的分支,则使用 case的语法就更方便了。

语法:

case $var in   <== case 和 in 是关键字
    "变量第一个取值")  <======取值内容最好用双引号引起来 加右小括号
    代码块   <===== 等于前面取值,执行这里
    ;;  <====每个代码块结束跟两个分号
    "变量第二个取值")
    代码块
    ;;
    "变量第三个取值")
    代码块
    ;;
    *)
    代码块
    ;;

esac   <===== 关键字,case 反过来,有点意思吧

示例代码:

#!/bin/bash

action=$1

case $action in
     "start")
      echo "start service"
      ;;

      "stop")
      echo "stop service"
      ;;

      "restart")
      echo "restart service"
      ;;

      *)
      echo "Usage: $0 start|stop|restart"
      ;;
 esac

循环

除了条件判断式之外,循环也是程序设计的一个重要部分。简单说循环就是不断的执行某段代码,直到满足用户设定终止条件时终止退出。所以说终止条件的设置是最关键的一部分,否则就会死循环。

while 循环

while 也很好理解就是当 条件一直满足时就进行循环,否则退出。我们看一下语法:

while [ condition ] <===条件判断表达式,满足条件则循环否则退出
do <==循环开始
循环体代码段
done <====循环结束

我们拿个小例子说明一下:

循环接收用户的输入验证是不是数字,当输入quit的时候终止退出。

#!/bin/bash

read -p "Please input a number:" num
while [ "$num" != "quit" ]
do
    r=`echo $num | sed 's/[0-9]//g' `

    if [ -z "$r" ]; then
       echo $num " is a number"
    else
       echo $num " is not a number"
    fi

    read -p "Please input a number:" num
done

while 循环的另外一种形式,until .. do,可以简单理解为直到什么什么条件终止循环,否则一直循环执行。我们看一下语法:

until [ condition ] <===条件判断表达式,满足条件则退出循环否则一直循环
do <==循环开始
循环体代码段
done <====循环结束

我们改写一下上面的例子:

#!/bin/bash

read -p "Please input a number:" num
until [ "$num" == "quit" ]
do
    r=`echo $num | sed 's/[0-9]//g' `

    if [ -z "$r" ]; then
       echo $num " is a number"
    else
       echo $num " is not a number"
    fi

    read -p "Please input a number:" num
done

for 循环

对于while循环来讲循环次数不固定,循环次数由终止条件和循环体程序块决定。如果你明确需要循环几次,那优先考虑for循环,这样相对来说方便快捷。我们来看一下语法:

for (( 控制变量的初始化; 循环的条件; 循环控制变量的更新 ))
do
程序块
done

这种语法适合于数值方式的运算当中,我们看看三个表达式的含义:

  • 控制变量的初始化:设置控制变量的初始值,可以理解为从多少开始循环,比如i=1
  • 循环的条件: 直到什么条件循环终止,这里可以理解为循环的限制值,比如 i<= 10
  • 循环控制变量的更新: 以固定步长增加或减少,更新控制变量,比如 i=i+1

演示一下:

#!/bin/bash

for ((i=1; i<=10; i++))
do
    echo $i
done

上面讲的是用数值的方式,控制循环范围,一种常用的形式是 for in 语法:

for 循环变量 in 数据列表
do
程序块
done

循环变量就是数据列表里面当前循环值;数据列表是一个确定的数据集合,可以是字符串列表、整数序列、通配符列表等。

举例说明一下:

#!/bin/bash

#字符串常量枚举
for s in "hello" "world" "you"
do
   echo $s
done

#默认按空格分割字符串
str="hello world how you"
for s in $str
do
  echo $s
done

# {} 变量展开生成数字序列
for i in {1..10}
do
  echo $i
done

# {} 变量展开生成单字符序列
for c in {a..z}
do
   echo $c
done

# 通配符生成数据列表
for f in /etc/*.conf
do
   echo $f
done

# 命令输出列表,默认换行分割
for f in `ls /sbin/a*`
do
   echo $f
done

#遍历参数列表
for p in $@
do
   echo $p
done

# 通过 seq命令生成整数序列
for i in `seq 10`
do
    echo $i
done

字符串变量操作

简单操作

语法 说明
${var} 变量var的 值, 与$var相同
${#var} 获取字符串长度
${var:position} 在$var中,从位置$position开始提取子串,索引从0开始
${var:position:length} 在$var中, 从位置$position开始提取长度为$length的子串

演示一下:

[root@localhost tmp]# var=hello
[root@localhost tmp]# echo ${var}
hello
[root@localhost tmp]# echo ${#var}
5
[root@localhost tmp]# echo ${var:2}
llo
[root@localhost tmp]# echo ${var:1}
ello
[root@localhost tmp]# echo ${var:2:2}
ll

变量测试

变量测试就是在变量未设置值或为空时,怎么处理给定初值。你可以用一个if判读实现,但是shell支持直接用符号表达式方式实现。

语法 说明
${var:+value} var变量是否有非空值,如果是 结果为:value值,否则为:空
${var+value} var变量是否未设置值,如果是 结果为:空;否则为: value值
${var:-value} var变量是否有非空值,如果是,结果为:$var;否则为: value值
${var-value} var变量是否未设置值,如果是, 结果为:value值;否则为:$var
${var:=value} var变量是否有非空值,如果是, var不变,结果为:$var;否则为:var=$value值,结果为:value值
${var=value} var变量是否未设置值,如果是, var=$value值,结果为:value值;否则为:var不变,结果为:$var
${var:?value} var变量是否有非空值,如果是,结果为:$var;否则报错,将value值输出到stderr
${var?value} var变量是否未设置值,如果是, 报错,将value值输出到stderr;否则为:$var

看起来不太好记,有冒号和没有冒号效果不一样,主要是为空的时候怎么处理?为空可能有两种含义:变量未设置或者变量设置成空。这要区别对待。

可以注意一点,等号是可能修改修变量var的值的,而其它的只是读取使用不会修改var变量。实践一下:

#var是一个未设置值的变量
[root@localhost ~]# str=${var:-new_value}
[root@localhost ~]# echo str:$str var:$var
str:new_value var:
[root@localhost ~]# str=${var-new_value}
[root@localhost ~]# echo str:$str var:$var
str:new_value var:

#var 设置成空值
[root@localhost ~]# var=""
[root@localhost ~]# str=${var:-new_value}
[root@localhost ~]# echo str:$str var:$var
str:new_value var:
[root@localhost ~]# str=${var-new_value}
[root@localhost ~]# echo str:$str var:$var
str: var:

#取消变量,达到未设置值的效果
[root@localhost ~]# unset var
[root@localhost ~]# str=${var:=new_value}
[root@localhost ~]# echo str:$str var:$var
str:new_value var:new_value
[root@localhost ~]#

字符串删除

语法 说明
${var#keyword} 从变量内容的开头开始向后搜索,删除最短匹配keyword成功的数据
${var##keyword} 从变量内容的开头开始向后搜索,删除最长匹配keyword成功的数据
${var%keyword} 从变量内容的结尾开始向前搜索,删除最短匹配keyword成功的数据
${var%%keyword} 从变量内容的结尾开始向前搜索,删除最长匹配keyword成功的数据

keyword 支持通配符,可以按通配符匹配删除。

演示一下:

[root@localhost ~]# VAR=abababccddee
#从开头最长匹配删除,*表示ab前可以有任意字符,ababab符合条件被删除
[root@localhost ~]# echo ${VAR##*ab}
ccddee

#从开头最短匹配删除,最短匹配一个就结束
[root@localhost ~]# echo ${VAR#*ab}
ababccddee

#从结尾最长匹配删除
[root@localhost ~]# echo ${VAR%%c*}
ababab

#从结尾最短匹配删除
[root@localhost ~]# echo ${VAR%c*}
abababc

字符串替换

语法 说明
${var/search/replacement} 从开头匹配search字符串,匹配成功用 replacement字符串替换
${var//search/replacement} 全局匹配search字符串,匹配成功用 replacement字符串全局替换

演示一下:

[root@localhost ~]# VAR=abababccddee

#局部替换ab为xx,就替换第一个
[root@localhost ~]# echo ${VAR/ab/xx}
xxababccddee

#局部替换ab为xx,就替换第一个
[root@localhost ~]# echo ${VAR//ab/xx}
xxxxxxccddee

函数定义

简单说shell函数就是一个你用脚本自定义的命令,通过函数名字想命令一样调用使用。我们来看看函数定义的语法:

function 函数名()
{
   函数体
}

函数定义的说明:

  • 函数名要保证全局唯一,遵循变量名规范
  • 函数必须在调用之前定义,shell脚本是顺序执行,前面没有定义使用会报错
  • function 关键字可有可无,推荐加上
  • 函数定义不带任何参数,参数获取和shell脚本获取参数一致,通过$0,$1,$2... 获取
  • 可以使用return 返回函数运行结果,返回值范围 0 ~ 255。调用这通过$? 获取返回值
  • 可以看出return 不是返回数据用的,如果需要返回数据则输出到标准终端,然后调用者解析标准输出

演示一下:

#!/bin/bash

#定义一个名字为 fun的函数
function fun()
{
   echo "parameter list:"
   for p in $@
   do
      echo $p
   done

   echo "first:" $1 "second:" $2

   return 0
}

#直接命令方式调用
fun p1 p2 p3 p4
echo "fun return:" $?

#获取标准输出
ret=`fun p1 p2 p3 p4`
echo "fun return:" $?
echo -e "$ret"

#另一写法
ret=$(fun p1 p2 p3 p4)
echo "fun return:" $?
echo -e "$ret"

变量作用域

shell脚本里面的变量没有特殊说明都是全局的,即函数外面定义的变量函数内部可以使用;函数内部定义的变量函数外部也可以使用。这点有点不安全,有些情况会出现混乱问题,出现问题很难排查。所以建议函数内部定义的变量,没有特殊用途都加上 local 关键字,把变量生命成局部变量。

#!/bin/bash
var1=10
echo "--var1:" $var1

function fun1()
{
   echo "----var1:" $var1
   var1=20
   echo "----var1:" $var1
}

fun1
echo "--var1:" $var1

function fun2()
{
    local f_var1="local"
    echo "----f_var1" $f_var1
    f_var2="global"
    echo "----f_var2" $f_var2
}

fun2

echo "--f_var1" $f_var1
echo "--f_var2" $f_var2

运行结果:

[root@localhost ~]# sh ./fun.sh
--var1: 10
----var1: 10
----var1: 20
--var1: 20
----f_var1 local
----f_var2 global
--f_var1 <=====局部变量外面访问不到
--f_var2 global

总结

可以看出shell脚本开发可简单,只要按照语法将linux工具使用粘合到一起就可以了,shell脚本本身没有什么工具包和函数库,只提供一下内部关键字和基础处理操作符命令。你所需要的大部分功能都需要依靠调用外部命令来实现,所以最根本的还是要熟练使用Linux的常用命令工具。

开发的时候可能会遇到语法错误或者非预期的结果,需要调试时,sh 可以增加 -x 参数选项(sh -x script.sh),这样会把整个执行过程和变量的变化过程都打印出来,方便你排查脚本问题。

版权声明:原创作品,允许转载,转载时务必以超链接的形式表明出处和作者信息。否则将追究法律责任。来自海汼部落-青牛,http://hainiubl.com/topics/8
本帖由 青牛 于 6年前 解除加精
回复数量: 0
    暂无评论~~
    • 请注意单词拼写,以及中英文排版,参考此页
    • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
    • 支持表情,可用Emoji的自动补全, 在输入的时候只需要 ":" 就可以自动提示了 :metal: :point_right: 表情列表 :star: :sparkles:
    • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif,教程
    • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
    Ctrl+Enter