更多继续
接上一篇:
运算符
Shell 和其他编程语言一样,支持多种运算符,包括:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加(注意使用的是反引号 ` 而不是单引号 '):
反引号,其实就是将expr命令执行的结果作为最终的结果值。
两点注意:
- 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
- 完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,而是在 Esc 键下边的反引号。
更多直接参考
Shell 基本运算符 | 菜鸟教程 (runoob.com)
注意:注意: 条件表达式要放在方括号之间,并且要有空格,例如: $a==$b 是错误的,必须写成 $a == $b 。注意,这里面有四个空格,从方括号开始后面就有空格了。
比较新奇的一个字符串运算符
不同的运算符还得用不同的括号,三种括号,\[\]、\[]、(()),还有个反引号``,还蛮复杂的。算数运算符时,用expr加反引号``
乘号需要加转义,乘号(*)前边必须加反斜杠(\)才能实现乘法运算;
比较数字的大小关系时,可以用==、!==
或者这些符号
建议使用第二种方式比较数字大小
逻辑符号&&和||在使用时,必须使用双方括号\[ \&\& ],否则不识别。
shell(13): Shell脚本,if语句使用方双括号_shell if 双括号-CSDN博客
字符串运算符
暂时掌握这些基本用法。
增强型的括号形式,后面再陆续补充吧。
=~你想了解的 =~ 是 Shell(特指 Bash)中用于正则表达式匹配 的运算符,主要用在
[[ ... ]]条件判断语句里,用来检查左边的字符串是否匹配右边的正则表达式。先看一个最简单的例子,帮你理解核心逻辑:
#!/bin/bash # 定义一个字符串 str="hello123world" # 使用=~匹配是否包含数字(正则表达式:[0-9]+ 表示至少一个数字) if [[ $str =~ [0-9]+ ]]; then echo "字符串包含数字" else echo "字符串不包含数字" fi输出结果 :
字符串包含数字关键特性解释
- 适用场景 :只能用在
[[ ... ]]中,不能用在[ ... ]或test命令里(这是新手最容易踩的坑)。- 正则写法 :右边的正则表达式不需要加引号(加引号会被当成普通字符串匹配,失去正则功能)。
- 匹配结果 :匹配成功返回
0(true),失败返回1(false);匹配到的内容会自动存入内置数组BASH_REMATCH中。
条件语句-if
**注意:**在 sh/bash 里,如果 else 分支没有语句执行,就不要写这个 else分支。
Shell 流程控制 | 菜鸟教程 (runoob.com)
以if开头,以fi结尾。
有if就要搭配then,最后分支的else可以不用。
条件放在方括号......里。我看两个参考文档里,有的说条件后要加分号,有的又没有加,比较混乱。参考下这篇:shell中的if语句 - 开始认识 - 博客园 (cnblogs.com)
再参考这篇:
Shell if else语句(详解版) (biancheng.net)
可知:
当then和if处于同一行时,分号;才是必须的。
一句话核心:分号 ; 用来在同一行分隔多条独立命令。
shell 天然以换行作为命令结束标记;换行 = 隐式的分号。
本质:; = 命令分隔符,和换行等价,只是写在同一行。
shell中if后面可以不加方括号吗?在Shell脚本中,
if语句后面通常需要使用方括号[ ]来包围条件表达式。这是因为方括号实际上是一个命令,它用于测试条件是否为真。例如:
if [ "$a" -eq "$b" ]; then echo "a is equal to b" fi在这个例子中,
[ "$a" -eq "$b" ]是一个条件测试,它会返回一个退出状态码(0表示真,非0表示假)。然而,在某些情况下,你可以省略方括号,但这样做有一些限制和注意事项:
1、使用
[[ ]]进行条件测试
[[ ]]是 Bash 的扩展语法,提供了更强大的功能和更好的错误处理。它可以替代[ ],并且不需要转义某些特殊字符。
if [[ "$a" -eq "$b" ]]; then echo "a is equal to b" fi2、使用
test命令你也可以使用
test命令,它与[ ]等价。
if test "$a" -eq "$b"; then echo "a is equal to b" fi3、使用
(( ))进行算术比较对于算术比较,可以使用
(( ))。
if (( a == b )); then echo "a is equal to b" fi4、使用命令的退出状态码
你可以直接使用命令的退出状态码来判断条件。
if grep -q "pattern" file; then echo "pattern found in file" fi在这个例子中,
grep -q "pattern" file的退出状态码直接用于判断条件。这种情况下,加方括号 反而会出错?
总结
虽然可以省略方括号
[ ],但为了代码的可读性和兼容性,建议尽量使用[ ]、[[ ]]或test命令来进行条件测试。这样可以避免一些潜在的问题,并确保脚本在各种Shell环境中都能正确运行。
在Shell脚本中,退出状态码(exit status)用于表示命令的执行结果。具体来说:
0 表示真(true),即命令成功执行且没有遇到错误。
非零值 表示假(false),即命令执行失败或遇到了某种错误。
示例
以下是一些常见的命令及其返回码:
$ ls /home/user # 如果目录存在且有权限访问,则返回码为 0 $ echo "Hello, World!" # 输出字符串并成功执行,返回码为 0 $ cp source.txt destination.txt # 如果文件复制成功,返回码为 0检查返回码
你可以使用特殊变量
$?来检查上一个命令的返回码。例如:
$ ls /home/user $ echo $? # 输出 0 表示命令成功执行条件判断
在Shell脚本中,可以使用条件判断来处理不同的返回码。例如:
#!/bin/bash if ls /home/user; then echo "Command succeeded" else echo "Command failed with return code $?" fi在这个例子中,如果
ls /home/user命令成功执行,脚本会输出 "Command succeeded";否则,它会输出 "Command failed with return code" 以及实际的返回码。shell中的这个判断逻辑,和C语言等,以及常规思维有所差别,所以要万分注意!
其实这就是因为linux中,命令执行成功时会返回0,所以,shell中判断0为真,其实就是判断命令执行成功了。
Shell中,会将前一条指令执行完,再执行下一条指令。比如,前一条指令执行了10分钟,那么下一条指令就需要等10分钟后才会执行。
从上面的if例子就能看出来。
关于shell脚本中的真和假shell 世界有 1 条铁律
::::::只要是【命令 / 程序】 → 0 = 成功 = 真
一、0 = 真(成功) → 99% 的情况
只要你在执行一个命令、工具、脚本、
[ ]判断 返回 0 = 成功 = 条件成立 = 真这些全是命令:
ls cd cp [ -f file ] test -f file [[ -f file ]] ./a.out规则:
- 执行成功 → 返回 0 → if 走 then
- 执行失败 → 返回 非 0 → if 走 else
是不是很反常识?我们在shell脚本里面,就和通常的函数返回一样,想着是成功还是失败就好,成功就是真,不要去想着是0,要不容易弄混。
二、0 = 假 → 只有 1 种情况
先看这一种场景:
if [ 0 ]; then echo "真"; else echo "假"; fi输出:真 因为
[ 0 ]判断的是字符串非空,不是数字 0。
只有在【纯数字、纯逻辑比较】里,0 才是假
真正数字逻辑 0 = 假(几乎不用)
# 算术比较 if (( 0 )); then echo "真"; else echo "假"; fi输出:假
原因:
(( ))是 算术运算,完全遵守 C 语言规则:
- 0 = 假
- 非 0 = 真
终极总结(你背这 2 句就够)
1. 命令 / 文件判断 /
[ ]/[[ ]]0 = 成功 = 真(你写脚本 99% 都是这种)
2. 算术运算
(( ))0 = 假(极少用)
选择语句-case
case ... esac 为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句,esac(就是 case 反过来)作为结束标记。
可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
case ... esac 语法格式如下:
更多参考:
Shell 流程控制 | 菜鸟教程 (runoob.com)
示例1:基本用法
#!/bin/bash echo "请输入一个数字(1-3):" read num case $num in 1) echo "你选择了数字 1" ;; 2) echo "你选择了数字 2" ;; 3) echo "你选择了数字 3" ;; *) echo "无效的数字" ;; esac示例2:处理多个模式
#!/bin/bash echo "请输入一个字符:" read char case $char in [a-z]) echo "你输入的是一个小写字母" ;; [A-Z]) echo "你输入的是一个大写字母" ;; [0-9]) echo "你输入的是一个数字" ;; *) echo "你输入的不是一个字母或数字" ;; esac这个示例中,用户被提示输入一个字符,然后通过
case语句判断该字符是小写字母、大写字母还是数字,或者都不是。
循环语句
Shell 流程控制 | 菜鸟教程 (runoob.com)
for循环 while循环 until循环
for循环
shell三种 for循环方式_shell 循环100次-CSDN博客
参考:
Bash for循环 - Bash Shell教程 (yiibai.com)
注意,for-in中列表是以空格来分开每一项。
更多直接参考:
Shell 流程控制 | 菜鸟教程 (runoob.com)
while死循环每隔1秒执行一次ls,怎么一行写?
while true; do ls; sleep 1; done拆解
while true:条件永远成立,死循环;分隔,然后dols; sleep 1:先执行 ls,再 sleep 休眠 1 秒done循环结束补充说明
- 退出:按
Ctrl + C- 如果你想每秒刷新看目录变化 ,也可以用
watch -n1 ls,效果类似,不用 while 循环。小知识点:
true是命令,返回 0(成功),所以 while 一直循环。等价写法:while :; do ls; sleep 1; done``:是 bash 内置空命令,返回 0,比 true 更轻量化。
while :; do ls; sleep 1; done对比
while true:调用外部命令/bin/true(部分 shell 是内置)while ::bash 内置命令,没有进程创建开销,推荐。
函数
Shell(Bash)函数详解
作用:把一段代码封装成函数,复用。bash 函数本质就是一组命令,没有返回值,只有退出码。⚠️ 只适用于 bash,POSIX sh 也支持基础函数。
1. 两种定义语法
写法 1(推荐,bash 通用)
func_name() { # 函数体,一堆命令 echo "hello function" }写法 2(带
function关键字,bash 专属,posix sh 不支持)
function func_name { echo "hello" }注意:
function name()这种混合写法也能跑,但不推荐,可读性差。✅ 调用:直接写函数名,不加括号!(和 C 语言巨大区别)
func_name❌ 坑:不要
func_name()调用,这是定义语法,不是调用!2. 函数传参(重点)
函数没有参数列表 ,调用时跟在函数名后面的就是位置参数
$1,$2,$@,$*,和脚本的位置参数一模一样。
say() { echo "第一个参数: $1" echo "第二个参数: $2" echo "全部参数: $@" } # 调用,传参 say apple banana输出:
第一个参数: apple 第二个参数: banana 全部参数: apple banana函数内部的
$1 $2 $@会覆盖脚本全局的位置参数;函数执行完恢复。3. 返回值(退出码 return)
shell 函数不能 return 字符串,只能返回 0~255 的数字(退出码)
return 0:成功(true)
return 非0:失败(false)不写 return:默认以函数最后一条命令的退出码作为返回码
test_func() {
if [[ $1 -gt 10 ]]; then
return 0 # 成功
else
return 1 # 失败
fi
}test_func 20
echo $? # 0test_func 5
echo $? # 1如果想返回字符串 :函数内部 echo 输出,用
$()捕获
get_name() { echo "zhangsan" } res=$(get_name) echo $res
$()会开子 shell 捕获标准输出,这是 shell 获取字符串返回值的标准做法。4. 变量作用域(非常重要)
全局变量:默认,函数里定义的变量默认全局
func() { VAR="inside" } func echo $VAR # inside,外面能读到!局部变量:
local关键字(bash 内置,函数内部使用)
func() { local VAR="local val" echo $VAR } func echo $VAR # 空,外面访问不到✅ 写函数尽量用 local 声明内部变量,避免污染全局变量。⚠️ local 只能在函数内部使用,外面直接写 local 会报错。
5. 函数内部特殊变量
$1 $2:函数传入的位置参数$#:函数传入参数个数"$@":函数所有参数(推荐遍历)$?:上一条命令 / 函数 return 退出码$$:进程 PID,不会因为函数改变(函数不新建子 shell)6. 一行简写(你前面喜欢一行写法)
f(){ echo "hi $1"; }; f world7. 删除函数
unset -f f不加
-f的unset f是删除变量;unset -f删除函数。8. 函数的执行环境
函数默认在当前 shell 执行,不会新建子 shell! 和
source类似,函数内修改全局变量、set 选项,会影响当前 shell。
VAR=10 change_var(){ VAR=999 } change_var echo $VAR # 999,全局被修改!如果想让函数在子 shell 执行,加括号:
( change_var )9. 高频坑汇总
- 调用函数不加括号 ,
func不是func()- return 只能数字 0~255,不能 return 字符串;字符串靠 echo+$() 捕获
- 函数内变量默认全局!忘记 local 很容易污染外部变量,是脚本 bug 重灾区
- 函数内部
$1会覆盖脚本的$1,函数执行完恢复function关键字不是 POSIX 标准,追求跨 sh 兼容,只用func(){}
文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:
在shell编程中,source命令用于在当前shell环境中执行指定的脚本文件。这意味着脚本文件中的所有变量和函数都将在当前shell会话中可用。Linux下source命令详解
Linux下source命令详解 - 水车 - 博客园 (cnblogs.com)
source命令其实算不上是什么导入,就是在当前shell环境中执行一个指定的shell脚本,只不过最终效果和导入是一样的。
在C语言中,
#include用于将指定的头文件包含到源文件中,以便在编译时插入头文件的内容,提供函数声明、宏定义、类型定义等。而在Shell脚本中,没有直接的"包含"其他文件内容作为脚本一部分的预处理指令。不过,Shell脚本可以通过source命令或.操作符来引入另一个脚本文件的内容,但这与C语言中的#include有本质区别。例如:
C语言:
#include "stdio.h"是将标准输入输出库的头文件包含进来。Shell脚本:
source another_script.sh是将another_script.sh脚本中的命令在当前Shell环境中执行,相当于把该脚本的内容复制到当前脚本中依次执行。
shell格式规范建议
在Shell脚本或命令行中编写指令时,有一些基本的格式要求和最佳实践,以确保脚本的可读性和可维护性。以下是一些常见的格式要求:
使用空格分隔参数
每个命令、选项和参数之间应该用空格分隔。例如:
ls -l /home/user使用引号包围包含空格的字符串
如果参数中包含空格,需要使用单引号 (') 或双引号 (") 将其包围。例如:
echo "Hello, World!"使用变量时加上花括号
在引用变量时,最好使用花括号
{}来避免歧义。例如:
echo "Home directory is ${HOME}"使用反斜杠转义特殊字符
如果需要在字符串中使用特殊字符(如
$,\,"),可以使用反斜杠\进行转义。例如:
echo "This is a backslash: \\"使用注释解释代码
注释可以帮助理解代码的意图,使用 开头的行作为注释。例如:
# This script lists all files in the current directory ls -l保持缩进一致
虽然shell脚本对命令的缩进没什么要求,不像makefile里一样要求规则中的命令以Tab开头,注意别搞混了。
不过,对于多行命令或复杂的脚本,还是建议保持缩进一致可以提高可读性。例如:
if [ -f "/etc/passwd" ]; then echo "File exists" else echo "File does not exist" fi使用适当的退出状态码
确保脚本在错误情况下返回非零退出状态码,以便调用者能够检测到错误。例如:
if [ ! -f "/etc/passwd" ]; then echo "File does not exist" exit 1 fi使用绝对路径
尽量使用绝对路径而不是相对路径,以避免路径解析问题。例如:
/usr/bin/python3 script.py避免硬编码路径和值
将路径和常量值提取为变量,以提高脚本的灵活性和可维护性。例如:
LOG_DIR="/var/log/myapp" LOG_FILE="${LOG_DIR}/app.log"使用函数封装重复代码
将重复使用的代码段封装成函数,提高代码的复用性和可读性。例如:
function log_message { echo "$(date): $1""${LOG_FILE}" } log_message "Starting script"遵循这些格式要求和最佳实践,可以编写出更清晰、更易维护的Shell脚本。
补充
当Shell脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(加载顺序通常是/etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc等),在加载了上述环境变量文件后,Shell就开始执行Shell脚本中的内容。
Shell脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在Shell脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。
通常情况下,在执行Shell脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子Shell脚本。
exit 命令退出shell,判断上一个命令是否执行成功,可用 echo $? ,为0,表示执行成功,不为0表示失败。失败时,可以使用exit命令退出整个shell脚本的进程。
Shell中函数不调用就不会被执行。
shift,输入参数左移命令Linux shell脚本中shift的用法说明_shell shift-CSDN博客
shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本) 。
每执行一次shift,参数就左移一位。
Bash Shell - shell 编程 | shift 命令用法笔记 - 《技术私房菜》 - 极客文档 (geekdaxue.co)
[Linux---shell中(( ))、( )、``与{ }的区别 - chengd - 博客园 (cnblogs.com)](https://www.cnblogs.com/chengd/p/7803664.html "Linux—shell中(( ))、( )、``与{ }的区别 - chengd - 博客园 (cnblogs.com) ")
**dirname,**获取路径的命令Linux dirname命令教程:如何从给定的路径或文件名中提取目录(附实例用法详解和注意事项)-Linux入门自学网 (bashcommandnotfound.cn)
dirname命令是一个用于从给定的路径或文件名中提取目录部分的有用工具。
不过,该命令获取的是相对路径。
realpath,用于获取指定目录或文件的绝对路径。
readlink命令readlink命令 -- 找出符号链接所指向的位置 -- Linux命令大全(手册) (linuxcool.com)
readlink命令来自英文词组"read link"的拼写,中文译为"读取链接",其功能是用于找出符号链接所指向的位置。
trap命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。trap命令 -- 指定采取的动作 -- Linux命令大全(手册) (linuxcool.com)
shell里没有命令要tab开头的语法要求,是makefile里有。
Shell里的变量默认都是全局的。shell中局部变量及local命令_shell local-CSDN博客
函数里的全局变量是指从调用函数并执行时开始生效。
Shell脚本,或者脚本语言,是解释性的,不是提前编译的,所以,如果有什么语法错误,只有执行到对应行的时候才能知道。没法提前知道。
shell脚本其实就是一系列linux命令的集合,然后加了一些shell语法,以此来实现自动化执行这些命令。shell命令和shell脚本有哪些区别?
shell命令和shell脚本存在显著区别,主要体现在以下几个方面:
定义与形式
Shell命令:是用户在命令行提示符下输入的、由shell解释执行的单个指令,如"ls"命令用于列出目录内容,"cd"命令用于切换工作目录等。
Shell脚本:是将一系列shell命令集中写在文件中,并添加了控制语句和流程控制的结构,如if条件判断、for循环等,形成一个可执行的脚本文件,通常以".sh"作为扩展名。
执行方式
Shell命令:用户在终端中逐行输入命令,按下回车键后,shell立即对命令进行解析和执行,并返回结果。例如,在终端输入"pwd"命令,shell会立即返回当前工作目录的路径。
Shell脚本:需要先通过shell解释器来执行整个脚本文件。用户可以在终端中使用"sh 脚本文件名""bash 脚本文件名"或"./脚本文件名"(前提是脚本文件具有可执行权限)等方式来运行脚本。
功能与用途
Shell命令:主要用于完成一些简单的、一次性的操作任务,如文件和目录的管理、系统信息的查看、软件的安装与卸载等。比如使用"mkdir"命令创建新目录,"rm"命令删除文件或目录等。
Shell脚本:更适用于复杂的、需要多次重复执行的任务,以及对多个命令进行批量处理的场景。可以将一系列的操作步骤编写在一个脚本中,实现自动化执行,提高工作效率。例如,编写一个备份脚本,可以定期自动备份指定目录下的文件到另一个存储位置。
可读性与维护性
Shell命令:通常是简洁明了的单个指令,易于理解和记忆,但当需要完成复杂任务时,可能需要组合多个命令,并且命令行参数较多时,可读性和可维护性会降低。
Shell脚本:由于可以添加注释和合理的代码结构,使得脚本的逻辑更加清晰,易于阅读和理解。同时,对于需要修改和维护的部分,也更容易进行定位和调整。
综上所述,shell命令是用户与操作系统交互的基础工具,而shell脚本则是将这些工具组合起来,形成更强大的自动化解决方案。两者相辅相成,在不同的应用场景中发挥着各自的作用。
所谓shell脚本,不过就是若干shell命令加上一些结构化的语法,方便进行自动化批处理。shell脚本中用到的命令其实就是shell命令。
Shell的调试模式在Linux中,
set -x是一个用于调试Shell脚本的命令。它的作用是打开Shell的调试模式,使得脚本在执行过程中会显示每一行命令及其执行结果。这对于查找和解决脚本中的错误非常有用。解释
当你在Shell脚本中使用
set -x时,Shell会在标准错误输出(通常是终端)上打印出每一条被执行的命令,以及这些命令的执行结果。这样,你可以清楚地看到脚本的执行流程,并更容易地定位问题所在。示例
假设有一个简单的Shell脚本
example.sh:如果你在脚本中添加
set -x,如下所示:然后运行这个脚本,你会看到类似以下的输出:
关闭调试模式
如果你想关闭调试模式,可以使用
set +x。例如:在这个例子中,
set +x之后的echo "This will not be traced"将不会被跟踪和显示。总结
作用 :
set -x用于打开Shell脚本的调试模式,显示每条被执行的命令及其结果。使用场景:主要用于调试Shell脚本,帮助开发者快速找到和解决问题。
关闭方法 :使用
set +x可以关闭调试模式。通过合理使用
set -x,你可以更高效地开发和维护Shell脚本,提高脚本的可靠性和可维护性。













