Shell 脚本编程
同传统的编程语言一样,shell 提供了很多特性,这些特性可以使你的 shell 脚本编程更为有用。
创建 Shell 脚本
一个 shell 脚本通常包含如下部分:首行+注释+内容
- 首行
第一行内容在脚本的首行左侧,表示脚本将要调用的shell解释器,内容如下:
#!/bin/bash
#!符号能够被内核识别成是一个脚本的开始,这一行必须位于脚本的首行,/bin/bash 是 bash 程序的绝对路径,在这里表示后续的内容将通过 bash 程序解释执行。
- 注释
注释符号 # 放在需注释内容的前面,如下:
#! /bin/bash
# my first shell script(注释)
- 内容
可执行内容和 shell 结构
#! /bin/bash
echo "hello,world"
Shell 脚本的权限
一般情况下,默认创建的脚本是没有执行权限的
# ll | grep bash
-rw-r--r--. 1 root root 56 11月 1 07:19 bash.sh
没有权限不能执行,需要赋予可执行权限
# chmod +x bash.sh
# ll | grep bash
-rwxr-xr-x. 1 root root 56 11月 1 07:19 bash.sh
Shell 脚本的三种执行方式
-
1、输入脚本的绝对路径或相对路径
注:相对路径不能省略 ./
-
2、bash (或 sh) + 脚本
注:当脚本没有 x 权限时,root 和文件所有者通过该方式可以正常执行
注:创建 bash 新环境并执行脚本,执行完释放 bash 环境
-
**3、在脚本的路径前再加 ". " 或 source **
注:第一种和第二种会创建一个 bash 环境,不同 bash 中的变量无法共享;但是第三种方式是在同一个 bash 里面执行的
Shell 变量
-
变量:是 shell 传递数据的一种方式,用来代表每个取值的符号名;当 shell 脚本需要保存一些信息时,如一个文件名或是一个数字,就把它存放在一个变量中
-
变量设置规则:
-
变量名称可以由字母,数字和下划线组成,但是不能以数字开头,环境变量名建议大写,便于区分
-
在 bash 中,变量的默认类型都是字符串型,如果要进行数值运算,则必须指定变量类型为数值型。
-
变量用等号连接值,等号左右两侧不能有空格
-
- 变量的值如果有空格,需要使用单引号或者双引号包括。
变量分类
Linux Shell 的变量分为用户自定义变量、环境变量、位置参数变量和预定义变量
# set # 可以查看系统中存在的所有变量
-
系统变量:保存和系统操作环境相关的数据,如 $HOME、$PWD、$SHELL、$USER 等
-
位置参数变量:主要用来向脚本中传递参数或数据,变量名不能自定义,变量作用固定
-
预定义变量:是 Bash 中已经定义好的变量,变量名不能自定义,变量作用也是固定的
用户自定义变量
用户自定义的变量由字母或下划线开头,由字母,数字或下划线序列组成,并且大小写字母意义不同,变量名长度没有限制。
-
设置变量
习惯上用大写字母来命名变量,变量名以字母表示的字符开头,不能用数字。
# name=zs
-
变量调用
在使用变量时,要在变量名前加上前缀 “$”
使用 echo 命令查看变量值。
# echo $name
- 变量赋值
- 定义变量时赋值:变量=值
# STR="hello world"# A=9
2. 将一个命令的执行结果赋给变量
# A=`ls -la` # 反引号,运行里面的命令,并把结果返回给变量A# A=$(ls -la) # 等价于反引号# echo $A# aa=$((4+5))
3. 将一个变量赋给另一个变量
# A=$STR
- 变量叠加
# aa=123# cc="$aa"456 # 使用双引号# cc=${aa}456 # 使用{}# cc='$aa'456 # 无法叠加变量
单引号和双引号的区别:
双引号:双引号里的变量名会替换为变量值
单引号:会将所有特殊字符脱意,里面的内容会原样全部输出
# NUM=10 # SUM="$NUM hehe" # echo $SUM 10 hehe# SUM='$NUM hehe' # echo $SUM $NUM hehe
-
列出所有的变量
# set
-
删除变量
# aa=22# echo $aa# unset aa# set# echo $aa# readonly bb=2 # 声明静态的变量不能unset# unset bb-bash: unset: bb: cannot unset: readonly variable
作用域:用户自定义的变量,作用域为当前的 shell 环境。
环境变量
用户自定义变量只在当前的 shell 中生效,而环境变量会在当前 shell 和其所有子 shell 中生效。如果把环境变量写入相应的配置文件,那么这个环境变量就会在所有的 shell 中生效。
export 变量名=变量值 # 申明环境变量
作用域:当前 shell 以及所有的子 shell
位置参数变量
变量 | 描述 |
---|---|
$n | n为数字,$0代表命令本身,$1-$9代表第一到第9个参数, 十以上的参数需要用大括号包含,如${10}。 |
$* | 代表命令行中所有的参数,把所有的参数看成一个整体。以"$1 $2 … $n"的形式输出所有参数 |
$@ | 代表命令行中的所有参数,把每个参数区分对待。以"$1" "$2" … "$n" 的形式输出所有参数 |
$# | 代表命令行中所有参数的个数。添加到shell的参数个数 |
-
shift 指令
参数左移,每执行一次,参数序列顺次左移一个位置,$# 的值减1,用于分别处理每个参数,移出去的参数不再可用
$ 和 $@ 的区别*
$* 和 $@ 都表示传递给函数或脚本的所有参数
- 不被双引号" "包含时,都以"$1" "$2" … "$n" 的形式输出所有参数
- 被双引号" "包含时
- "$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数
- "$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数
预定义变量
变量 | 描述 |
---|---|
$? | 执行上一个命令的返回值 执行成功,返回0,执行失败,返回非0(具体数字由命令决定) |
$$ | 当前进程的进程号(PID),即当前脚本执行时生成的进程号 |
$! | 后台运行的最后一个进程的进程号(PID),最近一个被放入后台执行的进程 &(后台运行) |
read 命令
read [选项] 值
read -p(提示语句) -n(字符个数) -t(等待时间,单位为秒) –s(隐藏输入)
运算符
// 无法正确运算(变量默认是字符串类型)# num1=11# num2=22# sum=$num1+$num2 # echo $sum
格式 : expr m + n 或 $((m+n))
expr 命令:对整数型变量进行算术运算
# expr 3 + 5 // expr 运算符间要有空格# expr 3 – 5# echo `expr 10 / 3`# expr 3 \* 10 // \ 是转义符
计算(2 +3 )× 4 的值
# S=`expr 2 + 3`# expr $S \* 4# expr `expr 2 + 3` \* 4# echo $(((2 + 3) * 4)) //推荐使用
$() 、${} 与 $(()) 的区别
-
$( ):用途和反引号``一样,用来表示优先执行的命令(与反引号可读性更高)
# echo $(ls -l)
-
${ } :获取变量值 (与$相比变量更容易识别)
# echo ${PATH}
-
$((运算内容)) :适用于数值运算(比expr更简洁)
# echo $((3+1*4))
条件测试
内置 test 命令
test 命令可用于测试表达式,支持测试的范围包括:字符串比较,算术比较,文件存在性、属性、类型等判断。例如,判断文件是否为空、文件是否存在、是否是目录、变量是否大于5、字符串是否等于"longshuai"、字符串是否为空等等。在shell 中,几乎所有的判断都使用 test 命令实现。
内置 test 命令常用操作符号有两种格式:
- test expression
- [ expression ] # expression表达式首尾都必须要有空格(推荐使用)
表达式的结果为真,则 test 的返回值为 0,否则为非 0
当表达式的结果为真时,则变量$?的值就为 0,否则为非 0
字符串测试:
- test str1 == str2 或 [ str1 == str2 ] #测试字符串是否相等 =
- test str1 != str2 或 [ str1 != str2 ] #测试字符串是否不相等
- test str1 或 [ str1 ] #测试字符串是否不为空,(不为空:true,为空:false)
- test -n str1 或 [ -n str1 ] #测试字符串是否不为空
- test -z str1 或 [ -z str1 ] #测试字符串是否为空
注意:程序世界中一般都用1表示true,0表示false。但0表示失败的话,没有具体详细的失败含义,比如失败原因可能是输入参数不合法,可能是数据不存在等等,使用0来涵盖这些所有的异常原因不利于问题排查。
因此在 Bash 中,用 0 表示 true,非0 表示 false。我们可以使用1-255中的任何一个数字来表示某个具体的错误,1是一个普遍的错误,126意味着一个文件不能被执行,127意味着’找不到命令’等。
; 命令连接符号,可以连接多个命令一起执行
- && 逻辑与条件满足,才执行后面的语句
# [ "hello" == "hello" ] && echo yes
- || 逻辑或,条件不满足,才执行后面的语句
[ "hello" == "world" ] && echo yes || echo no
整数测试:
符号 | 运算符 |
---|---|
-eq | == |
-ge | >= |
-gt | > |
-le | <= |
-lt | < |
-ne | != |
- test int1 -eq int2 或 [ int1 -eq int2 ] # 测试整数是否相等 equals
- test int1 -ge int2 或 [ int1-ge int2 ] # 测试int1是否>=int2
- test int1 -gt int2 或 [ int1 -gt int2 ] # 测试int1是否>int2
- test int1 -le int2 或 [ int1 -le int2 ] # 测试int1是否<=int2
- test int1 -lt int2 或[ int1 -lt int2 ] # 测试int1是否<int2
- test int1 -ne int2 或 [ int1 -ne int2 ] # 测试整数是否不相等
# test 100 –ge 100;echo $?# [ 100 -ge 100 ];echo $?
文件测试:
- test -d file 或 [ -d file ] # 指定文件是否目录
- test –e file 或 [ -e file ] # 文件是否存在 exists
- test -f file 或 [ -f file ] # 指定文件是否常规文件
- test –L file [ -L file ] # 文件存在并且是一个符号链接
- test -r file [ -r file ] # 指定文件是否可读
- test -w file [ -w file ] # 指定文件是否可写
- test -x file [ -x file ] # 指定文件是否可执行
# test -d install.log #是否为目录# test –r install.log #文件是否可读# test –f xx.log ; echo $? #是否是常规文件# [ -L service.soft ] && echo “is a link” #文件存在并且是一个符号链接# test -L /bin/sh ;echo $? #文件存在并且是一个符号链接# [ -f /root ] && echo “yes” || echo “no” #是否是常规文件
多重条件测试:
- 条件1 –a 条件2 (逻辑与:两个都成立,则为真)
- 条件1 –o 条件2 (逻辑或 :只要有一个为真,则为真)
- !条件 (逻辑非:取反)
注意与 && 和 || 的区别,不要混淆在一起
num=100 [ $num -gt 50 -a $num -gt 100 ];echo $? [ $num -gt 50 -o $num -gt 10 ];echo $?
流程控制语句
if/else命令
1, 单分支if条件语句
if [ 条件判断式 ] then 代码块fi
或者
if [ 条件判断式 ] ; then 代码块fi
#!/bin/shif [ $USER == "root" ];then echo "super man"fi
单分支条件语句注意事项:
-
if 语句使用 fi 结尾,和一般语言使用大括号结尾不同
-
[ 条件判断式 ] 就是使用 test 命令判断,所以中括号和条件判断式之间必须有空格
2,多分支if条件语句
if [ 条件判断式1 ] then 当条件判断式1成立时,执行程序1elif [ 条件判断式2 ] then 当条件判断式2成立时,执行程序2...省略更多条件else 当所有条件都不成立时,最后执行此程序fi
case 命令
case 命令是一个多分支的 if/else命令,case 变量的值用来匹配 value1,value2,value3 等等。匹配到后则执行跟在后面的命令直到遇到双分号为止(;😉,case 命令以 esac 作为终止符。
for 循环
for 循环命令用来在一个列表条目中执行有限次数的命令。比如,你可能会在一个姓名列表或文件列表中循环执行同个命令。
- for 命令后紧跟一个自定义变量、一个关键字 in 和一个字符串列表(可以是变量)
- 第一次执行 for 循环时,字符串列表中的第一个字符串会赋值给自定义变量,然后执行循环命令,直到遇到done语句;
- 第二次执行 for 循环时,会右推字符串列表中的第二个字符串给自定义变量,依次类推,直到字符串列表遍历完。
for 语法格式
- 第一种:
#!/bin/bashfor i in 1 2 3 do echo $idone
- 第二种:
for ((i = 0; i <= 5; i++))do echo "welcome $i times"done
while 循环
while 命令根据紧跟其后的表达式来判断是否执行 while 循环,当 表达式执行后的返回值为0时,则执行 while 循环语句块,直到遇到 done 语句,然后再返回到 while 命令,判断表达式的返回值,当得打返回值为非0时,则终止 while 循环。
while 语法格式
- 第一种(循环条件)
while expressiondo 代码done
- 第二种(死循环)
while :do 代码done
自定义函数
函数代表着一个或一组命令的集合,表示一个功能模块,常用于模块化编程。
以下是关于函数的一些重要说明:
-
在 shell 中,函数必须先定义再调用
-
使用 return value 来获取函数的返回值
-
函数在当前 shell 中执行,可以使用脚本中的变量
函数定义格式
function 函数名(){ 命令1…. 命令2…. return 返回值变量}
注意:
-
如果函数名后没有(),在函数名和 { 之间,必须要有空格以示区分
-
函数返回值,只能通过$? 系统变量获得,可以显示加:return 返回值,如果不加将以最后一条命令运行结果,作为返回值
脚本调试
- sh -x script:这将执行该脚本并显示所有变量的值。
- set -x :在shell脚本里添加,对部分脚本调试
- sh -n script:不执行脚本只是检查语法的模式,将返回所有语法错误。
- sh –v script:执行并显示脚本内容
数组
定义一个数组
[root@node-01 shell]# array=("zhangsan" "lisi" "wangwu")
打印出数组中所有的字符串
[root@node-01 shell]# echo ${array[@]}
zhangsan lisi wangwu
[root@node-01 shell]# echo ${array[*]}
zhangsan lisi wangwu
打印出数组参数的个数
[root@node-01 shell]# echo ${#array[*]}
3
[root@node-01 shell]# echo ${#array[@]}
3
在数组下标为3的位置插入元素
[root@node-01 shell]# array[3]="zhao"
再次打印数组元素的个数
[root@node-01 shell]# echo ${#array[@]}
4
清除数组下标为0的元素
[root@node-01 shell]# unset array[0]
再次打印数组元素的个数
[root@node-01 shell]# echo ${#array[@]}
3
清除数组0下标元素,下标0位置存在,无元素
[root@node-01 shell]# echo ${array[0]}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现