《Shell 大道:筑基篇(上)—— 神念控流程,函数成符阵》
文章目录
- 前言
- 一、流程控制
- 1.1 `if …else`语句
- 1.1.1 单支
- 1.1.2 双支
- 1.1.3 多支
- 1.1.4 拓展案例
- 1.2 `for` 循环
- 1.3 `while` 循环
- 1.4 `case` 语句
- 二、函数
- 2.1 定义与调用
- 2.2 参数与返回值
- 2.3 函数的高级用法
- 2.3.1 函数的嵌套调用
- 2.3.2 函数参数的默认值设置
- 2.3.3 传递数组作为参数
- 三、数组
- 3.1 定义与访问
- 3.2 遍历数组
- 四、文件包含source
- 4.1 语法
- 4.2 示例
- 4.3 实际环境用法
- 总结
前言
【筑基初阶,符阵奠基】
道友既已练气圆满,气海充盈,当褪去 “单一气劲(命令)” 的练气桎梏,迈入以神念(逻辑)驭术的筑基之境。练气期,汝借终端辟紫府、凭变量存真气,仅能完成基础灵气调度;而筑基初阶,需学会以 “流程控制” 为脉络,以 “函数” 为符篆,将零散气劲编织成环环相扣的符阵(脚本)。
本篇将传汝两大筑基核心秘术:其一为 “流程控制” 心法,教汝以if-else断符阵走向、for/while驱灵气循环、case辨参数分支,让符阵依序而行、不偏不倚;其二为 “函数化符” 之法,可将重复术法凝为一枚专属符篆,召之即来、用之即去,大幅精简符阵结构。此二术乃筑基根基,练至大成,可让汝初步构建有序、高效的符阵,为后续 “灵气流转” 深造打下基础。
一、流程控制
1.1 if …else
语句
1.1.1 单支
if 命令; then命令..
fi[ -d /etc ] && [ -r /etc ] && echo "true"
#因为&&先执行其前面的,如果前面正确运行即为1,则执行后面。
#所以&&可以当做单支if的一种写法
执行机制:判断一次,仅有一个结果
- 结果为true,执行命令
- 结果为flase,不执行命令
在 Shell 中,if
后面跟的命令必然会被执行,这是由 Shell 的语法规则决定的。
if
语句的工作原理是:
- 执行其后的命令
- 根据命令的退出状态码(0 表示成功,非 0 表示失败)来决定执行哪个分支
这就是为什么 if
后面的命令总会被执行 —— Shell 需要通过执行命令来获取其退出状态码,才能进行条件判断。
1.1.2 双支
if 命令; then# 命令执行成功(退出状态码为0)时执行的操作
else# 命令执行失败(退出状态码非0)时执行的操作
fi
shell中的命令状态码“0”为真,可以执行下一步判定。
#!bin/bash
if [ $UID -eq 0 ]; thenecho "当前登录用户是管理员"
elseecho "当前登录用户不是管理员"
fi
在if后跟命令,将命令的输出结果
#!/bin/bash
read -p "输入目录" dir
if ls $dir &> /dev/null; then
echo "有目录"
else
echo "无目录"
fi
1.1.3 多支
#!/bin/bash
read -e -p "输入文件路径:" file
if [ -e $file ]; thenif [ -d $file ]; thenecho "路径指向的是目录"elif [ -f $file ]; thenecho "路径指向的是文件"elif [ -b $file ]; thenecho "路径指向的是块设备文件"elseecho "啥也不是"fi
elseecho "不是有效路径"
fi
1.1.4 拓展案例
自动安装httpd服务
#!/bin/bash
if netstat -antulp | grep ":80" &> /dev/null; thenecho "web服务已运行!"
elseif rpm -qa httpd &> /dev/null; thenecho "未安装httpd正在安装..."yum install -y httpd &> /dev/nullif [ $? -eq 0 ]; thensystemctl start httpdsystemctl enable httpd &> /dev/nullecho "已安装完成httpd服务,并启动和开机自启httpd"elseecho "安装httpd服务失败,请稍后自行安装..."fielseecho "正在启动http服务..."systemctl restart httpdecho "http服务已启动"fi
fi
自动获取并判断时间段
#!/bin/bash
#1.获取时间变量
time=$(date +%H)
#2.判断早上时间段
if (( time >= 6 && time <= 10 ));thenecho "早上$time 点"
#3.判断中午时间段
elif (( time >= 10 && time <= 14 ));thenecho "中午$time 点"#4.判断下午时间段
elif (( time >= 14 && time <= 18 ));thenecho "下午$time 点"#5.判断晚上时间段
elif (( time >= 18 && time <= 24 ));thenecho "晚上$time 点"
elseecho "凌晨$time 点"
fi
1.2 for
循环
for 变量名 in 列表; do# 循环体:对每个变量值执行的操作命令1命令2...
done
- 变量名:用于临时存储列表中的每个元素(通常用i、item等)
- 列表:要遍历的值(可以是直接列出的值、文件名、命令输出等)
do...done
:包裹循环执行的命令
#C 语言风格的 for 循环
#!/bin/bash
for ((i=0; i<5; i++)); doecho $i
done
#遍历直接指定的列表
#!/bin/bash
for animal in cat dog elephant; doecho $animal
done
#for循环中进行操作
#!/bin/bash
count=0
for((i=1;i<=10;i++))
do
count=$(($count+$i))
done
echo "1加到10的结果为" $count
#!/bin/bashread -p "请输入你要探测的网段(格式如192.168.10):" network
#定义网段
#network=192.168.10echo "开始扫描${network}.0/24网段.."
echo "=============================="
>/root/ip1.txt
>/root/ip2.txtfor((i=0;i<255;i++));do{IP=${network}.${i}if ping -c 2 -i 0.2 -W 2 $IP &>/dev/null;thenecho "$IP ping通"echo $IP >> /root/ip1.txtelseecho "$IP ping不通"echo $IP >> /root/ip2.txtfi}&if ((i % 30 == 0 ));thenwaitfi
done
#后台进程(子进程)无法修改父进程的变量,当ping命令被放入{...}&内送至后台时无法再修改外部定义的计数变量,使用折中的方法,在外部生成一个可读写的临时文件,进行计数
suc=$(cat /root/ip1.txt | wc -l)
fai=$(cat /root/ip2.txt | wc -l)
wait
echo "==========================="
echo "扫描完成,共成功$suc 条,已将正确连通ip存储在/root/ip1.txt"
echo "扫描完成,共成功$fai 条,已将错误连通ip存储在/root/ip2.txt"
自动生成15位密码
#!/bin/bash
key="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
num=${#key}
for i in {1..15}
doindex=$[RANDOM%num]pass=$pass${key:$index:1}
doneecho "随机密码:$pass"
统计目录下的.log文件
#!/bin/bash
dir=/var/log
sum=0for i in $(ls -r "$dir"/*.log)
doif [ -f "$i" ];thenlet sum++echo "filename:$i"fi
done
echo "filenum is $sum"
1.3 while
循环
在 Shell 脚本中,while循环是一种常用的控制结构,用于重复执行一段代码,直到指定的条件变为假(非零退出状态)。
while [ 条件表达式 ]; do# 循环体:需要重复执行的命令
done
- 基础循环
输入对应字符后退出
#!/bin/bash
count=0
while [ "$y" != "yes" -a "$y" != "YES" ]
doecho "请输入yes/YES"read yif (( count < 10 ));theny=""echo "yes都不会打?"((count++))fi
done
echo "你做到了!"
- 类似for循环的用法
定义一个值,用值得大小作为循环结束的条件
#!/bin/bash
#while循环的第二种方式:计算10以内数值的累加
count=0 # 记录累加值结果
num=1 # 循环初始值
while((num<=10))
do
#计算累加值的和count=$[$count+$num]
#((num++))
#使用let命令,计算变量++let num++ # 修改循环条件中的值
done
echo "10以内数值的累加和:${count}";
- 死循环
#!/bin/bash
while true
doecho "请输入字符串:"read yif [ "$y" == "yes" ]; then#以状态码0进行退出,可以使用“#?”在外部获取状态码exit 0fi
done
不要使用下面的方法来自启linux系统中的文件,这会导致系统的额外开销,如果需要自动重启,systemd
可以配置。
#!/bin/bash
while true
dowhile ps -aux | grep httpd | grep -v grep &> /dev/nulldosleep 10donesystemctl restart httpdecho "httpd关闭,已重启..."
done
1.4 case
语句
在 Shell 脚本中,case 语句用于多分支条件判断,类似于其他编程语言中的 switch-case 结构,适合处理多个固定值的条件判断场景。
case 变量 in模式1)命令1;;模式2)命令2;;模式3|模式4) # 多个模式用 | 分隔(逻辑或)命令3;;*) # 默认匹配,匹配所有未被前面模式匹配的情况默认命令;;
esac
-
关键说明
- 变量:通常是一个字符串或变量值,会与后续的模式进行匹配模式
- 可以是具体的字符串(如 “start”、“stop”)
- 可以包含通配符(* 匹配任意字符,? 匹配单个字符,[abc] 匹配括号中的任意一个字符)
- 多个模式可用 | 分隔,表示 “或者” 的关系
- 终止符:每个模式对应的命令块必须以 ;; 结束
- 默认匹配:*) 用于匹配所有未被前面模式匹配的情况,类似 default
-
示例
#!/bin/bash
case $1 in"start")systemctl start httpdecho "启动http服务";;"stop")systemctl stop httpdecho "停止http服务";;"restart")systemctl stop httpdsystemctl start httpdecho "重启http服务";;*)echo "无效参数";;
esac
#!/bin/bash
read -p "选择要查看的硬件的资源使用情况 1 cup 2 磁盘 3 内存 4退出" numcase $num in
"1")echo "正在查看cpu使用情况................."#-b批量处理模式:不以交互模式处理#n1:只在top上运行一次,生成一次结果集#top -bn1 | grep "Cpu(s)"
;;"2")echo "正在查看磁盘使用情况................."df -h
;;"3")echo "正在查看内存使用情况................."free -h
;;"4")echo "退出程序"exit 0
;;*)echo "输入无效"
;;
esac
二、函数
2.1 定义与调用
在 Shell 脚本中,函数(Function) 是一段可重复调用的代码块,用于封装特定功能、简化脚本结构、提高代码复用性和可维护性。它类似其他编程语言中的 “子程序” 或 “方法”,能让脚本逻辑更清晰,避免重复编写相同代码。
function 函数名字 ()
{
程序段;
[return int;]
}
基础示例:
#!/bin/bashsayHello()
{echo "你好"echo "Hello"
}sayHello
2.2 参数与返回值
在 Shell 中,调用函数时可以向其传递参数。在函数体内部, 通过 $n
的形式来获取参数的值,例如, $1
表示第一个参数, $2
表示第二个参数…注意, 当 n>=10时,需要使用${n}
来获取参数。
而返回值,可以使用$()
来获取函数返回的输出字符串,或者用$?
来获得return的状态码。
示例:
read -p "number1:" num1
read -p "number2:" num2sum1(){local sum=$(($1+$2))echo $sumreturn 0
}sum2(){local sum=$(($1+$2))echo "sum:$sum"return 1
}sum3(){local sum=$(($1+$2))echo "wwww"return 2
}
sum4(){echo $(($1+$2))return 3
}
sum1=$(sum1 $num1 num2)
sumR1=$?
sum2=$(sum2 $num1 num2)
sumR2=$?
sum3=$(sum3 $num1 num2)
sumR3=$?
sum4=$(sum4 $num1 num2)
sumR4=$?
echo "$sum1:$sumR1"
echo "$sum2:$sumR2"
echo "$sum3:$sumR3"
echo "$sum4:$sumR4"
2.3 函数的高级用法
2.3.1 函数的嵌套调用
Shell 函数支持嵌套(函数内部调用其他函数,甚至自身,即递归)。
#!/bin/bash# 递归函数:计算 n 的阶乘(n! = n * (n-1) * ... * 1)
factorial() {local n=$1if [ $n -eq 1 ]; thenecho 1 # 基线条件:1! = 1else# 递归调用:n! = n * (n-1)!local prev=$(factorial $((n-1)))echo $((n * prev))fi
}# 调用递归函数
result=$(factorial 5)
echo "5! = $result" # 输出 120
2.3.2 函数参数的默认值设置
Shell 函数不直接支持 “默认参数”,但可通过判断参数是否为空来模拟:
#!/bin/bash# 函数:输出问候语,默认问候 "Guest"
greet() {# 若 $1 为空,则使用默认值 "Guest"local name=${1:-"Guest"}echo "Hello, $name!"
}# 调用函数(无参数,使用默认值)
greet # 输出 "Hello, Guest!"# 调用函数(传递参数)
greet "Alice" # 输出 "Hello, Alice!"
2.3.3 传递数组作为参数
若需传递数组给函数,需将数组展开为单个参数,再在函数内重新组合为数组:若需传递数组给函数,需将数组展开为单个参数,再在函数内重新组合为数组:
#!/bin/bash# 函数:计算数组所有元素的和
sum_array() {local arr=("$@") # 将所有参数重新组合为数组local total=0for num in "${arr[@]}"; dototal=$((total + num))doneecho $total
}# 定义数组
numbers=(1 3 5 7 9)# 调用函数:展开数组为参数(${numbers[@]})
total_sum=$(sum_array "${numbers[@]}")
echo "数组总和:$total_sum" # 输出 25
三、数组
3.1 定义与访问
Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。Shell 数组用括号来表示,元素用"空格"符号分割开。
array_name=(value1 value2 value3 ... valueN)
arr=(A B C d 123 2)
echo ${arr[0]} # 输出:A
echo ${arr[@]} # 输出所有元素
echo ${#arr[@]} # 输出数组长度
也可以使用以下方法创建
array_name=([0]=value1 [1]=value2 [2]value3 ... [N-1]=valueN)array_name[0]=value1
array_name[1]=value2num=([1]=123 [3]=222)
num=([1]=123 qqq [5]=12)
3.2 遍历数组
读取数组元素值的一般格式是:
${array_name[index]}
#index 是指数组的索引,从0开始
使用for循环可以遍历数组:
for item in "${arr[@]}"; do echo $item
done
for item in "${arr[*]}"; doecho $item
done
#!/bin/bash
my_arr=(AA BB CC) #定义数组
my_arr_num=${#my_arr[*]} #获取数组的长度
for((i=0;i<my_arr_num;i++));
do
echo ${my_arr[$i]}
done
四、文件包含source
在一个Shell程序中可以指定包含外部的其他Shell脚本程序。这样可以很方
便的封装一些公用的代码作为一个独立的文件。
4.1 语法
有两种方法在一个脚本中引入另一个脚本:
. 空格 filename # 注意点号"."和文件名中间有一空格
source filename
. /etc/config.sh
source /etc/config.sh
4.2 示例
config.sh
:app_name="MyApp"
main.sh
:source /etc/config.sh echo "应用名:$app_name"
4.3 实际环境用法
脚本一:采集数据
脚本二:处理数据
#!/bin/bash
echo "user cpu memory"
echo "1 2 3"user=$(whoami)cpu=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}')
#awk默认用空格做分割
memory=$(free -h | awk '/Mem/ {print $3}')echo "$user $cpu $memory"
#!/bin/bash
. /root/test0901/6.sh | while read user cpu mem
doif [ "$user" = "user" ];thenecho "user cpu mem hostname date"continue#跳出循环fihostname=$(hostname)date=$(date +"%Y-%m-%d %H:%M:%S")echo "$user $cpu $mem $hostname $date"
done
总结
【筑基初成,符阵有序】
恭喜道友!筑基初阶神通已修习得成!
此刻,汝之神念已能自如驾驭流程控制:if-else
可依条件定符阵分支,无论是判断用户身份、校验文件路径,还是自动化安装服务,皆能精准决策;for/while
可驱动灵气循环,遍历数组、重复交互皆不在话下;case
能快速辨识参数,简化服务启停类符阵的逻辑。更掌握了 “函数化符” 之术,可将重复代码凝为函数,调用随心,让符阵结构更简洁、维护更易。
至此,汝已从 “依样画符” 的炼气士,成长为能独立构建 “有序符阵” 的筑基修士,可应对日常自动化场景中的基础需求。然筑基之路未竟,上一篇《筑基篇(下)》,将授汝 “灵气流转” 之妙诀,掌控数据输入输出的走向,让符阵运转更灵活、无滞碍。待流控之术修成,方算筑基圆满,可向金丹之境迈进!