Linux入门第一部分(Ubuntu入门篇) 第三章 shell脚本入门(一)

一、Shell脚本介绍

1、Shell脚本的作用

(1)在Linux开发中通常涉及终端敲命令,对于一些标准的流程,每次执行都要敲一大串雷同的命令会比较麻烦,Shell脚本就是把多条命令写到纯文本文件中,批量自动执行,类似于Windows的bat批处理文件。

(2)在嵌入式开发中,只需掌握Shell脚本的基础用法即可,通常需要使用Shell脚本完成一键编译、环境配置、测试、烧录辅助等操作。

(3)Shell脚本的约定后缀为.sh,不过Linux系统不依赖后缀识别脚本。

2、Shell脚本的写法和运行

(1)Shell脚本本质是纯文本文件,运行时默认按顺序逐行执行,它支持条件语句和循环语句改变流程。

(2)Shell脚本的第一行必须是"#!/bin/bash",它被称为释伴行(shebang),在命令行中用"./<脚本文件名>"直接执行脚本时,它可告诉操作系统使用bash解释器来解析本脚本。

(3)运行Shell脚本的两种方式(假设脚本名为test.sh):

bash 复制代码
# 方式1:主动调用bash解释器,不看shebang,不需要脚本有执行权限
bash test.sh   # bash -x test.sh,增加选项-x,在执行的时候会打印每一条执行的指令

# 方式2:直接执行,内核读取shebang;脚本必须有执行权限x
./test.sh

二、Shell脚本语法入门

1、注释与信息打印

(1)运行Shell脚本时,如果希望在运行过程中打印一些信息,可以使用echo命令,它的地位相当于C语言中的printf函数,用法如下:

①语法与可选选项:

echo 选项 <字符串> # 方括号表示可选参数,选项用"-"引出

|----|-------------------------|
| 选项 | 作用 |
| -n | 不加该参数时,打印完字符串默认换行 |
| -e | 开启转义字符解析,不加该参数将不会识别转义字符 |
| -E | 关闭转义字符解析,该参数可加可不加 |

常见的转义字符:

