shell脚本详解

一.Shell简介

Shell是Linux系统的用户界面,它提供用户与内核的一种交互方式。它接收用户输入的命令,并把它送入内核去执行,是一个命令解释器,,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统。Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。作为命令语言,它互动式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高阶语言中才具有的控制结构,包括循环和分支。它虽然不是 Linux系统内核的一部分,但它调用了系统内核的大部分功能来执行程序、创建文档并以并行的方式协调各个程序的运行。
在这里插入图片描述
在这里插入图片描述

二.Shell脚本执行方式

  1. ./xxx.sh
    ./ 的意思是说在当前的工作目录下执行xxx.sh。如果不加上./,bash可能会响应找到不到xxx.sh的错误信息。因为目前的工作目录(/root/bin)可能不在执行程序默认的搜索路径之列,也就是说,不在环境变量PASH的内容之中。查看PATH的内容可用 echo $PASH 命令。chmod +x xxx.sh为给文件添加可执行权限
  2. bash xxx.sh
    切换到工作目录下(/root/bin),直接使用bash 或sh 来执行bash shell脚本,不用给shell脚本加执行权限即可
  3. . xxx.sh
    在当前的shell环境中执行用 . hello.sh或source hello.sh来执行 bash shell脚本

三.Shell语法

3.1 定义以开头:#!/bin/sh

#!用来声明脚本由什么shell解释,否则使用默认shell

#!/bin/sh

3.2 注释

单个"#"号代表注释当前行
<<EOF xxxxxxx EOF 能注释多行

# 这里是注释
<<EOF 
这里是多行注释
这里是多行注释
这里是多行注释
EOF

3.3 变量

variable=value			#普通赋值方法
variable='value'
variable="value"		#当变量的值(字符串)中包含任何空白符时,加双引号

在脚本中定义的变量,脚本结束后,变量和它的值还在,命令行echo $变量名可以查看。

可以通过typeset或declare可以设置变量类型。

# 表示将data设置为int型
typeset -i data 

使用一个定义过的变量,只要在变量名前面加美元符号$即可,如:

