Shell脚本编程的实用技巧和最佳实践

1. Bash的变量

shell中变量的设置规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头

  • 在Bash中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型

  • 变量使用等号连接,等号左右两侧不能有空格

  • 如果变量的值有空格(Linux中空格代表分割),需要使用单引号或双引号包括

  • 在变量的值中,可以使用\转义符

  • 如果需要增加变量的值,那么可以进行变量值的叠加。不过变量需要用双引号包含$变量名或者使用${变量名}

  • 如果是把命令的结果作为变量值赋予变量,则需要使用反引号或者$()包含变量

  • 建议环境变量使用大写

变量分类

  • 用户自定义变量
  • 环境变量:这种变量中主要保存的是和系统操作环境相关的数据
  • 位置参数变量:这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的
  • 预定义变量:是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的

1.1 用户自定义变量(本地变量)

  • 变量定义:aa=123 (注意等号左右不能有空格)

  • 变量叠加:两种方式aa="$aa"456或者aa=${aa}789

  • 变量查看:set

  • 变量删除:unset name

可以看到这四种变量从上到下变量是越来越严格的。

1.2 环境变量

1.2.1 环境变量和用户自定义变量的区别

用户自定义变量只在当前的Shell中生效,

而环境变量会在当前Shell和这个Shell的所有子Shell当中生效,如果把环境变量写入相应的配置文件,那么这个环境变量就会在所有的Shell中生效

环境变量和用户自定义变量的区别在于 作用的范围不同

1.2.2 如何设置环境变量

export 变量名=变量值:声明环境变量

env:查询变量

unset 变量名:删除变量

$变量名:调用变量

什么是父shell和子shell?

直接进入Linux是bash环境,如果再次输入bash即进入shell的子shell中,可以使用pstree命令来查看shell的结构,最后使用exit命令即可退出子shell

可以使用set或者env命令来查看系统中的变量

1.2.3 系统常见的环境变量
PATH 是系统查找命令的路径

在ubuntu中输入$PATH命令,输出为

-bash: /home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games: No such file or directory

从这里也可以看出来上次得到的 在bin目录中存放的是常用的可执行文件

在Linux系统中一切都是文件,我们输入的命令都是文件,那么为什么执行shell脚本的时候需要添加路径,而执行正常命令的时候却不要呢?

因为正常命令的路径已经存放在PATH当中去了,而Linux在执行命令时会首先在PATH中搜索,所以执行正常命令时不用添加路径

当然也可以脚本存放在PATH中,这样就可以不用输入路径就可以执行,不过通常不会直接把这样的脚本直接添加到PATH中,因为这样可能会对原系统改动

如果想要直接运行脚本,而不用每次添加路径,更为保险的做法是利用变量叠加添加脚本所在路径,

PATH="$PATH":/root 通过这条命令,就把root目录存放在PATH中了,可以再次使用$PATH命令查看是否已经添加了/root目录,不过这只是临时生效,想要永远生效需要写入配置文件

PS1 定义系统提示符的命令

1.2.4. 环境变量配置文件

见环境变量配置文件

1.3 位置参数变量

其实就相当于编程中的函数传递参数

位置参数变量 作用
$n | n为数字,$0代表命令本身,$1-$9表示接受第一个到第九个的参数,十以上的参数需要用大括号包含,如${10}
$* | 这个变量代表命令行所有的参数,$*把所有的参数看成一个整体
$@ | 这个变量也代表命令行中的所有参数,不过$@把每个参数区分对待
$# 这个变量代表命令行中所有参数的个数

下面是一个例子

#!/bin/bash
# 位置参数的功能

echo "A total of $# parameters"
#使用$#代表所有参数的个数
echo "The parameters is : $*"
#使用$*代表所有的参数
echo "The parameters is : $@"
#使用$@也代表所有的参数

./Loca.sh 12 34 56

输出为

A total of 3 parameters
The parameters is : 12 34 56
The parameters is : 12 34 56

1.4 预定义变量

预定义变量 作用
$? 最后一次执行的命令(上一条命令)的返回状态。如果这个命令的值为0,证明上一个命令正确执行,如果这个变量的值为非0(具体是哪个数,由命令自己决定),则证明上一个命令执行不正确
$$ 当前进程的进程号(PID)
$! 后台运行的最后一个进程的进程号(PID)

注: $?可以判断上一条命令是否正确

1.5 使用read接受键盘输入

read [选项] [变量名]

选项:

-p 提示信息 在等待read输入时,输出提示信息

-t 秒数 read可以一直等待用户输入,使用此选项可以指定等待时间

-n 字符数 read命令只接受指定的字符数,就会执行

-s: 隐藏输入的数据,

通过下面这个例子来体会

#!/bin/bash

read -t 30 -p "Please input your name: " name  #在shell脚本中,一定注意空格的使用,此处要添加空格
# -p  提示"请输入姓名"并等待30秒,把用户的输入保存入变量name中
echo "Name is $name"
echo -e "\n"

