目录
shell脚本编程注意事项
前言
shell:我们知道linux并不是一个可视化的操作系统,如果直接使用起来,成本比较高。而shell是一个命令行解释器,是用户与linux之间的桥梁,用户只需要输入命令,通过shell,解释成对应linux可以识别的命令。

Linux 中的 shell 有很多种类,常用的几种:
1. Bourne Shell(/usr/bin/sh 或/bin/sh)
2. Bourne Again Shell(/bin/bash)
3. C Shell(/usr/bin/csh)
4. K Shell(/usr/bin/ksh)
5. Shell for Root(/sbin/sh)
使用最多的是bash,也是shell默认的命令行解释器。
shell脚本:shell是用C语言编写的,shell脚本则是为shell编写的脚本程序。实际上,shell脚本就是一条一条linux命令组合起来,加上一些语法,为了实现一个目的。
在linux命令行编写命令,可以说也是再编写shell脚本

shell脚本编程注意事项
- shell脚本文件的命名一般以英文小写,大写,后缀以.sh结尾
- shell编程的用'#'表示注释
- shell编程必须以 #!/bin/bash 开头
- shell脚本的变量不能以数字,特殊符号开头,可以使用下划线,但是不能是用破折号
第一个shell脚本——Hello world
建立一个.sh文件,在里面编写如下内容:

执行shell脚本有两种方式:
- 给.sh文件加上可执行的权限
chmod +x first.sh ./first.sh #执行shell脚本 - 利用bash解释,将.sh文件作为bash的参数
/bin/bash first.sh 
shell脚本的变量
定义变量
定义变量,变量名不需要叫'$'符号。定义变量有两种方式。
- 直接定义
a=1 注意:以这种方式命名的变量类型默认是字符串类型。
- 使用declare命令,格式为 declare [+/-] [选项] 变量名
其中 '-' 表示给变量加上特定属性,'+' 表示取消变量的特定属性。选项有一下几种:

#!/bin/bash a=1 #字符串类型 b=2 sum1=$a+$b echo $sum1 declare -i x=2 #整数类型 declare -i y=3 declare -i sum2 sum2=$x+$y echo $sum2 
注意点:
- 变量名和等号之间不能有空格
- 变量命名只能使用英文字母(大写或者小写),数字和下划线,首个字母不能以数字和特殊符号开头。
- 变量名中间不能有空格
- 不能使用标点符号
- 不能使用shell关键字
使用变量
使用一个变量,需要在变量前加一个'$'符号。
#!/bin/bash #echo "hello world!" hello="hello world!" echo $hello echo ${hello} 如下使用'{}'大括号是为了帮助解释器识别边界范围。如下,如果不加'{}',解释器会将helloworld当作一个变量。
#!/bin/bash hello="hello " echo "${hello}world" 只读变量
使用readonly命令可以将变量定义为只读变量,只读变量不能被修改。相当于其他编程语言的常量。

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

变量类型
shell存在三种变量,系统变量,环境变量,局部(用户)变量。
- 系统变量:主要是用于对参数判断和命令返回值判断时使用,系统变量详解如下:
$0 shell本身文件名; $n 当前脚本的第n个参数,n=1,2,…9; $* 当前脚本的所有参数(不包括程序本身); $# 当前脚本的参数个数(不包括程序本身); $? 令或程序执行完后的状态,返回0表示执行成功; $$ 程序本身的PID号。 
- 环境变量:所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环 境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量。
PATH 命令所示路径,以冒号为分割; HOME 打印用户家目录; SHELL 显示当前Shell类型; USER 打印当前用户名; ID 打印当前用户id信息; PWD 显示当前所在路径; TERM 打印当前终端类型; HOSTNAME 显示当前主机名; PS1 定义主机命令提示符的; HISTSIZE 历史命令大小,可通过 HISTTIMEFORMAT 变量设置命令执行时间; RANDOM 随机生成一个 0 至 32767 的整数; HOSTNAME 主机名 - 局部变量:用户在脚本中定义的变量
shell的字符串
- 介绍
- shell脚本是用C语言写的,不难得到,字符串底层是用字符数组保存的。但是shell脚本的字符串并不是以'\0'结尾。
- 在shell脚本中'*'和'@'是通配符。