|------------|-------------------------------|
| 转义字符 | 含义 |
| \n | 换行 |
| \n | Tab水平制表 |
| \\ | 打印一个反斜杠"\" |
| \" | 打印一个双引号""" |
| \033[xxm | 设置后续打印内容的颜色,xx为待定系数,如果为0则表示重置 |

②基础示例:

1脚本文件:

bash 复制代码
#!/bin/bash

# ==========================================
# Shell 中 echo 命令的用法示例
# ==========================================

echo "--- 1. 默认,自动换行 ---"
echo hello
echo "(这是一个普通的换行输出)"
echo ""

echo "--- 2. -n 不换行 ---"
echo -n "hello"
echo "world"
echo "(上面两行连在了一起)"
echo ""

echo "--- 3. -e 启用转义 ---"
echo "3.1 换行符 \\n:"
echo -e "aaa\nbbb"
echo ""

echo "3.2 制表符 \\t (Tab):"
echo -e "a\tb"
echo ""

echo "3.3 回车符 \\r (覆盖行首):"
# 注意:\r 会让光标回到行首,后续输出的 "bbb" 会覆盖掉前面的 "aaa"
echo -e "aaa\rbbb" 
echo "(上面这行,你只能看到 bbb,因为 aaa 被覆盖了)"
echo ""

2运行结果:

bash 复制代码
zevalin@zevalin-VMware-Virtual-Platform:~/桌面$ bash test.sh
--- 1. 默认,自动换行 ---
hello
(这是一个普通的换行输出)

--- 2. -n 不换行 ---
helloworld
(上面两行连在了一起)

--- 3. -e 启用转义 ---
3.1 换行符 \n:
aaa
bbb

3.2 制表符 \t (Tab):
a	b

3.3 回车符 \r (覆盖行首):
bbb
(上面这行,你只能看到 bbb,因为 aaa 被覆盖了)

③引号的作用:

1如果字符串不用引号包裹,也能输出内容,但是连续的空格和Tab键会被压缩为单个空格,"*"和"?"会被glob通配符展开。

bash 复制代码
echo hello   world
#输出 hello world(多个空格被压缩)
echo *.sh
#打印当前目录所有sh文件名,而不是原样输出*.sh

2如果字符串用双引号包裹,字符串内容能正确输出,通配符不会展开,但"()"命令会被替换("()"命令见下一小节)。

bash 复制代码
NAME="test"
echo "name = $NAME"
#输出 name = test
echo "hello   world"
#保留中间多个空格

3如果字符串用单引号包裹,字符串完全按原样输出,变量不解析、转义字符不解析。

bash 复制代码
echo 'name = $NAME'
#输出 name = $NAME ,$NAME不会替换成变量值

④输出到文件:

1在语句末尾添加" > 文件名",可以把echo打印的内容以覆盖的方式式写入文件。

2在语句末尾添加" >> 文件名",可以把echo打印的内容以追加的方式式写入文件。

bash 复制代码
# > 覆盖写入(清空原有内容)
echo "hello" > log.txt

# >> 追加写入(在文件末尾加,不覆盖)
echo "new line" >> log.txt

(2)C语言中使用双斜杠"//"引出注释,而在Shell脚本文件中,则是使用"#"引出注释。

2、变量定义与引用

(1)脚本的位置参数:

①在运行Shell脚本时,用户可以在指令"bash test.sh"后面添加参数,多个参数用空格分隔,这些参数叫做位置参数,Shell脚本可以对它们进行引用。

②Shell脚本引用位置参数的方式:

|-----|----------------------------------|
| () | 含义 | | 0 | 脚本名 |
| n | 第n个位置参数,n为待定系数 | | # | 最后一个位置参数的标号 |
| ? | 上一条命令执行是否成功的结果 | | @ | 等价于1 2 ...... $n,n为最后一个位置参数的标号 |

③基础示例:

1脚本文件:

bash 复制代码
#!/bin/bash

echo "file name:" $0             # 引号不是echo参数的边界,引号外仍可追加内容
echo "total param num:" $#
echo "whole param:" $@
echo "first param:" $1
echo "second param:" $2

2运行结果:

bash 复制代码
zevalin@zevalin-VMware-Virtual-Platform:~/桌面$ bash test.sh 114 514
file name: test.sh
total param num: 2
whole param: 114 514
first param: 114
second param: 514

(2)自定义变量:

①除了位置参数以外,Shell脚本允许自定义变量,定义的语法为:

bash 复制代码
VAR=VALUE   # VAR是变量名,VALUE是变量的初值;等号两边不能有空格
# 需要注意Shell本身没有类型,变量默认全是字符串,如果VALUE正好能表示数字,才可以拿去做算数运算

②变量的访问方式有两种,如下所示:

bash 复制代码
echo $VAR     # VAR是变量名
echo ${VAR}   # VAR是变量名

③变量值的类型是字符串,如果字符串中有空格或者特殊字符,那就要用双引号把它包起来。

④使用set命令(不加任何参数)可以查看所有变量。

⑤基础示例:

1脚本文件:

bash 复制代码
#!/bin/bash

# 定义3个测试变量
var_name="Zevalin"
var_num=2026
var_city="Shenzhen"
# 使用set命令查看这三个变量
set

2运行结果:

bash 复制代码
# 使用set命令同时还会打印系统变量,这可以通过文本处理工具grep过滤,此处暂时先不介绍

(3)环境变量:

①按照常规方式定义变量,那它只是本地变量,仅在当前的shell进程有效,如果脚本里调用了别的程序或者子脚本,它们是无法感知这个变量的存在的。

②如果在定义变量时,在变量定义语句前加关键字export,即可将其定义为环境变量,派生的子进程、子脚本都能读取。

bash 复制代码
export VAR=VALUE   # VAR是变量名,VALUE是变量的初值

③使用env/printenv命令可以查看所有环境变量。

1printenv命令用法:

bash 复制代码
printenv                 # 打印全部环境变量
printenv <变量名>        # 单独打印指定环境变量

2env命令用法:

bash 复制代码
env                       # 打印全部环境变量
env <变量名>=<值> <命令>  # 临时设置环境变量(可多个),只给后面这条命令使用
env -i bash                 # 清空全部环境变量,在干净环境下执行命令

④基础示例:

1脚本文件:

bash 复制代码
#!/bin/bash

# 定义3个测试变量
export var_name="Zevalin"
export var_num=2026
export var_city="Shenzhen"
# 使用env命令查看这三个变量
env

2运行结果:

bash 复制代码
# 使用env命令同时还会打印系统变量,这可以通过文本处理工具grep过滤,此处暂时先不介绍

(4)只读变量:

①一些变量被定义后,可能会希望它只被读访问,而不希望它被改写(类似于const变量),这时可以在变量定义语句前加关键字readonly,即可将其定义为只读变量。

bash 复制代码
readonly VAR=VALUE   # VAR是变量名,VALUE是变量的初值
# VAR=NEW_VALUE   # 无法修改只读变量

②一旦设置了readonly,变量不能重新赋值,也不能用unset删除,适合脚本里写死的常量(如版本号、路径等)。

(5)变量删除:

①如果希望删除某个变量(或者函数),可以使用unset命令,使用方法如下:

bash 复制代码
unset [选项] <变量名/函数名>

|----|-----------------|
| 选项 | 作用 |
| -v | 默认选项,表示需要删除的是变量 |
| -f | 表示需要删除的是Shell函数 |

②unset不能删除只读变量和位置参数,但环境变量是可以用unset删除的。如果希望清空位置参数,可运行指令"set --"。

③unset只是删除当前Shell中的变量,不影响父Shell(即调用它的Shell)。

(6)Shell变量的加载顺序:

①Shell变量有加载顺序,对于同名的变量,后加载的会覆盖先加载的,大体的覆盖规则为全局系统配置先加载于用户个人配置。

②bash严格区分三类Shell,三类加载文件的优先级规则不同,具体如下:

1交互式登录Shell(如SSH登录):先加载/etc/profile(面向所有用户),内部会自动source /etc/profile.d/*.sh目录下所有脚本;然后按顺序~/.bash_profile → ~/.bash_login → ~/.profile查找,只加载第一个存在的文件;再加载~/.bashrc(用户终端配置),~/.bashrc内部再加载/etc/bash.bashrc。

2交互式非登录Shell(如直接在Ubuntu打开图形界面终端):先加载~/.bashrc,~/.bashrc内部再加载/etc/bash.bashrc。

3非交互式Shell(直接执行Shell脚本):默认不加载任何/etc/profile、~/.bashrc、~/.profile,如果环境变量BASH_ENV有值,就加载BASH_ENV指定的文件。

③source命令的作用就是在当前Shell进程里面,读取并执行目标文件里的命令(以及自定义变量),不新建子Shell,用法和bash类似,具体如下:

bash 复制代码
source <文件名> [位置参数]
. <文件名> [位置参数]           # 等价简写

3、数组操作

(1)数组的定义方式:

①一次性赋值:

bash 复制代码
ARR=(VALUE1 VALUE2 VALUE3 ......)
# 数组元素之间用空隔分隔,如果是带空格的字符串,则需要加引号

②逐个按下标赋值:

bash 复制代码
ARR[0]=VALUE1
ARR[1]=VALUE2
ARR[n]=VALUE3  # n≥0
# 下标可以不连续,中空的部分内容为空

③定义空数组:

bash 复制代码
ARR=()

(2)数组元素的读取方式:

bash 复制代码
${ARR[0]}   # 读取ARR数组下标0索引的元素
${ARR[n]}   # 读取ARR数组下标n索引的元素(n≥0)
${ARR[@]}   # 取出ARR数组全部元素(空格分开独立元素,可用于for循环)
${ARR[*]}   # 取出ARR数组全部元素,当成一整个字符串(默认IFS空格拼接)

(3)数组长度的获取方式:

bash 复制代码
${#ARR[@]}    # 获取数组ARR元素总个数(常用)
${#ARR[n]}     # 获取数组ARR下标n这个字符串的字符长度(n≥0)

(4)数组切片(截取一部分)方式:

bash 复制代码
# 格式:${<数组名>[@]:<起始下标>:<截取数量>}
ARR=(VALUE1 VALUE2 VALUE3 VALUE4 ......)
echo ${ARR[@]:1:3}   # 从下标1开始,取3个元素 → VALUE1 VALUE2 VALUE3

(5)对数组修改或追加元素的方式:

bash 复制代码
# 修改数组ARR下标n(n≥0)的元素:ARR[n]=NEW_VALUE
ARR[2]="new"                # 修改数组ARR下标2的元素为"new"

# 在数组ARR末尾追加若干个元素:ARR+=(VALUE1 VALUE2 ......)
ARR+=("grape" "pear")         # 在数组ARR末尾依次追加元素"grape"、"pear"

(6)删除数组元素或整个数组的方式:

bash 复制代码
unset ARR[1]    # 删除ARR数组下标1的元素,数组长度变化,但下标不会自动重排
unset ARR       # 删除整个ARR数组

4、字符串操作

(1)引号与命令替换:

|-----|------|----------------------------------------|---------------------------------|
| 符号 | 名称 | 规则 | 示例 |
| ' ' | 单引号 | 包含的字符串原样输出,不解析变量、不解析命令替换、不识别转义,但内部空格压缩 | echo 'PATH' →输出"PATH" |
| " " | 双引号 | 包含的字符串输出前解析变量、解析命令替换,内部空格不压缩,通配符*不展开 | echo "PATH" →输出PATH的真实值 | | () | 命令替换 | 执行小括号内的命令,命令的输出结果替换进字符串中 | VAR=$(echo VALUE) →等价于VAR=VALUE |

(2)字符串长度的获取方式:

${#<字符串变量名>} # 输出字符个数

(3)字符串子串的截取方式:

${<字符串变量名>:<起始位置>:长度}

起始位置支持填写负数,表示从最后一个字符开始取到倒数第几个字符(正序输出)

长度是可选项,省略则默认取到末尾

(4)字符串部分字符的替换方式(此处的"替换"不改变变量本身的值,相当于引用时先拷贝个副本,做个替换再使用):

bash 复制代码
str="a b a c"
# ${var/old/new}  只替换第一个匹配old的字符为new
echo ${str/a/1}   # 输出1 b a c

# ${var//old/new}  全局替换,全部匹配old的字符都换为new
echo ${str//a/1}  # 输出1 b 1 c

(5)在文件名处理中,还可以使用字符串操作实现前缀或后缀的删除(此处的"删除"也不改变变量本身的值):

bash 复制代码
str="test.txt"
${str#*.}   	# 删掉最短前缀匹配,输出后缀txt(*是通配符,字符串从左往右数第一个"."开始,取后面的内容,注意不取第一个".")
${str##*.}  	# 删掉最长前缀匹配,输出后缀txt(*是通配符,字符串从左往右数最后一个"."开始,取后面的内容,注意不取最后一个".")
${str%.*}   	# 删掉最短后缀匹配,输出前缀test(*是通配符,字符串从左往右数最后一个"."开始,取前面的内容,注意不取最后一个".")
${str%%.*}  	# 删掉最长后缀匹配,输出前缀test(*是通配符,字符串从左往右数第一个"."开始,取前面的内容,注意不取第一个".")

5、数值计算

(1)整数运算:

①在Shell脚本中,也可实现整数运算,不过需要注意的是,参与整数运算的变量只能是表示整数的字符串,并且有特定的语法。

bash 复制代码
echo $((a + b))   	# 输出a+b的结果
echo $((a - b))   	# 输出a-b的结果
echo $((a * b))   	# 输出a*b的结果
echo $((a / b))   	# 输出a/b的结果,向下取整
echo $((a % b))   	# 输出a%b的结果

②可以在定义变量时使用let命令直接使用两个整数的运算结果给变量赋值。

bash 复制代码
let c=a+b   	# 把a+b的结果赋给变量c
let c=a-b   	# 把a-b的结果赋给变量c
let c=a*b   	# 把a*b的结果赋给变量c
let c=a/b   	# 把a/b的结果赋给变量c
let c=a%b   	# 把a%b的结果赋给变量c

(2)小数运算:

①bash本身无法实现小数运算(浮点运算),需要依赖外部工具,比如bc和awk,其中bc为Ubuntu自带的工具。

②在echo时,指令末尾添加"| bc",表示使用bc工具识别小数,而在打印的字符串中,如果涉及小数运算,则需要用字符串"scale=n"声明保留的小数位数n(直接按n位截断,不会四舍五入),语法为------"echo "scale=<小数位数>;<运算表达式>" | bc"。bc工具的使用示例:

bash 复制代码
a=10
b=3
echo "scale=4; $a / $b" | bc
# 输出 3.3333

echo "scale=2; 2.5 + 3.7" | bc
# 输出 6.20

# 支持比较(bc返回1真,0假)
echo "1.2 > 0.9" | bc

6、逻辑运算

(1)test命令的使用:

①test命令可用于判断表达式是否成立,它还有另一种写法------中括号" ",语法如下,其中对于中括号的写法,表达式和中括号之间必须留空格。(test命令常配合if判断使用)

test <表达式>

\<表达式\>

②test命令可用于字符串的判断:

bash 复制代码
[ "$s1" = "$s2" ]    	# 判断字符串s1和s2是否相等(注意使用的是"="而不是"==")
[ "$s1" != "$s2" ]   	# 判断字符串s1和s2是否不等
[ -z "$s" ]          	# -z表示判断字符串s长度是否为0(空)
[ -n "$s" ]          	# -n表示判断字符串s长度是否不为0(非空)

③test命令也常用于测试文件:

bash 复制代码
[ -e file ]   	# 判断文件/目录file是否存在
[ -f file ]   	# 判断文件/目录file是否是普通文件
[ -d file ]   	# 判断文件/目录file是否是目录
[ -r file ]   	# 判断文件/目录file是否可读
[ -w file ]   	# 判断文件/目录file是否可写
[ -x file ]   	# 判断文件/目录file是否可执行
[ -s file ]   	# 判断文件/目录file是否存在且大小不为空

(2)整数的比较本质就是一个条件表达式,比较运算符的语法如下:

bash 复制代码
[ $a -eq $b ]   	# 判断整数a和b是否相等
[ $a -ne $b ]   	# 判断整数a和b是否不相等
[ $a -gt $b ]   	# 判断整数a是否大于b
[ $a -ge $b ]   	# 判断整数a是否大于等于b
[ $a -lt $b ]   	# 判断整数a是否小于b
[ $a -le $b ]   	# 判断整数a是否小于等于b

(3)Shell脚本中的逻辑运算符和C语言有较大出入,在Shell脚本的条件表达式中,"!"表示非,"-a"表示与,"-o"表示或,使用举例如下:

bash 复制代码
[ ! -f a.txt ]          		# 判断a.txt是否不是不同文件
[ -d testdir -a -r testdir ]   	# 判断testdir是否是目录且是否可读,同时成立才为真
[ -f a.txt -o -f b.txt ]       	# 判断a.txt是否是普通文件或b.txt是否是普通文件,任一条件成立则为真

(4)Shell脚本也有"&&"和"||"的语法,但它们不算是逻辑运算符,而是命令连接符,用法如下:

①cmd1 && cmd2:cmd1返回0(真),才执行cmd2。

②cmd1 || cmd2:cmd1返回非0(假),才执行cmd2。

③举例------查看文件是否存在:

bash 复制代码
#!/bin/bash
echo "please input file name:"
read -p "file name:" filename        # read命令用于交互,用户需输入文件名
test -e $filename && echo "$filename exist" || echo "$filename no exist"  #判断用户输入的文件名是否存在,如存在则输出exist,cmd1 && cmd2的结果为真,"||"后面的命令不执行,否则cmd1 && cmd2的结果为假"||"后面的命令执行,输出no exist
相关推荐
丘山望岳1 小时前
Linux基础指令入门篇
linux·运维·服务器
笨笨饿1 小时前
#143_屏幕画撕裂的问题及解决方法
linux·stm32·单片机·嵌入式硬件·mcu·lvgl·嵌入式实时数据库
小年糕是糕手1 小时前
【AI】用 Python 做一个 AI 截图检索助手:把收藏夹里的截图找回来
linux·人工智能·python·chatgpt·agent·codex·deepseek
hhhjhl1 小时前
Zod 跨端三方库仓颉适配全流程
linux·运维·ubuntu
2035去旅行1 小时前
U-Boot DM(Driver Model)驱动框架
嵌入式·arm·soc
叼烟扛炮1 小时前
Linux 学习笔记(三十二):线程控制——创建、等待与分离
linux·运维·服务器·笔记·学习·1024程序员节
夜雪一千2 小时前
Linux 权限、用户、用户组内容介绍
linux·运维·服务器
拂拉氏2 小时前
【知识讲解】 Linux信号的认识与使用
linux·信号
硅基造物主2 小时前
STM32 从 0 到 1 做智能家居(一):先想清楚要做什么
stm32·mqtt·嵌入式·智能家居·hal库·esp-12f