文章目录
一.Shell简介
Shell是Linux系统的用户界面,它提供用户与内核的一种交互方式。它接收用户输入的命令,并把它送入内核去执行,是一个命令解释器,,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统。Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。作为命令语言,它互动式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高阶语言中才具有的控制结构,包括循环和分支。它虽然不是 Linux系统内核的一部分,但它调用了系统内核的大部分功能来执行程序、创建文档并以并行的方式协调各个程序的运行。
二.Shell脚本执行方式
- ./xxx.sh
./ 的意思是说在当前的工作目录下执行xxx.sh。如果不加上./,bash可能会响应找到不到xxx.sh的错误信息。因为目前的工作目录(/root/bin)可能不在执行程序默认的搜索路径之列,也就是说,不在环境变量PASH的内容之中。查看PATH的内容可用echo $PASH
命令。chmod +x xxx.sh
为给文件添加可执行权限 - bash xxx.sh
切换到工作目录下(/root/bin),直接使用bash 或sh 来执行bash shell脚本,不用给shell脚本加执行权限即可 - . 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
- 打印环境变量PATH的值
echo $PATH
3.6 Shell预定义变量
$#:传给 shell 脚本参数的数量
$*:传给 shell 脚本参数的内容
$1、$2、$3、、$9:运行脚本时传递给其的参数,用空格隔开
$?: 命令执行后返回的状态
$?:用于检查上一个命令执行是否正确(在Linux中,命令退出状态为0表示该命令正确执行,任何非0 值表示命令出错)。
$0:当前执行的进程名
$$:当前进程的进程号
$$:变量最常见的用途是用作临时文件的名字以保证临时文件不会重复
3.7 字符串操作
-
计算字符串长度
string="abcd" echo ${#string} #输出 4
-
提取子串
string="alibaba is a great company" echo ${string:1:4} #输出liba
-
拼接字符串
your_name="qinjx" greeting="hello, "$your_name" !" greeting_1="hello, ${your_name} !" echo $greeting $greeting_1
-
查找字符串
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
-
替换字符
string="123123" echo ${string/3/0} #用0替换第一个遇见的3 echo ${string//3/0} #用0替换串中所有3
3.8 if语句
-
基本用法
if [ command ]; then 符合该条件执行的语句 fi
-
扩展用法
if [ command ];then 符合该条件执行的语句 elif [ command ];then 符合该条件执行的语句 else 符合该条件执行的语句 fi
-
文件/目录判断:
常用的: [ -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 指向相同的设备和节点号则返回为真。
-
字符串判断
[ -z STRING ] 如果STRING的长度为零则返回为真,即空是真 [ -n STRING ] 如果STRING的长度非零则返回为真,即非空是真 [ STRING1 ] 如果字符串不为空则返回为真,与-n类似 [ STRING1 == STRING2 ] 如果两个字符串相同则返回为真 [ STRING1 != STRING2 ] 如果字符串不相同则返回为真 [ STRING1 < STRING2 ] 如果 “STRING1”字典排序在“STRING2”前面则返回为真。 [ STRING1 > STRING2 ] 如果 “STRING1”字典排序在“STRING2”后面则返回为真。
-
数值判断
[ 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返回为真,<=
-
逻辑判断
[ ! EXPR ] 逻辑非,如果 EXPR 是false则返回为真。 [ EXPR1 -a EXPR2 ] 逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。 [ EXPR1 -o EXPR2 ] 逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。 [ ] || [ ] 用OR来合并两个条件 [ ] && [ ] 用AND来合并两个条件
-
其他判断
[ -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