value="严长生"
echo $value
echo ${value}
形式说明
$(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的值。

3.4 变量的作用域

运行shell时,会同时存在三种变量:

  • 局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  • 环境变量:所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
  • shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

3.5 输出echo

  1. 打印环境变量PATH的值
    echo $PATH
    
    在这里插入图片描述

3.6 Shell预定义变量

$#:传给 shell 脚本参数的数量
$*:传给 shell 脚本参数的内容
$1、$2、$3、、$9:运行脚本时传递给其的参数,用空格隔开
$?: 命令执行后返回的状态
$?:用于检查上一个命令执行是否正确(在Linux中,命令退出状态为0表示该命令正确执行,任何非0 值表示命令出错)。
$0:当前执行的进程名
$$:当前进程的进程号
$$:变量最常见的用途是用作临时文件的名字以保证临时文件不会重复

3.7 字符串操作

  1. 计算字符串长度

    string="abcd"
    echo ${#string} #输出 4
    
  2. 提取子串

    string="alibaba is a great company"
    echo ${string:1:4} #输出liba
    
  3. 拼接字符串

    your_name="qinjx"
    greeting="hello, "$your_name" !"
    greeting_1="hello, ${your_name} !"
    echo $greeting $greeting_1
    
  4. 查找字符串

    string="alibaba is a great company"
    echo `expr index "$string" is`
    
    text="read phy addr: 0x1f  reg: 0x1f   value : 0x0"
    str="value : "
    value=${text#*$str}
    
    #打印value的值
    echo "$value"
    0x0
    
  5. 替换字符

    string="123123"
    echo ${string/3/0}			#用0替换第一个遇见的3
    echo ${string//3/0}			#用0替换串中所有3
    

3.8 if语句

  1. 基本用法

    if [ command ]; then
         符合该条件执行的语句
    fi
    
  2. 扩展用法

    if [ command ];then
         符合该条件执行的语句
    elif [ command ];then
         符合该条件执行的语句
    else
         符合该条件执行的语句
    fi
    
  3. 文件/目录判断:

    常用的:
    [ -a FILE ] 如果 FILE 存在则为真。
    [ -d FILE ] 如果 FILE 存在且是一个目录则返回为真。
    [ -e FILE ] 如果 指定的文件或目录存在时返回为真。
    [ -f FILE ] 如果 FILE 存在且是一个普通文件则返回为真。
    [ -r FILE ] 如果 FILE 存在且是可读的则返回为真。
    [ -w FILE ] 如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
    [ -x FILE ] 如果 FILE 存在且是可执行的则返回为真。
     
    不常用的:
    [ -b FILE ] 如果 FILE 存在且是一个块文件则返回为真。
    [ -c FILE ] 如果 FILE 存在且是一个字符文件则返回为真。
    [ -g FILE ] 如果 FILE 存在且设置了SGID则返回为真。
    [ -h FILE ] 如果 FILE 存在且是一个符号符号链接文件则返回为真。(该选项在一些老系统上无效)
    [ -k FILE ] 如果 FILE 存在且已经设置了冒险位则返回为真。
    [ -p FILE ] 如果 FILE 存并且是命令管道时返回为真。
    [ -s FILE ] 如果 FILE 存在且大小非0时为真则返回为真。
    [ -u FILE ] 如果 FILE 存在且设置了SUID位时返回为真。
    [ -O FILE ] 如果 FILE 存在且属有效用户ID则返回为真。
    [ -G FILE ] 如果 FILE 存在且默认组为当前组则返回为真。(只检查系统默认组)
    [ -L FILE ] 如果 FILE 存在且是一个符号连接则返回为真。
    [ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则返回为真。
    [ -S FILE ] 如果 FILE 存在且是一个套接字则返回为真。
    [ FILE1 -nt FILE2 ] 如果 FILE1 比 FILE2 新, 或者 FILE1 存在但是 FILE2 不存在则返回为真。
    [ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 老, 或者 FILE2 存在但是 FILE1 不存在则返回为真。
    [ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则返回为真。
    
  4. 字符串判断

    [ -z STRING ] 如果STRING的长度为零则返回为真,即空是真
    [ -n STRING ] 如果STRING的长度非零则返回为真,即非空是真
    [ STRING1 ]  如果字符串不为空则返回为真,与-n类似
    [ STRING1 == STRING2 ] 如果两个字符串相同则返回为真
    [ STRING1 != STRING2 ] 如果字符串不相同则返回为真
    [ STRING1 < STRING2 ] 如果 “STRING1”字典排序在“STRING2”前面则返回为真。
    [ STRING1 > STRING2 ] 如果 “STRING1”字典排序在“STRING2”后面则返回为真。
    
  5. 数值判断

    [ INT1 -eq INT2 ] INT1和INT2两数相等返回为真 ,=
    [ INT1 -ne INT2 ] INT1和INT2两数不等返回为真 ,<>
    [ INT1 -gt INT2 ] INT1大于INT2返回为真 ,>
    [ INT1 -ge INT2 ] INT1大于等于INT2返回为真,>=
    [ INT1 -lt INT2 ] INT1小于INT2返回为真 ,<
    [ INT1 -le INT2 ] INT1小于等于INT2返回为真,<=
    
  6. 逻辑判断

    [ ! EXPR ] 逻辑非,如果 EXPR 是false则返回为真。
    [ EXPR1 -a EXPR2 ] 逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。
    [ EXPR1 -o EXPR2 ] 逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。
    [ ] || [ ] 用OR来合并两个条件
    [ ] && [ ] 用AND来合并两个条件
    
  7. 其他判断

    [ -t FD ] 如果文件描述符 FD (默认值为1)打开且指向一个终端则返回为真
    [ -o optionname ] 如果shell选项optionname开启则返回为真
    

3.9 case 语句

case 语句和 if…elif…else 语句一样都是多分支条件语句,不过和多分支 if 条件语句不同的是,case 语句只能判断一种条件关系,而 if 语句可以判断多种条件关系。

case $变量名 in
"值 1")
;;
如果变量的值等于值1,则执行程序1,值
2")
如果变量的值等于值2,则执行程序2
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac

四.Shell中的getopt解析

1. Shell中的选项说明

Linux中绝大多数命令都提供了短选项长选项。一般来说,短选项是只使用一个"-“开头,选项部分只使用一个字符,长选项是使用两个短横线(即”–“)开头的。例如”-a"是短选项,"–append"是长选项。一般来说,选项的顺序是无所谓的,但并非绝对如此,有时候某些选项必须放在前面,必须放在某些选项的前面、后面。

一般来说,短选项:

  • 可以通过一个短横线"-“将多个短选项连接在一起,但如果连在一起的短选项有参数的话,则必须作为串联的最后一个字符。
    例如”-avz"其实会被解析为"-a -v -z",tar -zcf a.tar.gz串联了多个短选项,但"-f"选项有参数a.tar.gz,所以它必须作为串联选项的最后一个字符。

  • 短选项的参数可以和选项名称连在一起,也可以是用空白分隔。例如-n 3-n3是等价的,数值3都是"-n"选项的参数值。

  • 如果某个短选项的参数是可选的,那么它的参数必须紧跟在选项名后面,不能使用空格分开。至于为什么,见下面的第3项。

一般来说,长选项:

  • 可以使用等号或空白连接两种方式提供选项参数。例如--file=FILE--file FILE
  • 如果某个长选项的参数是可选的,那么它的参数必须使用"="连接。
  • 长选项一般可以缩写,只要不产生歧义即可。

2. getopt解析

下面这个是最常用的getopt解析方式(有这个命令就够了)。如果要了解getopt更完整的语法,见man getopt。

getopt -o SHORT_OPTIONS -l LONG_OPTIONS -n "$0" -- "$@"

其中:

  • -o SHORT_OPTIONS--options SHORT_OPTIONS
    getopt通过"-o"选项收集命令行传递的短选项和它们对应的参数。
  • -l LONG_OPTIONS--longoptions LONG_OPTIONS
    getopt通过"-l"选项收集命令行传递的长选项和它们对应的参数。可能从别人的脚本中经常看到"–long",是等价的,前文已经解释过,长选项只要不产生歧义,是可以进行缩写的。关于LONG_OPTIONS的格式见下一小节。
  • -n NAME
    getopt在解析命令行时,如果解析出错(例如要求给参数的选项没带参数,使用了无法解析的选项等)将会报告错误信息,getopt将使用该NAME作为报错的脚本名称。
  • -- "$@"
    其中–表示getopt命令自身的选项到此结束,后面的元素都是要被getopt解析的命令行参数。这里使用"$@",表示所有的命令行参数。注意,不能省略双引号。

getopt使用"-o"或"-l"解析短、长选项和参数时,将会对每个解析到的选项、参数进行输出,然后不断放进一个字符串中。这个字符串的内容就是完整的、规范化的选项和参数。

getopt使用"-o"选项解析短选项时:

  • 多个短选项可以连在一起
  • 如果某个要解析的选项需要一个参数,则在选项名后面跟一个冒号
  • 如果某个要解析的选项的参数可选,则在选项名后面跟两个冒号
  • 例如,getopt -o ab:c::中,将解析为-a -b arg_b -c [arg_c],arg_b是-b选项必须的,arg_c是-c选项可选的参数,"-a"选项无需参数

getopt使用"-l"选项解析长选项时:

  • 可以一次性指定多个选项名称,需要使用逗号分隔它们
  • 可以多次使用-l选项,多次解析长选项
  • 如果某个要解析的选项需要一个参数,则在选项名后面跟一个冒号
  • 如果某个要解析的选项的参数可选,则在选项名后面跟两个冒号
  • 例如,getopt -l add:,remove::,show中,将解析为–add arg_add --remove [arg_rem] --show,其中arg_add是–add选项必须的,–remove选项的参数arg_rem是可选的,–show无需参数

3. 示例分析getopt的解析方式

例如在脚本test.sh中,下面的getopt的结果保存到变量parameters中,然后输出getopt解析完成后得到的完整参数列表。

#!/usr/bin/env bash

parameters=`getopt -o ab:c:: --long add:,remove::,show -n "$0" -- "$@"`
echo "$parameters"

执行这个脚本,并给这个脚本传递一些选项和参数,这些脚本参数将被收集到$@,然后被getopt解析。

$ ./test.sh -a non-op_arg1 -b b_short_arg non-op_arg2 --rem --add /path --show -c non-op_arg3
 -a -b 'b_short_arg' --remove '' --add '/path' --show -c '' -- 'non-op_arg1' 'non-op_arg2' 'non-op_arg3'

首先可以看出,传递给脚本的参数都是无序的:

  • 长选项有:
    • --rem:是–remove的缩写形式,它的参数是可选的,但没有为它传递参数
    • --add:并设置了该选项的参数/path
    • --show:没有任何参数
  • 短选项有:
    • -a:它是无需参数的选项,所以它后面的non-op_arg1是一个非选项类型的参数
    • -b:它是必须带参数的选项,所以b_short_arg是它的参数
    • -c:它的参数是可选的,这里没有给它提供参数(前面解释过,要给参数可选的选项提供参数,短选项时,参数和选项名称必须连在一起)。
  • 非选项类型的参数有:
    • non-op_arg1
    • non-op_arg2
    • non-op_arg3

从getopt的输出结果中,可以看出:

  • 先解析选项和选项参数
  • 选项和选项参数是按照从左向右的方式进行解析的
  • 参数都使用引号包围
  • 那些参数可选的选项,当没有为它们提供参数时,将生成一个引号包围的空字符串参数
  • 解析完所有的选项和选项参数后,开始解析非选项类型的参数
  • 非选项类型的参数前面,会生成一个"–"字符串,它将选项(以及选项参数)与非选项类型的参数隔开了

4.处理getopt解析的结果

getopt解析得到了完整、规范化的结果,当然要拿来应用。例如直接传递个函数,或者根据while、case、shift将选项、参数进行分割单独保存。

如果要进行分割,由于getopt的解析结果通常保存在一个变量中,要解析这个结果字符串,需要使用eval函数将变量的内容进行还原,一般来说会将其设置为一个位置参数(因为shift只能操作位置变量)。

一般来说,整个处理流程是这样的:

parameters=$(getopt -o SHORT_OPTIONS -l LONG_OPTIONS -n "$0" -- "$@")
[ $? != 0 ] && exit 1
eval set -- "$parameters"   # 将$parameters设置为位置参数
while true ; do             # 循环解析位置参数
    case "$1" in
        -a|--longa) ...;shift ;;    # 不带参数的选项-a或--longa
        -b|--longb) ...;shift 2;;   # 带参数的选项-b或--longb
        -c|--longc)                 # 参数可选的选项-c或--longc
            case "$2" in 
                "")...;shift 2;;  # 没有给可选参数
                *) ...;shift 2;;  # 给了可选参数
            esac;;
        --) ...; break ;;       # 开始解析非选项类型的参数,break后,它们都保留在$@中
        *) echo "wrong";exit 1;;
    esac
done
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AoDeLuo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值