shell脚本的应用

TOC

一、初入Shell世界

Shell是一个用C语言编写的程序,它是用户给Linux发送指令的桥梁。Shell既是一种命令语言,又是一种程序设计语言。

什么是Shell脚本?

一种用shell命令语言编写的脚本程序,它的本质是一个文本文件,里面写了一系列 Linux/Unix 命令以及控制语句(如循环、条件判断、函数等)。通俗易懂就是把多个命令放到一个文本文件中,运行这个文件则会依次执行里面的所有命令。

Shell脚本开始

Shell环境

shell编程跟其他类似Java、Python一样需要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器。而shell环境就是脚本解释器

常见的shell环境有

  • Bourne Shell:/bin/sh,简单、稳定,但功能比较有限,现在一般用作其他Shell的兼容模式。
  • Bourne Again Shell:/bin/bash,最主流的Shell,Linux默认的基本都是它,是sh的增强版。
  • Z Shell:/bin/zsh,功能更强大,交互体验比bash好。
  • C Shell:/usr/bin/csh,类似C语言语法的Shell。
  • Korn Shell:/usr/bin/ksh,由David Korn开发,结合了sh和csh的优点。

查看当前的shell环境只需要查看SHELL变量即可,如下所示:

ehco $SHELL

查看系统支持的shell环境,查看文件/etc/shells,如下所示:

$ cat /etc/shells
/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

切换当前的shell环境,临时切换退出后(exit),会回到原来的shell。永久切换退出并重新登录,或者重启终端,就会进入新的默认shell。

# 临时切换
bash
zsh
sh
# 永久切换
chsh -s /bin/zsh

第一个Shell脚本

编写一个shell脚本:test.sh,扩展名随便,因为Linux中并没有文件后缀区分文件类型的说法。

#!/bin/bash
ls /home
echo "The test.sh script has completed running."

第一行的#!就叫Shebang,也叫hashbang。是为了告诉系统后面的路径告诉系统,用哪个程序来解释执行这个脚本。

脚本运行方式

运行脚本的方式有两种:
1.作为可执行文件运行
添加可执行权限,可以是相对路径或者绝对路径运行脚本。该种运行方式会默认使用脚本里面第一行定义的解释器来运行脚本,无论系统使用的是什么shell环境

chmod +x test.sh
# 运行脚本
./test.sh

特别注意:
1.当脚本没有第一行的Shebang指定运行解释器的内容时候,系统会使用 当前用户的默认登录shell来运行脚本。
2.一定要使用相对路径或者绝对路径运行脚本,比如:./test.sh,如果直接命令test.sh,系统会去PATH(/bin, /sbin, /usr/bin,/usr/sbin)里寻找有没有叫test.sh的,你的当前目录通常不在PATH里,会报错找不到该命令。

2.作为解释器参数运行
直接运行解释器,将文件作为参数运行脚本。该种运行方式会直接使用所运行的解释器运行脚本,无论系统使用的什么shell环境以及脚本第一行定义的解释器环境

sh test.sh
zsh test.sh

二、Shell基本语法

shell中的变量

变量的定义和使用

定义变量

a="shudg2ch"
b=1

这个变量的定义也可以是命令的执行结果,需要使用``或者$()来定义

a=`ls /data`
b="$(cat b.txt)"

变量的使用

name='jack'
echo $name
echo ${name}

只读变量

myStr="helloworld"  
readonly myStr

删除变量

unset myStr

使用${}引用变量

${}的优势

$和${}都是用来引用变量的,但是${}有着一些特殊的优势。
在使用变量和字符串拼接的时候,$后面跟字符串会无法找到该变量,如下所示:

a='my name is '
echo "$aBob"  #会出现找不到$aBob变量
echo "${a}Bob"  #会正常输出内容:my name is Bob 

注意:如果想要正常使用变量则$变量后面必须跟上'-'、','、'_'等特殊符号才能和后面的字符串区分开来

${}设置、赋值、备用默认值

设置默认值(:-)
如果变量未定义或为空,则使用默认值,但不改变变量本身的值。

VAR=""
echo ${VAR:-default}  # 输出 default
echo $VAR  # 输出为空,因为VAR变量本身没有被改变

赋值默认值(:=)
如果变量未定义或为空,则将其设置为默认值,并使用该值。

VAR=""
echo ${VAR:=default}  # 输出 default
echo $VAR  # 输出 default,因为VAR变量被赋值为"default"

备用默认值(:+)
如果变量已定义且不为空,则使用备用值;否则,使用空值。

VAR="value"
echo ${VAR:+default}  # 输出 default,因为VAR变量已定义且不为空
VAR=""
echo ${VAR:+default}  # 输出为空,因为VAR变量为空

${}字符串操作