字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好
单引号:任何字符都会原样输出。没有转义,也不能使用变量。
双引号:可以使用转义和变量。

-
拼接字符串

- 获取字符串长度:使用'#'

- 提取子字符串
注意:shell脚本的字符串是从下标0开始的。
${string:start} # 从左边start位置开始,从左到右截取剩余所有的字符串 ${string:start:length} # 从左边start位置开始,从左到右截取length长度的字符串 ${string:0-start} # 从右边start位置开始,从左到右截取剩余所有的字符串 ${string:0-start:length} # 从右边start位置开始,从左到右截取length长度的字符串 ${string#chars} # 从左开始匹配第一个chars字符串,截取其右边的字符串 ${string##chars} # 从左开始匹配最后一个chars字符串,截取其右边的字符串 ${string%chars} # 从右开始匹配第一个chars字符串,截取其左边的字符串 ${string%%chars} # 从右开始匹配最后一个chars字符串,截取其左边的字符串 
- 查找子字符串:使用 expr命令,可以进行四则运算和字符串操作,expr index是在字符串中查找字符
shell脚本反引号(``)作用:反引号包含内的字符会被作为shell命令来进行输出,并返回结果,主要是为了获得返回结果。如果想直接执行,写到脚本中就可以了。

- 替换字符串
${string/substring/replacement} # 使用$replacement, 来代替第一个匹配的$substring ${string//substring/replacement} # 使用$replacement, 代替所有匹配的$substring ${string/#substring/replacement} # 如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring ${string/%substring/replacement} # 如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring 
- 判断与默认值
${str:-DEFAULT} # 如果str为空, 那么就以DEFAULT作为返回值 ${str:=DEFAULT} # 如果str为空, 那么就以DEFAULT作为返回值, 并将str赋值为DEFAULT ${str:+OTHER} # 如果str不为空, 那么就以OTHER作为返回值 ${str:?ERR_MSG} # 如果str为空, 那么输出报错信息ERR_MSG并推出程序 
Shell数组
bash支持一维数组,不支持多维数组,并且没有限定数组的大小。
类似于C语言,数组下标有0开始编号。获取数组元素需要通过下标。下标可以是整数或者算术表达式,其值应当大于等于0。
- 定义数组
在shell中用括号来表示数组,数组元素用空格符号分割。如下:
#!/bin/bash arr1=(1 2 3 4 5) #单独定义数组分量 declare -a arr2 arr2[0]=a arr2[1]=b arr3[5]=e - 获取数组元素
需要使用下标的方式来访问。并且必须加上花括号。

使用"@"或者"*"可以访问数组的所有元素。

- 获取数组长度
获取数组长度的方式和获取字符串长度的方式相同。

- 数组遍历
数组遍历有三种方式:
1. 标准for循环。在bash中适用,在sh中不适用


2. for in形式

3. while循环

流程控制语句
if分支语句
- 格式
//单分子 if [ 条件表达式 ];then 语句 fi //双分支 if [ 条件表达式 ];then 语句1 else 语句2 fi //多分支 if [ 条件表达式 ];then 语句1 elif [ 条件表达式 ];then 语句2 elif [ 条件表达式 ];then 语句3 else 语句4 fi - 常用逻辑判断运算符
-f 判断文件是否存在 eg: if [ -f filename ]; -d 判断目录是否存在 eg: if [ -d dir ]; -eq 等于,应用于整型比较 equal; -ne 不等于,应用于整型比较 not equal; -lt 小于,应用于整型比较 letter; -gt 大于,应用于整型比较 greater; -le 小于或等于,应用于整型比较; -ge 大于或等于,应用于整型比较; -a 双方都成立(and) 逻辑表达式 –a 逻辑表达式; -o 单方成立(or) 逻辑表达式 –o 逻辑表达式; -z 空字符串; -x 是否具有可执行权限 || 单方成立; && 双方都成立表达式。 - 例子
1. 使用单分支语句判断进程是否运行
$1: 获得传入脚本第一个参数。
$():脚本里使用,为执行括号里的命令。作用相当于反引号。
grep -v 不显示含有后面的字符串
grep -c 返回显示函数