read -s -t 30 -p "Please input your age(using hidden mode): " age
# 年龄是隐私,所以我们用-s选项隐藏输入
echo "\t"
echo "Age is $age"
echo -e "\n"

read -n 1 -t 30 -p "Please select your gender[M/F]: " gender
#使用"-n 1"选项只接收一个输入字符就会执行(不用输入回车会直接执行)
echo -e "\n"
echo "Sex is $gender"

2. Bash运算符

2.1 数值运算与运算符

方法一 declare声明变量类型

语法:declare [+-][选项] 变量名

选项:

- 给变量设定类型属性

+ 取消变量的类型属性

-i 将变量声明为整数型(integer)

-x 将变量声明为环境变量

-p 显示指定变量的被声明类型

#!/bin/bash
# 数值运算

aa=11
bb=22
declare -i cc=$aa+$bb
echo $cc
方法二 expr或let数值运算工具
#!/bin/bash
# 使用expr工具进行数值运算

aa=11
bb=22
dd=$(expr $aa + $bb)  #注意"+"左右两边有空格
echo $dd
方法三 $((运算式))或者$[运算式]

ff=$(($aa + $bb)) 注意在$aa$bb两边加上空格

2.2 变量测试和内容替换

对于两个变量x和y,测试y的值然后对x赋值(其实可以通过if实现同样的功能)

由于那个表格过于复杂,所以就不详细记录了,形如下面两个

x=${y-新值}

x=${y:-新值} 之类的语法,就是实现变量测试的功能

3. 条件判断(Conditionals)

3.1 按照文件类型进行判断

测试选项 作用
-d 文件 判断该文件是否存在,并且是否为目录文件(是目录为真)
-e 文件 判断该文件是否存在(存在为真)
-f 文件 判断该文件是否存在,并且是否为普通文件(是普通文件为真)

两种判断格式

使用test命令

一个例子 test -e /root/install.log 判断/root下是否有install.log

使用中括号 []

[ -e /root/install.log ] 请注意命令两边距离中括号有空格

如何查看判断结果? ->使用echo $?命令

但是这样是不是稍微显得有一些麻烦,如何解决这个问题?

[ -d /root ] && echo "yes" || echo "no" 第一条命令如果正确执行,则打印 yes,否则打印 no

3.2 按照文件权限进行判断

测试选项 作用
-r 判断文件是否存在,并且是否该文件拥有读权限
-w 判断该文件是否存在,并且该文件是否拥有写权限
-x 判断该文件是否存在,并且是否该文件拥有执行权限

3.3 两个文件之间进行比较

测试选项 作用
文件1 -nt 文件2 判断文件1的修改时间是否比文件2的新
文件1 -ot 文件2 判断文件1的修改时间是否比文件2的旧
文件1 -ef 文件2 判断文件1和文件2的Inode号是否一致,可以理解为两个文件是否为同一个文件

3.4 两个整数之间的比较

测试选项 作用
整数1 -eq 整数2 判断整数1与整数2是否相等
整数1 -ne 整数2 判断两个整数是否不相等
整数1 -gt 整数2 整数1是否大于整数2
整数1 -lt 整数2 小于
整数1 -ge 整数2 大于等于
整数1 -le 整数2 小于等于

一个例子 [ 22 -gt 23 ] && echo yes || echo no

表示如果22>23则返回yes否则返回no

结果是no

3.5 字符串的判断

测试选项 作用
-z 字符串 判断字符串是否为空 (为空返回为真)
-n 字符串 判断字符串是否为非空(非空返回真)
字符串1==字符串2 判断字符串1是否和字符串2相等(相等返回为真)
字符串1!=字符串2 判断字符串2是否和字符串2不相等(不相等返回为真)

一个例子[ -z "$name" ] && echo yes || echo no

如果name没有赋值,返回为yes,如果为name赋值,则返回no

3.6 多重条件判断

测试选项 作用
判断1 -a 判断2 逻辑与,判断1与判断2都成立,最终结果为真
判断1 -o 判断2 逻辑或,判断1与判断2有一个成立,返回结果为真
! 判断 逻辑非

输入命令的时候 注意空格

aa=11
[ -n "$aa" -a "$aa" -gt 23 ] && echo yes || echo no
# -n "$aa" 判断变量aa的是否有值,同时判断变量aa是否大于23
# 因为变量aa的值不大于23,所以第一个判断为真,但是逻辑与要求均为真,所以最后返回结果为假

4. 流程控制(Loops)

4.1 if语句

1. 单分支if条件语句
if [ 条件判断式 ] ; then

	程序

fi

或者

if [ 条件判断 ]
	then
		程序
fi

注意:

  • 以if开头,以fi结尾,和其他语言不同

  • [ 条件判断式 ] 就是使用test命令判断,所以中括号和条件判断式之间必须有空格

  • then后面跟符合条件之后执行的程序,可以放在[]之后,用;分割,也可以换行写入,就不需要;

