Linux 的 shell编程
§1 SHELL 是什么
shell 是一个命令行解释器,它为用户提供了一个向 Linux 内核发送请求以便云心程序的界面系统级成程序,用户可以用 SHELL 启动、挂起、停止、编写一些程序
§2 shell 的头、执行、注释
§2.1 头
shell 以 #!/bin/bash 或 #!/bin/sh 开头
#! 不是注释,是一个特殊符号,用于指定 shell 脚本解释器的路径
通常,sh 会软连接至 bash,并开启 POSIX 标准模式,相当于 #!/bin/bash --posix
§2.2 执行
脚本的执行
shell 脚本有以下三种执行方式:
- 使用脚本绝对路径执行,脚本需要具有执行权限
- 使用脚本相对路径执行,脚本需要具有执行权限
- sh 脚本执行,脚本不需要执行权限
脚本的后台执行
./play.sh &
脚本的后台执行同时输出重定向
nohup ./play.sh >output 2>&1 &
linux 中三个标准流
- 0:标准输入流 stdin
- 1:标准输出流 stdout
- 2:标准错误流 stderr
nohub 指令
- 默认在当前目录下,输出一个名叫 nohup.out 的文件
- 使用 > 文件 重定向 nohub 的输出 ,>文件 其实是 1>文件 的简写
- 使用 流1> &流2 可以重定向流(引用流的目的或源),如 2>&1,表示标准错误流的输出位置引用当前的标准输出流
- 2>1 表示标准错误流的输出位置是文件 1
§2.3 注释
按惯例,分为单行注释和多行注释
- 单行注释
# 被注释的一行语句
- 块注释
:<<!
被注释的多行语句
被注释的多行语句
被注释的多行语句
!
§3 变量
变量分类
在 shell 中,可以使用以下几类变量
- 系统变量
- 用户自定义变量
- 脚本参数变量
- 预定义变量
§3.1 系统变量
系统变量也叫环境变量,下表为常见 系统变量,可以用 set 指令显示所有 系统变量
变量 | 含义 |
---|---|
$BASH | 展开为调用bash实例时使用的全路径名 |
$CDPATH | cd命令的搜索路径。它是以冒号分隔的目录列表,shell通过它来搜索cd命令指定的目标目录。例如.:~:/usr |
$EDITOR | 内置编辑器emacs、gmacs或vi的路径名 |
$ENV | 每一个新的bash,shell(包括脚本)启动时执行的环境文件。通常赋予这个变量的文件名是.bashrc。 |
$EUID | 展开为在shell启动时被初始化的当前用户的有效ID |
$GROUPS | 当前用户所属的组 |
$HISTFILE | 指定保存命令行历史的文件。默认值是~/.bash_history。如果被复位,交互式shell退出时将不保存命令行历史 |
$HISTSIZE | 记录在命令行历史文件中的命令数。默认是500 |
$HOME | 主目录。未指定目录时,cd命令将转向该目录 |
$IFS | 内部字段分隔符,一般是空格符、制表符和换行符,用于由命令替换,循环结构中的表和读取的输入产生的词的字段划分 |
$LANG | 用来为没有以LC_开头的变量明确选取的种类确定locale类 |
$OLDPWD | 前一个工作目录 |
$PATH | 命令搜索路径。一个由冒号分隔的目录列表,shell用它来搜索命令,一个普通值为 /usr/gnu/bin:/usr/local/bin:/usr/ucb:/usr/bin |
$PPID | 父进程的进程ID |
$PS1 | 主提示符串,默认值是$ |
$PS2 | 次提示符串,默认值是> |
$PS3 | 与select命令一起使用的选择提示符串,默认值是#? |
$PS4 | 当开启追踪时使用的调试提示符串,默认值是+。追踪可以用set –x开启 |
$PWD | 当前工作目录。由cd设置 |
$RANDOM | 每次引用该变量,就产生一个随机整数。随机数序列可以通过给RANDOM赋值来初始化。如果RANDOM被复位,即使随后再设置,它也将失去特定的属性 |
$REPLY | 当没有给read提供参数时设置 |
$SHELL | 当调用shell时,它扫描环境变量以寻找该名字。shell给PATH、PS1、PS2、MAILCHECK和IFS设置默认值。HOME和MAIL由login(1)设置 |
$SHELLOPTS | 包含一列开启的shell选项,比如braceexpand、hashall、monitor等 |
$UID | 展开为当前用户的用户ID,在shell启动时初始化 |
设置系统变量
- 声明:export 系统变量名=值
- 生效:使用 source /etc/profile 指令命令使系统变量生效
- 引用:$系统变量名
§3.2 用户自定义变量
声明/赋值、引用、销毁、引用命令执行结果
- 声明一般变量:变量名=值
- 变量名可以由 字母、数字、下划线组成,但不能由数字开头
- = 两边不能有空格
- 变量名通常大写
- 声明静态变量:readonly 变量名 = 值,静态变量不能销毁
- 引用变量的值:$变量名
- 引用命令执行结果
- 变量名=`命令`
- 变量名=$(命令)
- 销毁变量:unset 变量名
#!/bin/bash
A=111
readonly B=222
echo "a=$A"
echo "b=$B"
unset A
unset B
echo "a=$A"
echo "b=$B"
#!/bin/bash
A=`ls -l /home`
B=$(ls -l /root)
echo "A=$A"
echo "B=$B"
§3.3 脚本参数变量
执行脚本时,需要使用命名,并可能带有参数
若希望对命令本身和参数进行操作,可以使用 脚本参数变量
常见语法
- $n/${n}:n 是数字
- n=0,表示命令本身,不带参数
- n∈(1-9),表示命令的第 1 - 第 9 个参数
- n>=10,使用 ${n},表示命令的第 n 个参数
- $* :所有参数,作为整体,与 $@ 区别见 §5.3.1 示例
- $@:所有参数,作为数组,与 $* 区别见 §5.3.1 示例
- $#:参数个数
#!/bin/bash
echo "指令本身 $0"
echo "第二个参数 $2"
echo "所有参数 $*"
echo "所有参数 $@"
echo "参数个数 $#"
§3.4 预定义变量
下列符号为预定义变量
- $$:当前进程号
- $!:后台运行的最后一个进程的进程号
- $?:上一个命令的执行状态
- 返回 0,上条命令执行正确
- 返回 非0,上条命令执行不正确,但具体返回什么随不同命令变化
#!/bin/bash
# 输出当前进程号
echo "$$"
# 将一个脚本后台运行
./beplaied &
# 输出后台运行的最后一个进程号
echo "$!"
# 随便执行一个指令
cd /root
# 查看刚刚执行的指令是否成功
echo "$?"
§4 表达式与运算符
表达式主要可以分为
- 运算表达式
- 判断表达式
§4.1 运算表达式
运算表达式的基本形式
- $((表达式))
- $[表达式]
- expr 表达式,expression,expr 与表达式之间需要空格,表达式的运算符两边也要有空格
运算表达式中的基本运算符
- + 加
- - 减
- \* 乘,需要转义
- / 除
- % 取余
- ++/-- 自增/自减,只能在 for(()) 中使用
#!/bin/bash
# $((3+2)) == 5
# $[$((3+2))*2] == 5*2 == 10
# expr $[$((3+2))*2] \* 5 == 50
# expr 与表达式之间需要空格,这个表达式的运算符两边也要有空格
echo "`expr $[$((3+2))*2] \* 5`"
# 求两个入参之和
echo $[$1+$2]
§4.2 判断表达式
判断表达式的基本形式:
- [ 表达式 ]
- 表达式与 [] 之间必须带空格
- 关键字与 [] 之间必须带空格
- [[ 表达式 ]]
- 含义等同于 [ 表达式 ]
- 使用 == 比较字符串时有不同含义,详见下面基本逻辑运算符
- [ 表达式 ] && 语句1 || 语句2,三元运算符
基本逻辑运算符:
- 整数比较
- = 等于,[ 数字1 = 数字2 ],下同
- -lt 小于
- -le 小于等于
- -gt 大于
- -ge 大于等于
- -ne 不等于
- 字符串比较
- = 等于,[ 字符串1 = 字符串2 ]
- == 等于,基本和 = 相同,但有歧义,以 "$A" == a* 为例
- [ "$A" == a* ] 用于判断 A 是否就是字符串 "a*"
- [[ "$A" == a* ]] 用于判断 A 是否以 a 开头
- -n 非空,字符串长度不为 0
- -z 为空,字符串长度为 0
- 权限比较
- -r 有读权限,[ -r 文件或目录 ],下同
- -w 有写权限
- -x 有执行权限
- 文件比较
- -e 文件存在,[ -e 文件或目录 ],下同
- -f 文件存在,且是个文件
- -d 文件存在,且是个目录
- 参数比较
- if [ 参数 ] 参数不为空
- if [ ! -n "参数" ] 参数为空
#!/bin/bash
# 字符串比较,注意表达式两侧的空格
[ "aaa" = "aaa" ] && echo "true" || echo "false"
# 数字比较
[ 1 -ge 2 ] && echo "true" || echo "false"
# 权限比较
[ -r /shell/play ] && echo "true" || echo "false"
# 文件比较
[ -d /shell ] && echo "true" || echo "false"
§5 流程控制
按惯例分为:
- 顺序结构
- 选择结构
- 循环结构
§5.1 顺序结构(略)
不略没有天理 [手动捂脸]
§5.2 选择结构
§5.2.1 if
基本语法 -- 形式1,行数少,但细看反人类
# 形式1
if [ 判断表达式 ] ;then
代码1
else 代码2
fi
注意:
- if 和 [] 之间需要空格
- 表达式和 [] 之间需要空格
- if 和 fi 不能在同一行
- 没有 else 时,表达式1 不需要换行
#!/bin/bash
# 形式1
if [ $1 -gt $2 ];then echo "true"
else echo "false"
fi
基本语法 -- 形式2,省略分号,每个关键字都换行
# 形式2
if [ 判断表达式1 ]
then
代码1
elif [ 判断表达式2 ]
then
代码2
else
代码3
fi
#!/bin/bash
# 形式2
if [ $1 -gt $2 ]
then
echo "if"
elif [ $1 -lt $2 ]
then
echo "elif"
else
echo "else"
fi
§5.2.2 case
基本语法:
case $变量名 in
匹配值1)
代码1
;;
匹配值2)
代码2
;;
匹配值3)
代码3
;;
esac
说明:
- case 最后一个匹配值后的双分号(;;)可以不写
- 匹配值可以使用 "" 包裹,但省略不影响执行效果,但推荐带引号
- case 可以实现与参数的对比,而不是只能和常量对比
#!/bin/bash
# 字符串形式
case $1 in
"1")
echo "比字符串"
esac
# 数字形式
case $2 in
2)
echo "比数字"
esac
# 参数
case $1 in
"$2")
echo "最后一个判断后的 ;; 可以不写"
;;
$3)
echo "比参数"
esac
§5.3 循环结构
§5.3.1 for-in
基本语法:
for 变量 in 数组
do
代码
done
#!/bin/bash
# 省略式
echo "============无==========="
for i
do
echo $i
done
# 不带引号 $*,怀疑非法表达,解释为 省略式
echo "============\$*==========="
for i in $*
do
echo $i
done
# 不带引号 $@,怀疑非法表达,解释为 省略式
echo "============\$@==========="
for i in $@
do
echo $i
done
# 带引号 $*,参数列表作为整体字符串
echo "============\"\$*\"==========="
for i in "$*"
do
echo $i
done
# 带引号 $@,参数列表作为数组
echo "============\"\$@\"==========="
for i in "$@"
do
echo $i
done
§5.3.2 for(())
基本语法:
for((初始化;条件判断;变量判断))
do
代码
done
#!/bin/bash
for((i=0;i<10;i++))
do
SUM=$[ $SUM + $i ]
done
echo $SUM
§5.3.3 while
while [ 判断表达式 ]
do
代码
done
#!/bin/bash
SUM=0
i=0
while [ $i -le $1 ]
do
SUM=$[$SUM+$i]
i=$[$i+1]
done
echo $SUM
§5.4 控制台输入
命令:read 选项 变量
说明
选项说明:
- -p:等待输入时的提示符(提示语)
- -t:等待输入时的最大等待时间,单位秒
#!/bin/bash
# 不带时间
read -p "press some key: " INPUT
echo "input = $INPUT"
# 带时间输入
read -t 3 -p "press some key: " WAIT
if [ ! -n "$WAIT" ] ;then WAIT="null"
fi
echo "wait for $WAIT"
# 带时间不输入
read -t 3 -p "press some key: " WAIT
if [ ! -n "$WAIT" ] ;then
WAIT="null"
echo ""
fi
echo "wait for $WAIT"
§6 函数
§6.1 系统函数
§6.1.1 basename
指令:basename pathname suffix 截断前缀,相当于截取文件/目录名
说明:
- 会将 pathname 的最后一级(最后一个 / 右侧),作为字符串返回
- 若 pathname 是文件,只剩文件名
- 若 pathname 是目录,只剩最后一级目录名
- 可以指定 suffix,此时会额外将 suffix 的内容作为后缀截取,但若截取后是空,则忽略
#!/bin/bash
# 截取文件前缀
name=$(basename /sheell/beplaied.sh)
echo $name
# 截取文件前缀,若以 sh 结尾,则作为后缀截取
name=$(basename /sheell/beplaied.sh sh)
echo $name
# 截取目录前缀
name=$(basename /sheell)
echo $name
# 截取目录前缀
name=$(basename /sheell ll)
echo $name
§6.1.2 dirname
指令:dirname pathname 截取前缀,相当于截取文件/目录所在的上级目录路径
说明:
- 会将 pathname 的最后一级之前,作为字符串返回,相当于返回上级目录
- 可以指定 suffix,此时会额外将 suffix 的内容作为后缀截取,但若截取后是空,则忽略
#!/bin/bash
# 截取文件前缀
name=$(dirname /sheell/beplaied.sh)
echo $name
# 截取目录前缀
name=$(dirname /sheell)
echo $name
§6.2 自定义函数
§6.2.1 语法
格式:
# 声明
function function_name(){
代码
}
# 调用
function_name 参数...
说明:
- 声明函数时,不需要写形参
- 函数可以有返回值,但返回值必须是数字
- 执行函数后,可以用 $? 获取返回值,而不能用变量直接接收
- 执行函数并返回后,若需要使用返回值,则不可在使用前执行任何语句,否则 $? 会获取这个语句的执行结果
- 函数的返回值,宜用来表示函数执行的状态,即是否成功
- 函数的执行效果,宜在函数中输出,或赋予变量,并通过变量使用
# 返回字符参
function a(){
MSG="hahahaha: $1"
}
a $1
echo ${MSG}
# 返回数字
function a(){
return $[$1*2]
}
$(a $1)
echo $?
echo $?
$9 语法注意事项
空格总结
语法 | 款项 | 空格 |
---|---|---|
赋值 | = 两侧(SUM=$SUM+1) | 禁止 |
运算表达式 | expr 与 表达式之间(expr 1 + 1) | 必带 |
运算表达式 | expr 的表达式中运算符两侧(expr 1 + 1) | 必带 |
判断表达式 | 表达式与 [] 之间([ 1 -le 2 ]) | 必带 |
判断表达式 | 关键字与 [] 之间(if [ $i -le 10 ]) | 必带 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)