Shell脚本编程

849 阅读8分钟

S_HE^LL脚本编程快速入门

Q: 什么是Shell脚本?

A:

Shell脚本(英语:Shell script),又称Shell命令稿、程序化脚本,是一种电脑程序使用的文本文件,内容由一连串的shell命令组成,经由Unix Shell直译其内容后运作。被当成是一种脚本语言来设计,其运作方式与解释型语言相当,由Unix shell扮演命令行解释器的角色,在读取shell脚本之后,依序运行其中的shell命令,之后输出结果。利用shell脚本可以进行系统管理,文件操作等。

维基百科

通俗点来说, 就是将你想要执行的一系列命令放到一个文件当中, 这样:

  • 逻辑更清晰: 因为如果你在命令行下面敲着一系列的命令的话, 每打一个命令, 会有一些输出, 你可能打着打着都忘了之前打的什么命令了, 而在一个脚本文件中, 你就只用管逻辑就行了
  • 方便复用, 下次要执行这组命令时, 直接调用这个脚本文件就好了

Shell脚本类似于Windows中的批处理指令, 但不同之处在于它的效率更高, 因为它使用的是Linux下的命令.

Q: shell与shell脚本的区别?

A:

Shell就是一个命令行解释器,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统。它为用户提供了一个向Linux发送请求以便运行程序的接口系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。 Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。作为命令语言,它互动式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高阶语言中才具有的控制结构,包括循环和分支。它虽然不是 Linux系统内核的一部分,但它调用了系统内核的大部分功能来执行程序、创建文档并以并行的方式协调各个程序的运行。

百度百科

简单来说, shell是一个服务, 一个用户可以通过它来访问操作系统内核的服务.

shell脚本是一个为shell编写的脚本程序, shell这个服务可以解释脚本中的这些命令.

快速入门的示例

#!/bin/sh
cd ~
mkdir shell_tut
cd shell_tut
for ((i=0; i<10; i++)); do
    touch test_$i.txt
done

示例解释

  • 第1行:指定脚本解释器,这里是用/bin/sh做解释器的
  • 第2行:切换到当前用户的home目录
  • 第3行:创建一个目录shell_tut
  • 第4行:切换到shell_tut目录
  • 第5行:循环条件,一共循环10次
  • 第6行:创建一个test_0…9.txt文件
  • 第7行:循环体结束

从这个示例可以看出, shell支持类似C语言里面的数组操作, 我还了解到, shell甚至还支持数组等操作