a='sdanudhsaudsabu'
# 统计字符串长度
echo ${#a}
# 某个位置提取一定长度的子字符串(字符串索引从0开始)
echo ${a:3:5}  # 从第三位开始提取长度为5的字符串
# 字符串替换
echo ${a/sda/abc}  # 会替换所有匹配的内容

${}参数扩展

file="file.txt"
echo ${file#*.}  # 输出 txt(删除最短匹配的前缀)
echo ${file##*.}  # 输出 txt(删除最长匹配的前缀)
echo ${file%.*}  # 输出 file(删除最短匹配的后缀)
echo ${file%%.*}  # 输出 file(删除最长匹配的后缀)

${}条件检查

检查变量是否被设置。

VAR=""
# 如果VAR变量没有被设置输出"variable not be set!!!"并退出
echo ${VAR:?variable not be set!!!}

shell中$变量的含义

  • $#:表示执行脚本传入参数的个数
  • $*:表示执行脚本传入参数的列表(不包括$0)
  • $$:表示进程的id;Shell本身的PID(ProcessID,即脚本运行的当前 进程ID号)
  • $!:Shell最后运行的后台Process的PID(后台运行的最后一个进程的 进程ID号)
  • $@:表示获取执行脚本传入的所有参数
  • $0:表示执行的脚本名称
  • $1:表示第一个参数
  • $2:表示第二个参数
  • $?:表示脚本执行的状态,0表示正常,其他表示错误

Shell传递参数

参数的基本传递

在运行脚本的时候后面跟上参数就是向脚本传递参数,如下所示:

# 运行脚本传递参数
chmod +x test.sh
./test.sh 1 2

上面的示例就是我传递了两个参数。
【示例】获取运行脚本get.sh传递的三个参数

#!/bin/bash
echo "---传递的参数列表---"
echo "执行文件名: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"

执行脚本查看结果

$ ./get.sh 1 2 3
---传递的参数列表---
执行文件名: ./get.sh
第一个参数: 1
第二个参数: 2
第三个参数: 3

$*$@的区别:
相同点都是引用所有的参数。不同点是假如脚本有三个参数1、2、3,*等同于"1 2 3"传递了一个参数,而@等同于"1" “2” "3"传递了三个参数。

脚本常用read的用法

read:读取键盘输入的字符
语法:read -p “请输入密码:” 【定义值的名字】 -n 5(定义输入长度)
-a:后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
-d:后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。
-p:后面跟提示信息,即在输入前打印提示信息。
-e:在输入的时候可以使用命令补全功能。
-n:后跟一个数字,定义输入文本的长度,很实用。
-r:屏蔽\,如果没有该选项,则\作为一个转义字符,有的话 \就是个正常的字符了。
-s:安静模式,在输入字符时不再屏幕上显示,例如login时输入密码。
-t:后面跟秒数,定义输入字符的等待时间。
-u:后面跟fd,从文件描述符中读入,该文件描述符可以是exec新开启的。

【示例】编写一个脚本read.sh来获取命令输入的内容

#!/bin/bash
read -p "请输入内容:" input_str
echo "输入内容为: $inpu_str"

Shell中的数组

shell中也有数组,shell中定义数组如下:

array=(apple banana orange)
# 也可以这样定义
array[0]='apple'
array[1]='banana'
array[2]='orange'

读取数组的方式:${数组名[索引名]},如下所示:

echo ${array[0]}

获取数组中的所有元素
使用@*可以获取数组中的所有元素
【示例】定义一个数组,使用@*循环来获取里面所有的元素

array=(a b c d)
echo "数组的元素: ${array[@]}"
echo "数组的元素: ${array[*]}"

关联数组
bash环境支持关联数组,类似Python中的字典,sh环境就不支持,所以需要注意运行环境。
【示例】定义一个关联数组并使用

declare -A array=(
    ["google"]="www.google.com"
    ["baidu"]="www.baidu.com"
    ["taobao"]="www.taobao.com"
)

访问使用关联数组方法:${关联数组名["数组的键值"]},如下所示:

echo ${array["baidu"]}

Shell运算符

算数运算符

原生bash可以通过其他命令来实现,例如awk和 expr,expr最常用
使用其他命令时,表达式要用反引号 ` 而不是单引号 '修饰
算术运算符包含:加:(+) 减:(-) 乘:(*) 除:(/) 取余:(%)

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字
-eq:判断是否相等:左边等于右边
-ne:判断是否不相等:左边不等于右边
-gt:左边大于右边
-lt:左边小于右边
-ge:左边大于等于右边
-le:左边小于等于右边

布尔运算符

!:非运算,表达式为 true 则返回 false,否则返回 true
-o:或运算,有一个表达式为 true 则返回 true
-a:与运算,两个表达式都为 true 才返回 true

字符串运算符

=:检测两个字符串是否相等,相等返回 true
!=:检测两个字符串是否相等,不相等返回 true
-z:检测字符串长度是否为 0,为 0 返回 true
-n:检测字符串长度是否为 0,不为 0 返回 true
$:检测字符串是否为空,不为空返回 true

逻辑运算符

&&:逻辑的 AND
||:逻辑的 OR

文件判断

-e:测试目录或文件是否存在
-f:测试是否为文件
-d:测试文件夹是否存在
-r:测试是否为目录
-w:测试当前用户是否有写入权限
-x:测试是否设置有可执行权限
-nt:判断文件A是否比文件B新
-ot:判断文件A是否比文件B旧
-ef:判断两个文件是否为同一文件,判断两文件是否同一个inode(软硬连接)

三、shell脚本的流程控制

if语句

语法格式:
1.单分支if语句:

if 判断条件
then
执行的操作
fi

2.多分支if语句:

if 判断条件
then
执行命令
elif 判断条件
then
执行命令
else
执行命令
fi

case语句

语法格式:

case 变量值 in
条件1)
    执行命令
;;
条件2)
执行命令
;;
*) 相当于if的else
执行命令
esac

循环语句

for循环(取值型循环)
可根据已知的列表对象重复执行命令序列更适合无规律的循环操作读取不同的变量值,逐个执行同一组命令
语法格式:

for 变量名 in 取值列表
do 
   循环命令
done

while循环(判断型循环)
可根据特定的条件成立重复执行命令序列,更适合有规律的循环操作
语法格式:

while 条件测试操作
do
   执行命令
done

until循环
重复测试某个条件,只要条件不成立则反复执行
语法格式:

until
do
  执行命令
done

设置循环体的退出语句—break:退出整个循环体,continue:退出当次循环
注意:一般用于配合条件为ture时的死循环

四、进阶知识

[]和()的区别

[]符号常用于if语句的条件判断。
【示例1】判断变量大小

if [ $a -lt 100 ];then
    echo "a < 100"
elif [ $a -eq 100 ];then
    echo "a = 100"
elif [ $a -gt 100 ];then
    echo "a > 100"
fi

【示例2】判断文件或者目录是否存在。

if [ -f "/etc/passwd" ];then
    echo "文件存在"
fi
if [ -d "/data" ];then
    echo "目录存在"
fi

()符号在子Shell中执行命令,执行完成后环境变量不会影响父Shell。
【示例1】在子shell里切换目录并执行ls,执行完后当前目录不变。

(cd /tmp && ls)

【示例2】输出shell里的a=100。

(a=100; echo "子shell里的a=$a")

[]和()的区别:

  • []必须有空格,例如:[ $a -lt $b ],否则会报错。
  • 用于比较字符串、整数或检查文件属性。

[[]]表达式

[[]][]的升级版,支持更多操作符和更安全的语法,还可以直接使用其他运算符。
【示例】用新的关系运算符判断变量的值。

if [[ $str = 'centos' ]];then
    echo "centos系统"
elif [[ $str = 'ubuntu' ]];then
    echo "ubuntu系统"
fi

对于逻辑运算符的运用
原来[]符号需要使用多个[]符号来衔接逻辑运算符,如下所示:

if [ $a == 'test' ] && [$b == 'debug' ];then
    echo 'success'
fi

使用[[]]结合逻辑运算符,如下所示:

if [[ $a = 'test' && $b = 'debug' ]];then
    echo 'success'
fi

温馨提示:更加推荐使用[[]],使用语法更多,非常方便,而且更安全。

(())表达式

(())()的升级版,在原来的基础上做了算术扩展,也可以用于其他符号的关系运算符判断。
【示例1】实现变量自加操作。

i=0
for str in (a b c)
do
    ((i++))
done

【示例2】判断变量是否大于100。

if (($a > 100));then
    echo "a > 100"
fi

五、函数应用

基本用法

语法格式:

[function] 函数名(){
  命令语句
   [return x]
}

【示例】定义一个函数并调用

test_func(){
    echo "function is running"
}
# 调用
test_func

向函数传递参数

函数也能像运行脚本传递参数那样传递参数,获取参数的方式也是使用$1$2等变量
【示例】向函数传递参数

get_parameter(){
    echo "第一个参数: $1"
    echo "第二个参数: $2"
}
# 调用函数传递参数
get_parameter 'shdug' 23

六、其他

显示行号方法总结:
包括空行: cat -n、nl -ba、less -N
不包括空行: cat -b、nl -bt
awk ‘{print NR,$0}’
执行shell脚本的方法:
1.路径执行(相对路径或绝对路径)需要x权限
2.sh脚本路径,不需要x权限
3.source 脚本路径 不需要x权限

shell脚本调试
echo命令 bash命令参—sh -nvx【脚本名】
-n:不会执行该脚本,仅查询脚本语法是否有问题,有问题会提示报错
-v:先将脚本内容输出到屏幕上然后执行脚本,有错误会给出错误提示
-x:将执行的脚本具体内容输出到屏幕上
set:命令
set -x:开启调节模式 set +x:关闭调解模式