例子:判断分区使用率

#!/bin/bash

rate=$(df -h | grep "/dev/vda2" | awk '{print $5}' | cut -d "%" -f1)
# 把根分区使用率作为变量值赋予变量rate

if [ $rate -ge 80 ]
	then 
		echo "Warning! /dev/vda2 is full!!! "
fi
2. 双分支if条件语句
if [ 条件判断式 ]
	then
		条件成立时,执行的程序
	else
		条件不成立时,执行的另一个程序
fi
3. 多分支if 语句
if [ 条件判断式1 ]
	then
		当判断1成立时,执行程序1
	elif [ 条件判断式2 ]
		then
			当条件判断式2成立时,执行程序2
	elif ...
	else
		当所有条件都不成立,最后执行此程序
fi

一个例子,体会多分支语句

#!/bin/bash
#判断用户输入的是什么文件
# 通过这个脚本体会多分支语句

read -p "Please input a filename: " file
#接受键盘的输入,并赋予变量file

if [ -z "$file" ]
#判断file是否为空
	then 
		echo "Error, please input a filename"
		exit 1
elif [ ! -e "$file" ]
#判断file的值是否存在
	then 
		echo "Your input is not a file!"
		exit 2
elif [ -f "$file" ]
#判断file的值是否为普通文件
	then 
		echo "$file is a regulare file"
elif [ -d "$file" ]
#判断file的值是否为目录文件
	then 
		echo "$file is a directory"
	else
		echo "$file is an other file"
fi

4.2 case语句 多分支判断语句

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

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

一个例子

#!/bin/bash
#判断用户的输入

read -p "Please choose yes/no: " -t 30 cho
case $cho in 
	"yes")
		echo "Your choose is yes!"
		;;
	"no")
		echo "Your choose is no!"
		;;
	*)
		echo "Your choose is error!"
		;;
esac

不得不说,这语法太奇怪了...

4.3 for循环

语法1
for 变量 in 值1 值2 值3...
	do
		程序
	done

一个例子

#!/bin/bash
#打印时间
for time in morning noon afternoon night
	do 
		echo "$time"
	done

还有一个例子

#!/bin/bash

cd /root/sh/
ls *.sh > ls.log

y=1
for i in $(cat ls.log)
	do
		echo $y
		y=$(( $y+1))
	done
语法2
for (( 初始值;循环控制条件;变量变化 ))
	do
		程序
	done

一个例子

#!/bin/bash
#从1加到100

s=0
for ((i=1;i<=100;i=i+1))
do
	s=$(( $s+$i ))
done
echo "The sum of 1+2+3+...100 is: $s"

这里面有一个例子 ->批量添加指定数量的用户,不过有些复杂,和我的目的无关了,就不写了

4.4 while循环与until循环

while循环

while循环是不定循环,也称作条件循环,只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。这就和for的固定循环不太一样

语法

while [ 条件判断式 ]
	do
		程序
	done

一个简单的例子

#!/bin/bash

i=1
s=0
while [ $i -le 100 ]
#如果变量i的值小于等于100,则执行循环
do
		s=$(( $s+$i ))
		i=$(( $i+1 ))
done
echo "The sum is: $s"
until循环

until循环,和while循环相反,until循环时只要条件判断式不成立则进行循环,并执行循环程序,一旦循环条件成立,则终止循环

until [ 条件判断式 ]
	do
		程序
	done

5. 函数(Functions)

可以参考菜鸟教程

function funname(){
    action;
    return int;
}

从上面可以看出函数形式和C语言中是相似的

需要注意的是其中的关键字function,大部分时间都可以省略,这样还可以与其他的shell 兼容,但是有时候bash中命令可能与你的函数名重名,这是就不能省略了,见What is the 'function' keyword used in some bash scripts?

一个例子

#!/bin/bash

function demoFun1(){
    echo "这是我的第一个 shell 函数!"
    return `expr 1 + 1`
}

demoFun1
echo $?
echo $?
相关推荐
程序员_三木2 分钟前
使用 Three.js 创建动态粒子效果
开发语言·前端·javascript·数码相机·webgl·three.js
时间sk7 分钟前
CSS——17. nth-child选择器2
前端·css
时间sk8 分钟前
CSS——16. nth—child序列选择器1
前端·css
夜斗(dou)9 分钟前
CSS Grid 布局示例(基本布局+代码属性描述)
前端·css
远洋录10 分钟前
Tailwind CSS 实战:深色模式设计与实现
前端·人工智能·react
前端熊猫38 分钟前
css实现垂直文本
前端·css
JINGWHALE11 小时前
设计模式 结构型 桥接模式(Bridge Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·桥接模式
匹马夕阳1 小时前
Vue3-跨层组件通信Provide/Inject机制详解
前端·javascript·vue.js
凌览2 小时前
高收入的程序员为什么难找到女朋友?
前端·后端
Pee Wee2 小时前
责任链模式
java·前端·责任链模式