变量

  • 定义变量:

    定义变量时, 变量名不加$, 例如:

    name="wang"
    

    注意: 变量名和等号之间不能有空格

    变量命名规则:

    • 首个字符必须为字母(a-z,A-Z)。
    • 中间不能有空格,可以使用下划线(_)。
    • 不能使用标点符号。
    • 不能使用bash里的关键字(可用help命令查看保留关键字)。

    还可以使用语句给变量赋值, 如下所示:

    for file in `ls /etc`
    
  • 使用变量:

    如果想要使用一个定义过的变量, 只用在变量名前面加$即可

    echo $name
    echo ${name}
    

    变量名外的花括号可加可不加, 加花括号是为了帮助解释器识别变量的边界, 推荐加上

    for skill in Java Python Shell Perl Ada; do
    	echo "I am good at ${skill}Scripts"
    done
    
  • 重定义变量:

    已经定义过的变量, 它的值是可以重新定义的, 也就是说, 它是一个可变变量.

    注意: 第二次赋值时, 变量前面不要加$

  • 只读变量

    在变量前面加readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

  • 删除变量

    使用 unset 命令可以删除变量, 变量被删除后不能再次使用;unset 命令不能删除只读变量。

  • 变量类型

    运行shell时,会同时存在三种变量: 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

    2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

    3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。

  • 特殊变量

    变量 含义
    $0 当前脚本的文件名
    $n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2, 注意, 超过10需要加上{}
    $# 传递给脚本或函数的参数个数。
    $* 传递给脚本或函数的所有参数。
    $@ 传递给脚本或函数的所有参数。被双引号(“ “)包含时,与 $* 稍有不同
    $? 上个命令的退出状态,或函数的返回值。所谓退出状态,就是上一个命令执行后的返回结果。
    ? 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

    注意: $*$@的区别

    $*$@ 都表示传递给函数或脚本的所有参数,不被双引号(“ “)包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。

    但是当它们被双引号(“ “)包含时,”$*“ 会将所有的参数作为一个整体,以”$1 $2 … $n“的形式输出所有参数;”$@“ 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。

  • 命令替换

    命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。

    #!/bin/bash
    DATE=`date`
    echo "Date is $DATE"
    
  • 变量替换

    变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值。

    形式 说明
    ${var} 变量本来的值
    ${var:-word} 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。
    ${var:=word} 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。
    ${var:?message} 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。若此替换出现在Shell脚本中,那么脚本将停止运行。
    ${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。

注释

#开头的就是注释, 会被解释器所忽略

sh里面没有多行注释, 只能每一行加一个#

Q: 如果遇到大段代码需要临时不使用, 需要注释起来, 待会又要使用怎么办呢?

A:

方法一: 可以将这段代码放在一个函数里面, 如果没有地方调用到这个函数, 那么这段代码就不会执行(解释性语言的特性) 方法二: 可以利用编辑器的快捷键, 批量注释, 解除注释

字符串

字符串是shell编程里面最常用而且最有用的数据类型, 字符串可以用单引号, 也可以用双引号, 也可以不用引号

Q: 单引号与双引号的区别?

A:

  • 单引号里面的任何字符都会原样输出, 单引号中的变量是无效的, 单引号字符串中不能出现单引号, 即使转义后也不行
  • 双引号里面可以有变量, 双引号里面可以出现转义字符

字符串操作

  • 拼接字符串

    yourName="XiaoMing"
    greeting="hello, "${yourName}" !"
    greeting_1="hello, ${yourName} !"
    
    echo ${greeting} ${greeting_1}
    
  • 获取字符串长度

    str="abcd"
    echo ${#str} #输出: 4
    
  • 提取字符串

    str="I am learning shellScripts"
    echo ${str:2:5} #输出从下标2开始的5个字符: am le
    
  • 查找字符串

    str="I am learning shellScripts"
    echo `expr index "$str" is`#找出str中第一次出现i或者s的位置
    

    expr更多用法

    STRING : REGEXP   anchored pattern match of REGEXP in STRING
    match STRING REGEXP        same as STRING : REGEXP
    substr STRING POS LENGTH   #从STRING中POS位置开始截取LENGTH个字符。POS索引是从1开始的。
    index STRING CHARS         #在STRING中查找字符CHARS首次出现的位置,没有找到返回0
    length STRING              #字符串长度
    

数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

在Shell中,用括号来表示数组,数组元素空格符号分割开。定义数组的一般形式为:

array_name=(value1 value2 ... valuen)

还可以单独定义数组的各个分量:

array_name[0]=value0
array_name[1]=value1
array_name[2]=value2

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

echo ${array_name[2]} #读取下标为2的元素
echo ${array_name[*]} #读取所有元素
echo ${array_name[@]} #读取所有元素
echo ${#array_name[*]} #获取数组长度
echo ${#array_name[@]} #获取数组长度
echo ${#array_name[1]} #获取数组中单个元素的长度

运算符

  • 算数运算符

    expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

    注意这里很坑的几点:

  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
  • 乘号(*)前边必须加反斜杠()才能实现乘法运算
  • 完整的表达式要被 包含,注意这个字符不是常用的单引号,在 Esc 键下边。
expr 1 + 1
expr 2 - 1
expr 3 \* 2
expr 4 / 2

a=1
b=2
val=`expr $a \* $b`
echo ${val}
  • 关系运算符

    关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

    运算符 说明
    -eq 检测两个数是否相等,相等返回 true。同算数运算符==
    -ne 检测两个数是否相等,不相等返回 true
    -gt 检测左边的数是否大于右边的,如果是,则返回 true。
    -lt 检测左边的数是否小于右边的,如果是,则返回 true。
    -ge 检测左边的数是否大等于右边的,如果是,则返回 true。
    -le 检测左边的数是否小于等于右边的,如果是,则返回 true。
  • 布尔运算符

    运算符 说明
    ! 非运算,表达式为 true 则返回 false,否则返回 true。
    -o 或运算(or),有一个表达式为 true 则返回 true。
    -a 与运算(and),两个表达式都为 true 才返回 true。
  • 字符串运算符

    运算符 说明
    = 检测两个字符串是否相等,相等返回 true。
    != 检测两个字符串是否相等,不相等返回 true。
    -z 检测字符串长度是否为0,为0返回 true。
    -n 检测字符串长度是否为0,不为0返回 true。
    str 检测字符串是否为空,不为空返回 true。

流程控制

  • 流程控制不可为空, 如果else分支没有语句执行, 就不要写这个else

  • sh里面的方括号是一个可执行程序, 所以方括号后面必须加空格

    if [ $foo -eq 0 ]
    
  • if else

    if condition
    then
        command1 
        command2
        ...
        commandN 
    elif condition2
    	commanda
    else
    	commandb
    fi
    

    当然, 也可以写成一行

    if `ps -ef | grep ssh`;  then echo hello; fi
    

    末尾结束的fi就是if倒过来

  • for while

    • for
    for var in item1 item2 ... itemN
    do
        command1
        command2
        ...
        commandN
    done
    

    写成一行

    for var in item1 item2 ... itemN; do command1; command2… done;
    

    C风格的for

    for (( EXP1; EXP2; EXP3 ))
    do
        command1
        command2
        command3
    done
    
    • while
    while condition
    do
        command
    done
    

    无限循环

    while :
    do
        command
    done
    

    或者

    while true
    do
        command
    done
    

    或者

    for (( ; ; ))
    
    • until
    until condition
    do
        command
    done
    
    • case
    case "${opt}" in
        "Install-Puppet-Server" )
            install_master $1
            exit
        ;;
        "Install-Puppet-Client" )
            install_client $1
            exit
        ;;
        "Config-Puppet-Server" )
            config_puppet_master
            exit
        ;;
        "Config-Puppet-Client" )
            config_puppet_client
            exit
        ;;
        "Exit" )
            exit
        ;;
        * ) echo "Bad option, please choose again"
    esac
    

    case的语法和C family语言差别很大,它需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示break

  • continue与break

    与C语言的一样