2. 判断学生成绩等级

case选择语句
case语句主要用于对多个选择条件进行匹配输出,与if elif结构相似,通常用于脚本传递输入参数,打印输出结果和内容。
- 结构:以case ... in 开头,以esac结束。
注意:
1. 每一个模式后需要以')'反括号结尾,执行的命令后,需要以双引号结尾';;'
2. esac,每一次都会执行。
#找到对应模式,执行命令 case 模式名 in 模式1) 命令 ;; 模式2) 命令 ;; #以上模式执行完后,都会执行这里的命令 esac - 例子:使用shell脚本来启动和杀死一个进程。
首先:编写一个程序,让其进入死循环。

接下来编写shell 脚本
read 命令作用:将硬件键盘输入到对应变量中。
read -p:打印后面字符串
#!/bin/bash read -p "please select start|stop test.c# " way case $way in start) gcc test.c -o test ./test & ;; stop) num=`ps -axj | grep test | grep -vc grep` echo $num if [ $num -eq 1 ];then testpid=`pidof test` echo $testpid kill -9 $testpid rm -f test else echo "test proc is not run" fi ;; #利用通配符来做其他选择判断 *) echo "select way error" ;; esac echo "shell script run success" 
循环语句
标准for语句
在shell脚本中支持一种和C语言for循环写法差不多的形式。但是在bash中适用,在sh解释器中不适用。
格式:
for (( ...里面写法和C语言类似... )) do 命令 done 
for...in循环语句
在bash和sh解释器中都适用
格式:variable 表示变量,value_list 表示取值列表,in 是 Shell 中的关键字。意思就是,顺序获取到在in右边元素列表的元素,直到取完。
for variable in value_list do statements done 获取元素列表的方式有下面几种方式:
- 在in右边直接给出各各值,每个值之间用空格隔开。

遍历数组,实际就是将所有数组元素中间空格隔开,放到in右边。

- 给出一个取值范围。
{start..end} 
- 使用命令的执行结果
用到$()或者反引号``来执行命令获得结果。
seq命令,获得某个范围内的整数,并且可以设置步长。
seq 2 2 100,是从2开始,每次加2,到100结束。


while循环语句
格式:
while 判断语句 do 命令 done 例子:
- 死循环

- 打印数字

注意:shell脚本同样支持break和continue关键字,作用同C语言。

select语句
格式:
select param in list do 语句 done select命令是一个无限循环,所以注意需要设置好退出循环条件。 select命令中,一般需要配合case命令。
注意输入是select选项的序号。

select一般结合PS3使用, PS3会在select选项后打印PS3的值。

函数
介绍
shell脚本中支持将一组命令集或语句形成一个可用块,成为shell函数。shell函数只需要定义一次,可以重复使用,提高代码的利用率。
结构:
#语法 funcname(){ 命令1 命令2 ... } #调用 funname 传参和使用:
传参直接在掉哦那个函数后面加上需要的参数,参数之间用空格分隔
在函数里面使用参数可以使用
- $n:获得第n个参数
- $#:获得传递参数的个数
- $@或者$*:可以一次性获得全部参数

返回值:
函数可以看作是一条命令,函数里如果使用了return关键字,可以使用$?,获取到函数执行的状态码,即return的值。
但是,状态码只有8位,最大值为255。如果超过了255,会被截断。

如果想获取到的值不截断
- shell脚本的变量(不加local的)默认是全局有效,可以在里面进行操作,外面也能得到。

- 在函数体内使用echo输出,在函数体外赋值

消除一个顾虑:
在函数体内如果使用echo,在函数体外,没有接收,会被打印。
如果在函数体外接收,不会打印。

注意点:
- shell脚本中的变量默认是全局有效,如果想定义局部变量,需要加上关键字local。
函数库

