# Shell脚本编程精讲笔记前六章 # 目录 - 第一章Shell 介绍 - 第二章变量 - 第三章条件测试 - 第四章流程控制之条件判断 - 第五章流程控制之循环 - 第六章函数和数组 --- # 第一章Shell 介绍 ## 1.1 基本概念 - Shell 是用户与 Linux 内核之间的桥梁命令解释器 - 常见 Shellsh、bashGNU Bourne-Again Shell最常用、csh、tcsh、ksh - Shell 同时也是一门解释型脚本语言无需编译直接运行 ## 1.2 常用查看指令 bash # 查看当前系统支持的 shell cat /etc/shells # 查看当前默认 shell echo $SHELL --- ## 1.3 脚本书写规范 | 要求 | 说明 | |------|------| | 文件命名 | 建议以 .sh 结尾见名知意 | | 首行声明 | #!/bin/bash | | 注释 | 以 # 开头建议英文注释 | | 多行注释 | 使用 :EOF ... EOF | **多行注释写法** bash :EOF 这里是注释内容 可以写多行 EOF --- ## 1.4 输出命令echo 与 printf ### echo 常用选项 bash # 基本输出 echo hello world # -n不换行输出 echo -n hello world # -e启用转义字符彩色输出 # 格式echo -e \e[字体控制;颜色 内容 \e[0m # 字体控制1高亮, 4下划线, 5闪烁 # 字颜色30-37背景色40-47 echo -e \e[1;31m红色字\e[0m echo -e \e[4;45;37m紫底白字\e[0m ### printf 格式化输出 bash printf hello world\n # 格式化占位符 # %s 字符串, %d 整数, %f 浮点数 printf %10s %-3d %-4.2f\n zhangsan 9 130.23 # 正数表示右对齐负数-表示左对齐 --- ## 1.5 连接多条命令 | 符号 | 作用 | |------|------| | ; | 顺序执行不管前面是否成功 | | | 前面成功才执行后面 | | \|\| | 前面失败才执行后面 | | \| | 管道左边输出作为右边输入 | bash date ; ls -l /etc/passwd mkdir /mnt/iso mount /dev/sr0 /mnt/iso mkdir tt || ls / free -h | grep Mem | tr -s | cut -d -f4 --- ## 1.6 执行 Shell 脚本的四种方式 bash # 方式1绝对/相对路径需要 x 执行权限启动子shell chmod ax /shell/test.sh /shell/test.sh # 方式2bash 命令不需要执行权限启动子shell bash /shell/test.sh # 方式3source在当前 shell 执行脚本中的变量会保留 source /shell/test.sh # 方式4.等同于 source . /shell/test.sh ⚠️ 注意路径方式和 bash 方式启动子 shell脚本内 cd 不会改变当前目录 source 和 . 方式在当前 shell 执行cd 会真正改变当前目录。 --- ## 1.7 退出状态码 bash # 查看上一条命令的退出状态码 echo $? # 常见状态码 # 0 → 命令执行成功 # 1 → 未知错误 # 2 → 误用 shell 命令 # 126 → 命令不可执行 # 127 → 命令未找到 # 128x → Linux 信号 x 的严重错误 # 130 → CtrlC 终止 # 255 → 退出状态码越界 **脚本中指定退出码** bash #!/bin/bash echo hello world exit 1 # 使用 exit 指定退出码范围 0-255 --- ## 【实验1-1】创建并运行第一个脚本 bash mkdir /shell vim /shell/test.sh # 内容 #!/bin/bash echo hello world bash /shell/test.sh # hello world chmod ax /shell/test.sh /shell/test.sh # hello world --- # 第二章变量 ## 2.1 变量定义规则 - 变量名只允许数字、字母、下划线**不能以数字开头** - 赋值等号左右 **不能有空格**如 varvalue - 含空格的值必须用引号括起来 - Shell 是弱类型语言变量统一以字符串存储 ## 2.2 定义变量的三种方式 bash # 方式1直接赋值 var1test # 方式2命令替换将命令输出赋给变量 var2date var2$(date) # 方式3交互式输入read read -p 请输入变量值 var3 var4 --- ## 2.3 位置参数变量特殊变量 | 变量 | 含义 | |------|------| | $0 | 脚本文件名 | | $1~$9 | 第1至9个参数 | | ${10} | 第10个及以上参数需加花括号 | | $* | 所有参数当成一个整体 | | $ | 所有参数每个作为独立字段 | | $# | 参数个数 | | $$ | 当前进程的PID | | $? | 上一条命令的退出状态码 | bash # 示例脚本 test2.sh #!/bin/bash echo 第1个参数: $1 echo 第2个参数: $2 echo 所有参数: $* echo 参数个数: $# echo 进程PID: $$ bash test2.sh 10 2 3 679 9 **取消变量** bash unset var1 var2 --- ## 2.4 变量使用 ### 引号的作用重要 | 符号 | 作用 | |------|------| | 单引号 | 所有字符失去特殊含义纯字符串 | | 双引号 | 除 \ 、 、 $ 外其余字符失去特殊含义 | | 反斜线 \ | 使紧跟的字符失去特殊含义转义 | | 反单引号 | 内部字符作为命令执行 | | $ | 读取变量值 | ### 输出变量 bash varhello echo $var echo ${var} echo ${var}1 # 花括号用于分隔变量名与后续字符 # 查看所有变量 set # 所有变量 declare # 所有变量和函数 env # 所有环境变量全局变量 --- ## 2.5 整数运算 bash var11; var2 # 以下方法均可进行整数运算 echo $((var1var)) echo $[var1var] let var3var1var; echo $var3 expr $var1 $var echo $var1$var | bc # 只有 bc 和 awk 可以做小数运算 echo 11.2 | bc # 2.2 awk BEGIN {a1;b1.2;print ab} # 2.2 --- ## 2.6 字符串操作 bash filenamefile.tar.gz ${#filename} # 字符串长度11 ${filename:1:3} # 切割从第1位取3个字符 → ile ${filename/./-} # 替换第一个 . 为 - → file-tar.gz ${filename//./-} # 替换所有 . 为 - → file-tar-gz ${filename#*.} # 删除从左到第一个.前的内容 → tar.gz ${filename##*.} # 删除从左到最后一个.前的内容 → gz ${filename%.*} # 删除从右到第一个.后的内容 → file.tar ${filename%%.*} # 删除从右到最后一个.后的内容 → file --- ## 2.7 变量默认值重要扩展语法 | 语法 | 未定义/为空时 | 有值时 | 是否修改var | |------|--------------|--------|------------| | ${var:-word} | 返回 word | 返回原值 | 否 | | ${var:word} | 返回 word并将 var 赋为 word | 返回原值 | 是 | | ${var:word} | 返回空 | 返回 word | 否 | | ${var:?word} | 打印 word 报错并退出 | 返回原值 | 否 | bash echo ${port:-80} # port未定义则输出80 echo ${log_dir:/var/log/mylog} # 未定义则赋值并输出 echo ${info:test} # info有值则返回test echo ${password:?error} # 未定义则报错退出 --- ## 2.8 变量作用域 - **局部变量**当前 shell 或函数中有效 - **全局变量环境变量**当前 shell 及子进程中有效用 export 声明 bash # 局部变量子进程无法使用 namexiaoming bash echo $name # 空 # 环境变量子进程可以使用 export namexiaoming bash echo $name # xiaoming # 函数中的局部变量 local v1200 **常用内置环境变量** | 变量 | 说明 | |------|------| | PATH | 命令搜索路径 | | HOME | 用户主目录 | | SHELL | 当前 shell 路径 | | PWD | 当前工作目录 | | HISTFILE | 历史命令文件 | | LOGNAME | 当前登录名 | --- ## 【实验2-1】计算两个变量的四则运算 bash vim /shell/chap02/test1.sh #!/bin/bash a$1; b$2 echo ab$(($a$b)) echo a-b$((a-b)) echo a*b$((a*b)) echo a/b$((a/b)) echo a%b$((a%b)) bash /shell/chap02/test1.sh 10 3 # ab13, a-b7, a*b30, a/b3, a%b1 ## 【实验2-2】计算1到100的和 bash # 方法1{} 展开 tr bc echo {1..100} | tr | bc # 5050 # 方法2seq seq -s 100 | bc # 5050 # 奇数和 echo {1..100..2} | tr | bc # 2500 seq -s 1 2 100 | bc # 2500 ## 【实验2-3】批量修改文件名去掉 _finish bash mkdir /shell/dir1 touch /shell/dir1/open_{1..5}_finish.jpg touch /shell/dir1/open_{1..5}_finish.png vim /shell/chap02/test2.sh #!/bin/bash for file in $(ls /shell/dir1) do old_file/shell/dir1/$file new_file${old_file/_finish/} # 字符串替换 mv $old_file $new_file done bash /shell/chap02/test2.sh ls /shell/dir1/ # open_1.jpg open_2.jpg ... --- # 第三章条件测试 ## 3.1 条件测试语法 bash 格式1test -选项 条件表达式 格式2[ 条件表达式 ] # 注意[] 左右要有空格 格式3[[ 条件表达式 ]] # 注意[[]] 左右要有空格 格式4(( 条件表达式 )) # 只能测试整数 条件为真 → 返回 0条件为假 → 返回非0 --- ## 3.2 文件测试表达式 | 操作符 | 说明 | |--------|------| | -a/-e 文件 | 文件是否存在 | | -f 文件 | 存在且为普通文件 | | -d 文件 | 存在且为目录 | | -L 文件 | 存在且为链接文件 | | -b 文件 | 存在且为块文件 | | -c 文件 | 存在且为字符文件 | | -s 文件 | 存在且大小不为0 | | -r 文件 | 存在且可读 | | -w 文件 | 存在且可写 | | -x 文件 | 存在且可执行 | | -u 文件 | 存在且设置了suid位 | | f1 -nt f2 | f1 比 f2 新修改时间 | | f1 -ot f2 | f1 比 f2 旧修改时间 | bash [ -d /root ] echo y || echo n [ -e /root/file ] echo 已存在 || touch /root/file --- ## 3.3 字符串测试表达式 | 操作符 | 说明 | |--------|------| | -n 字符串 | 长度不为0则为真 | | -z 字符串 | 长度为0则为真 | | 串1 串2 | 字符串相等则为真可用 | | 串1 ! 串2 | 字符串不等则为真 | bash test -n abc; echo $? # 0非空真 test -n ; echo $? # 1空假 test abc abcd; echo $? # 1不等假 ⚠️ 建议用双引号引起变量避免变量为空时报错 --- ## 3.4 整数测试表达式 | [] / test 写法 | (()) / [[]] 写法 | 说明 | |----------------|-----------------|------| | -eq | 或 | 相等 | | -ne | ! | 不相等 | | -gt | | 大于 | | -ge | | 大于等于 | | -lt | | 小于 | | -le | | 小于等于 | bash [ 5 -gt 3 ] echo y || echo n (( 5 3 )) echo y || echo n # [[]] 特殊用法~ 正则匹配 n123 [[ $n ~ ^[0-9]$ ]] echo 是数字 || echo 不是数字 --- ## 3.5 逻辑操作符 | [] / test | [[]] / (()) | 说明 | |-----------|-------------|------| | -a | | 与AND | | -o | \|\| | 或OR | | ! | ! | 非NOT | bash [ -f /etc/passwd -a abc abc ]; echo $? # 0真 [[ -f /etc/passwd abc abc ]]; echo $? # 0真 ! [ 3 -eq 3 ]; echo $? # 1假取反变假 --- ## 【实验3-1】文件类型和权限判断脚本 bash vim /shell/chap03/test1.sh #!/bin/bash read -p input a filename: filename test -z $filename echo you must input a filename exit 0 test ! -e $filename echo the file do not exist exit 0 test -f $filename filetyperegular file test -d $filename filetypedirectory test -r $filename permreadable test -w $filename perm$perm writable test -x $filename perm$perm executable echo the $filename is a $filetype echo and the permissions are: $perm bash /shell/chap03/test1.sh # 输入/root # 输出the /root is a directory # and the permissions are: readable writable executable ## 【实验3-2】计算输入两数之和带数字校验 bash vim /shell/chap03/compute.sh #!/bin/bash read -p 请输入x的值 x read -p 请输入y的值 y if [ -n $x -a -n $y ] then if [[ $x ~ ^[0-9]$ ]] [[ $y ~ ^[0-9]$ ]] then echo $x$y$[xy] else echo 请输入数字 fi else echo 请输入有效内容 fi --- # 第四章流程控制之条件判断 ## 4.1 if 三种结构 ### 单分支 bash if 条件表达式 then 指令 fi ### 双分支 bash if 条件表达式 then 指令序列1 else 指令序列2 fi ### 多分支 bash if 条件1 then 命令1 elif 条件2 then 命令2 elif 条件3 then 命令3 else 命令n fi --- ## 4.2 case 多条件判断 bash case 变量名 in 值1) 指令1 ;; 值2) 指令2 ;; *) 默认指令 esac - 遇到 ;; 跳出 case - * 匹配所有未匹配的情况 --- ## 【实验4-1】判断执行脚本的用户是否为 root bash vim /shell/chap04/choice1.sh #!/bin/bash if [ $USER ! root ] then echo please switch user root fi **检查当前账户的四种方法** bash whoami # 输出用户名 echo $USER # 输出用户名 id -u # 输出 UIDroot为0 echo $UID # 输出 UID ## 【实验4-2】输出两个整数的最大值 bash vim /shell/chap04/choice2.sh #!/bin/bash read -p 请输入第一个整数: x read -p 请输入第二个整数: y max$x if (($maxy)) then max$y fi echo 最大值$max ## 【实验4-3】内存告警脚本 bash vim /shell/chap04/choice3.sh #!/bin/bash free_mem$(free -m | grep Mem | tr -s | cut -d -f4) if [ $free_mem -lt 100 ] then echo 警告剩余内存${free_mem}MB低于警戒线100MB else echo 剩余内存${free_mem}MB空间足够 fi # free -m以MB显示 | grep Mem取Mem行 | tr -s 压缩空格 | cut -d -f4取第4列 ## 【实验4-4】判断 sshd 是否运行 bash vim /shell/chap04/choice4.sh #!/bin/bash num$(ps -ef | grep sshd | grep -v grep | wc -l) if [ $num -gt 0 ] then echo sshd is running else echo sshd is not running fi # 或者通过端口判断 netstat -lntup | grep 22 | wc -l ## 【实验4-5】ping 测试主机是否存活 bash vim /shell/chap04/choice5.sh #!/bin/bash read -p 请输入测试主机的IP地址 ip ping -c 2 -w 3 $ip /dev/null # -c 2发送2个包-w 3等待3秒注意不能等1秒可能包未发完就结束 if [ $? -eq 0 ] then echo 主机$ip 已运行 else echo 主机$ip 未运行 fi ## 【实验4-6】判断CPU生产商 bash vim /shell/chap04/choice6.sh #!/bin/bash vendor$(grep vendor_id /proc/cpuinfo | uniq | cut -d -f2) if [ $vendor GenuineIntel ]; then echo Intel elif [ $vendor AuthenticAMD ]; then echo AMD else echo other fi ## 【实验4-7】判断输入的成绩等级if 版 bash vim /shell/chap04/choice10.sh #!/bin/bash read -p please input your score: score if [ -z $score ];then echo you must input your score; exit 1 fi if [ $score -lt 0 -o $score -gt 100 ];then echo invalid score; exit 2 fi if [ $score -ge 85 ];then echo A elif [ $score -ge 70 ];then echo B elif [ $score -ge 60 ];then echo C else echo D fi ## 【实验4-8】判断成绩等级case 版 bash vim /shell/chap04/choice11.sh #!/bin/bash read -p please enter your score (0-100): grade case $grade in 8[5-9]|9[0-9]|100) echo A ;; 7[0-9]|8[0-4]) echo B ;; 6[0-9]) echo C ;; *) echo D esac ## 【实验4-9】判断今天是工作日还是休息日 bash vim /shell/chap04/choice12.sh #!/bin/bash adate %w # %w0-60为周日 case $a in [1-5]) echo 今天是工作日社畜没有休息日哦。;; [067]) echo 今天是休息日请好好休息哦~;; esac --- # 第五章流程控制之循环 ## 5.1 for 循环 ### 三种格式 **格式1带列表** bash for var in {1..10} # 1到10 for var in {1..10..2} # 1到10步长2奇数 for var in value1 value2 # 直接列举 for var in command # 命令输出作为列表 do command done **格式2不带列表从命令行获取** bash for var do command done # 等同于for var in $ **格式3C语言风格** bash for ((i1; i30; i)) do command done --- ## 【实验5-1】遍历 IP 地址 bash vim /shell/chap05/for1.sh #!/bin/bash for ip in 192.168.168.{10..20..2} do echo $ip done # 输出192.168.168.10 12 14 16 18 20 ## 【实验5-2】计算100以内奇数之和 bash vim /shell/chap05/for2.sh #!/bin/bash sum0 for i in {1..100..2} do sum$[$sum$i] done echo sum$sum # sum2500 ## 【实验5-3】批量创建用户 bash vim /shell/chap05/for5.sh #!/bin/bash for ((i1; i10; i)) do if id test$i /dev/null then echo test$i 已存在 else useradd test$i echo 123456 | passwd --stdin test$i /dev/null fi done --- ## 5.2 while 循环 bash # 格式1条件为真则循环 while expression do command done # 死循环写法1 while true do command done # 死循环写法2 while : do command done **while 读取文件三种写法** bash # 写法1exec 重定向 exec file while read a; do echo $a done # 写法2管道 cat file | while read a; do echo $a done # 写法3输入重定向 while read a; do echo $a done file --- ## 【实验5-4】猜商品价格游戏 bash vim /shell/chap05/while2.sh #!/bin/bash PRICE$((RANDOM%20)) # RANDOM产生[1,32767]的整数%20 控制在0-19 count0 while true do read -p 请输入[1-20]间的商品价格: INT let count if [ $INT -eq $PRICE ]; then echo 恭喜你猜对了共猜了 $count 次 break elif [ $INT -gt $PRICE ]; then echo 价格高了. else echo 价格低了. fi done ## 【实验5-5】批量 ping 测试 bash vim /shell/chap05/while3.sh #!/bin/bash FILE/shell/chap05/ip.txt cat $FILE | while read line do echo 正在检测$line ping -c 2 -w 3 $line /dev/null if [ $? -eq 0 ]; then echo $line 可以连通 else echo $line 无法连通 fi echo ------------------------- done --- ## 5.3 until 循环 bash # 条件不成立则执行条件成立则退出与 while 相反 until [ $i -gt 10 ] do command let i done --- ## 5.4 select 循环创建菜单 bash # select 会显示带编号的菜单等待用户输入选择 select 变量名 in 菜单列表 do statement done bash vim /shell/chap05/select2.sh #!/bin/bash PS3Choose your favorite fruit: # 自定义提示符 select fruit in Apples Bananas Pears Watermelons Grapes do echo Your favorite fruit is $fruit. break done --- ## 5.5 循环控制语句 | 命令 | 说明 | |------|------| | break | 立即跳出当前循环break N 可跳出N层 | | continue | 跳过本次迭代进入下一次continue N 跳到第N层 | | exit | 退出整个脚本exit N 指定退出码 | bash # break 跳出多层循环 if [ $k -eq 222 ]; then break 2 # 跳出2层循环 fi # continue 跳过当前层继续上一层 if [ $k -eq 222 ]; then continue 2 # 跳到第2层循环的下一次迭代 fi # exit 退出整个脚本 if [ $k -eq 222 ]; then exit 2 # 退出脚本状态码为2 fi --- # 第六章函数和数组 ## 6.1 函数 ### 函数定义三种写法 bash # 完整写法 function 函数名() { 指令 return } # 简化1 function 函数名 { 指令 } # 简化2最常用 函数名() { 指令 } ### 函数调用与参数 bash # 在同一文件中直接调用可带参数 函数名 [参数1] [参数2] # 在其他文件中调用先加载再调用 source /shell/func.sh 函数名 [参数] 函数内用 $1、$2 等获取传入参数$# 获取参数个数 ### 函数返回值 bash # 返回整数0-255 return 0 # 返回字符串用 echo 输出外部用 $() 接收 result$(函数名 参数) ### 变量作用域 bash # 函数内未用 local 的变量是全局变量 # 用 local 定义的变量仅在函数内有效 function func1() { local v1200 # 局部变量 v2global # 全局变量 } --- ## 【实验6-1】函数实现加减法运算 bash vim /shell/chap06/func1.sh #!/bin/bash add() { sum$[$1$2] echo $sum } read -p input first number: first read -p input second number: second add $first $second ## 【实验6-2】判断单词长度是否 5 bash vim /shell/chap06/func3.sh #!/bin/bash length() { word$1 if [ ${#word} -ge 5 ]; then echo $word: length ${#word} fi } for line in $(cat /shell/chap06/file) do length $line done ## 【实验6-3】函数判断主机存活含 return 值 bash vim /shell/chap06/func4.sh #!/bin/bash online() { if ping -c 2 -w 3 $1 /dev/null; then return 0 else return 1 fi } for i in {1..10} do online 192.168.168.$i if [ $? -eq 0 ]; then echo 192.168.168.$i up else echo 192.168.168.$i down fi done ## 【实验6-4】调用函数库文件 bash # 函数库文件 add.sh #!/bin/bash add() { sum$[$1$2]; echo $sum; } # 主脚本 func.sh source /shell/chap06/add.sh . /shell/chap06/div.sh read -p input first: first read -p input second: second echo $first$second$(add $first $second) ## 【实验6-5】递归函数阶乘 bash vim /shell/chap06/func7.sh #!/bin/bash fact() { local n$1 if [ $n -eq 0 -o $n -eq 1 ]; then final1 else let m$n-1 result$(fact $m) let final$n*$result fi echo $final } read -p please input a number: num echo $num factorial : $(fact $num) # 输入6 → 输出6 factorial : 720 --- ## 6.2 数组 ### 数组定义五种方式 bash # 方式1小括号列举 array(value1 value2 value3) # 方式2键值对指定下标 array([0]one [1]two [2]three) # 方式3逐个赋值 array[0]a; array[1]b; array[2]c # 方式4命令输出 array(ls /test) array$(ls /test) # 方式5定义空数组 declare -a array # 普通数组整数下标 declare -A array # 关联数组字符串下标 # 删除数组 unset array ### 数组操作 bash arr(apple banana grape peach) ${arr[*]} # 所有元素apple banana grape peach ${arr[]} # 同上 ${!arr[*]} # 所有下标0 1 2 3 ${#arr[*]} # 元素个数4 ${arr[2]} # 读取某个元素grape arr[4]cherry # 添加元素 arr[6]fruit arr[6]pineapple # 修改元素 unset arr[6] # 删除元素 ### 循环遍历数组 bash # 方式1C 语言风格 for for ((i0; i${#ip[*]}; i)) do echo ${ip[$i]} done # 方式2直接遍历 for ip in ${ip[*]} do echo $ip done --- ## 【实验6-6】读取输入存入数组 bash vim /shell/chap06/array2.sh #!/bin/bash i0; n3 while [ $i -lt $n ] do read -p Please input strings... $[$i1]: array[$i] let i done echo ${array[*]} ## 【实验6-7】将字符串逐个字符存入数组 bash vim /shell/chap06/array3.sh #!/bin/bash charsaAbcdeFg for ((i0; i${#chars}; i)); do array[$i]${chars:$i:1} done echo ${array[*]} # a A b c d e F g ## 【实验6-8】关联数组统计 IP 出现次数 bash vim /shell/chap06/array4.sh #!/bin/bash declare -A arr for ip in $(cat /shell/chap06/ip.txt) do let arr[$ip]arr[$ip]1 done for ip in ${!arr[*]} do echo $ip: ${arr[$ip]} done --- # 附录常用快速参考手册 ## 特殊变量汇总 | 变量 | 含义 | |------|------| | $0 | 脚本名 | | $1~$9 | 位置参数 | | $# | 参数个数 | | $* | 所有参数整体 | | $ | 所有参数独立 | | $? | 上条命令状态码 | | $$ | 当前进程PID | | $RANDOM | 随机数[1,32767] | ## 字符串操作速查 | 语法 | 说明 | |------|------| | ${#var} | 字符串长度 | | ${var:n:m} | 从第n位取m个字符 | | ${var/old/new} | 替换第一个 old | | ${var//old/new} | 替换所有 old | | ${var#pattern} | 从左删除最短匹配 | | ${var##pattern} | 从左删除最长匹配 | | ${var%pattern} | 从右删除最短匹配 | | ${var%%pattern} | 从右删除最长匹配 | ## 算术运算速查 | 方式 | 示例 | |------|------| | $((...)) | echo $((12)) | | $[...] | echo $[12] | | let | let a12 | | expr | expr 1 2 | | bc | echo 12 \| bc | | awk | awk BEGIN{print 12} |