文件包含

可以使用source.关键字

source ./func.sh
. ./func.sh

作用: 读入func.sh的内容, 并执行里面的内容

函数

函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell 也支持函数。Shell 函数必须先定义后使用

  • 函数定义

    function function_name () {
        list of commands
        [ return value ]
    }
    

    function关键字是可选的

    #!/bin/bash
    hello(){
        echo 'hello';
    }
    hello #输出 hello
    
    • 调用函数只需要给出函数名即可, 不需要添加括号

    • 对于函数的返回值: 可以显式的增加return语句, 如果不加, 会将最后一条命令的运行结果作为返回值

    • 注意:return只能用来返回整数值

    • 如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。

  • 删除函数

    删除函数也可以使用 unset 命令,不过要加上 .f 选项

    unset .f function_name
    
  • 函数参数

    调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值

    #!/bin/bash
    function sum(){
        case $# in 
            0) echo "no param";;
            1) echo $1;;
            2) echo `expr $1 + $2`;;
            3) echo `expr $1 + $2 + $3`;;
            *) echo "$# params! It's too much!";;
        esac
    }
    sum 1 3 5 6
    

    运行结果

    4 params! It's too much!
    

    注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

    另外,还有几个特殊变量用来处理参数

    特殊变量 说明
    $# 传递给函数的参数个数
    $* 显示所有传递给函数的参数
    $@ 与$*相同,但是略有区别,请查看Shell特殊变量
    $? 函数的返回值

交互式

  • 打印输出

    echo: 是Shell的一个内部指令,用于在屏幕上打印出指定的字符串。

    printf:格式化输出语句。它的可移植性更好

    printf的语法:

    printf  format-string  [arguments...]
    #format-string 为格式控制字符串,arguments 为参数列表。功能和用法与c语言的 printf 命令类似。
    

    与C语言printf的不同之处

    • printf 命令不用加括号
    • format-string 可以没有引号,但最好加上,单引号双引号均可。
    • 参数比格式控制符(%)多时,格式控制符可以重用,可以将所有参数都转换。
    • arguments 使用空格分隔,不用逗号。
  • 读取输入

    read: 命令行从输入设备读入内容

    #!/bin/bash
    echo "What is your name?"
    read NAME #输入
    echo "Hello, $NAME"
    

常用的命令

1. 字符处理三剑客: grep, awk, sed

grep负责找出特定的行, awk将行拆分成多个字段, sed则实现更新, 插入, 删除等操作.

2. ps

查看进程列表

3. xargs

xargs命令的作用是将参数列表转换成小块分段传递给其他命令,以避免参数列表过长的问题。

Q: xargs与管道运算符的区别?

A:

管道符| 是将前一个命令的标准输出作为后一个命令的标准输入耳使用;而xargs命令是将前一个命令的标准输出作为后一个命令的参数而使用

4. curl

用来请求 Web 服务器。它的名字就是客户端(client)的 URL 工具的意思。

curl 的用法指南-阮一峰的博客


参考书籍:

  • Shell脚本学习教程
  • Shell脚本编程30分钟入门