shell编程基础知识及脚本示例
文章目录
- 前言
- 一、shell变量
- 1.命名规则
- 2.定义及使用变量
- 二、shell传递参数
- 1.位置参数
- 2. 任意参数
- 三、shell一维数组
- 0.定义方式
- 1.定义并获取数组的单个元素
- 2.定义并获取数组的所有元素
- 3.定义并获取数组的元素个数
- 4.定义并获取数组的元素索引
- 四、shell条件判断语法
- 五、shell常用的数值比较命令
- 六、shell常用的字符串比较命令
- 七、shell常用的文件比较命令
- 八、shell处理参数的特殊字符
- 九、流程控制
- 1.if else-if else语法格式
- 2. while语法格式
- 3. for语法格式
- 4.until语法格式
- 5.case
- 6. 跳出循环
- break
- continue
- 十、shell函数
- 十一、日常脚本示例
前言
shell脚本作为运维人员的基操,本博客从实战角度出发,以清晰的结构和通俗的示例,系统梳理 Shell 脚本的核心知识点。内容涵盖从基础变量定义到复杂流程控制,从参数解析到函数封装,最终通过日常脚本示例串联所有技能点。无论你是刚接触 Shell 的新手,还是希望查漏补缺的进阶者,都能在此找到可落地的代码片段和深入浅出的原理剖析。
一、shell变量
1.命名规则
首个字符必须为字母(a-z,A-Z)。中间不能有空格,可以使用下划线(_)。不能使用标点符号。不能使用bash里的关键字(可用help命令查看保留关键字)
2.定义及使用变量
my_name="AAA"
echo $my_name
echo ${my_name}#注意
ehco两句效果一样,均为输出变量的值。变量名外面的花括号是可选的,加花括号是为了帮助解释器识别变量的边界。
二、shell传递参数
注意:$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数
1.位置参数
代码如下(示例):
$0:表示脚本名称
$1: 脚本的第一个参数,以此类推
[root@ops ~]# cat i.sh
#!/bin/bash
echo "The script name is $0"
echo "The number of arguments is $1"[root@ops ~]# sh i.sh "位置参数"
The script name is i.sh
The number of arguments is 位置参数
2. 任意参数
代码如下(示例):
$*和$@的区别:相同点:都是引用所有参数。不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 “ * “ 等价于 “1 2 3”(传递了1个参数)而 “@” 等价于 “1” “2” “3”(传递了3个参数)
$*示例
[root@ops ~]# cat i.sh
#!/bin/bash
for i in "$*"; doecho $i
done
[root@ops ~]# sh i.sh 1 2 3
1 2 3
$@示例
[root@ops ~]# cat i.sh
#!/bin/bash
for i in "$@"; doecho $i
done
[root@ops ~]# sh i.sh 1 2 3
1
2
3
三、shell一维数组
此处以一维数组为例,只能使用整数作为数组索引
0.定义方式
方法一: 一次赋一个值数组名[下标]=变量值例如:#array[0]=pear#array[1]=apple查看数组:declare -a | grep array echo ${array[@]}
方法二: 一次赋多值数组名=(值1 值2 值3) 查看:echo ${array [@]}
1.定义并获取数组的单个元素
[root@ops ~]# cat i.sh
#!/bin/bash
my_array=(A B "C" D) #定义数组
echo "第一个元素为: ${my_array[0]}"
echo "第二个元素为: ${my_array[1]}"
echo "第三个元素为: ${my_array[2]}"
echo "第四个元素为: ${my_array[3]}"
[root@ops ~]# sh i.sh
第一个元素为: A
第二个元素为: B
第三个元素为: C
第四个元素为: D
2.定义并获取数组的所有元素
[root@ops ~]# cat i.sh
#!/bin/bash
my_array=(A B "C" D) #定义数组
echo "my_array元素是: ${my_array[*]}"
echo "my_array元素是: ${my_array[@]}"
[root@ops ~]# sh i.sh
my_array元素是: A B C D
my_array元素是: A B C D
3.定义并获取数组的元素个数
[root@ops ~]# cat i.sh
#!/bin/bash
my_array=(A B "C" D) #定义数组
echo "my_array元素个数: ${#my_array[@]}"
[root@ops ~]# sh i.sh
my_array元素个数: 4
4.定义并获取数组的元素索引
[root@ops ~]# cat i.sh
#!/bin/bash
my_array=(A B "C" D) #定义数组
echo "my_array元素个数: ${!my_array[@]}"
[root@ops ~]# sh i.sh
my_array元素索引: 0 1 2 3
四、shell条件判断语法
使用test
[root@ops ~]# cat i.sh
#!/bin/bash
num1=10
num2=100
if test $[num1] -eq $[num2]
thenecho '两个数相等!'
elseecho '两个数不相等!'
fi
test 命令的作用
test 是 Shell 内置命令,用于条件判断。它直接评估表达式,返回退出状态码:
0 表示条件为真(True)。
1 表示条件为假(False)。在示例中:test $[num1] -eq $[num2] 比较 num1 和 num2 是否相等。$[ ] 是旧式算术展开语法(现代 Shell 中建议用 $(( )) 替代),此处等效于 $((num1)) 和 $((num2))
使用[ ],它是 test 的另一种形式,本质是同一个命令
[root@ops ~]# cat i.sh
#!/bin/bash
num1=10
num2=100
if [ $[num1] -eq $[num2] ]
thenecho '两个数相等!'
elseecho '两个数不相等!'
fi
使用[[ ]] 双重方括号,该条件下可以使用正则
[root@ops ~]# cat i.sh
#!/bin/bash
num1=10
num2=100
if [[ $[num1] =~ $[num2] ]]
thenecho '两个数相等!'
elseecho '两个数不相等!'
fi
五、shell常用的数值比较命令
参数 | 说明 |
---|---|
-eq | 等于 |
-le | 小于等于 |
-ge | 大于等于 |
-lt | 小于 |
-gt | 大于 |
-ne | 不等于 |
六、shell常用的字符串比较命令
参数 | 说明 |
---|---|
= | 等于则为真 |
!= | 不相等则为真 |
-z 字符串 | 字符串的长度为0则为真 |
-n 字符串 | 字符串的长度不为0则为真 |
判断字符串是否相等示例
[root@ops ~]# cat i.sh
#!/bin/bash
num1="xx"
num2="xxx"
if [[ $num1 = $num2 ]]
thenecho '两个数相等!'
elseecho '两个数不相等!'
fi
[root@ops ~]# sh i.sh
两个数不相等!
七、shell常用的文件比较命令
判断文件是否存在示例
[root@ops ~]# cat i.sh
#!/bin/bash
if test -e ./bash
thenecho '文件已存在!'
elseecho '文件不存在'
fi
[root@ops ~]# sh i.sh
文件不存在
八、shell处理参数的特殊字符
参数处理 | 说明 |
---|---|
$0 | 脚本名称 |
$# | 传递到脚本中的参数个数 |
$* | 以单一字符串的形式给脚本传递参数 |
$@ | 以多个字符串的形式给脚本传递参数 |
$$ | 当前脚本运行的进程号 |
$! | 后台运行的最后一个进程的PID号 |
$- | 显示Shell使用的当前选项,与set命令功能相同 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
九、流程控制
1.if else-if else语法格式
if condition1
thencommand1
elif condition2
thencommand2
elsecommandN
fi
2. while语法格式
while condition
docommand
done
3. for语法格式
for var in item1 item2 ... itemN
docommand1
done
4.until语法格式
until循环执行一系列命令直至条件为真时停止,与while循环刚好相反
until condition
docommand
done
5.case
case 值 in模式1)command1command2commandN;;模式2)command1command2;;
esac
6. 跳出循环
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue
break
break命令允许跳出所有循环(终止执行后面的所有循环)
#!/bin/bash
while :
doecho -n "输入 1 到 5 之间的数字:"read aNumcase $aNum in1|2|3|4|5) echo "你输入的数字为 $aNum!";;*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"break;;esac
done
continue
#!/bin/bash
while :
doecho -n "输入 1 到 5 之间的数字:"read aNumcase $aNum in1|2|3|4|5) echo "你输入的数字为 $aNum!";;*) echo "你输入的数字不是 1 到 5 之间的!"continueecho "游戏结束";;esac
done
十、shell函数
函数形式的shell编程示例
#!/bin/bash
services=(
aries:8199
nginx:80
)
stop_all_services () {for service in "${services[@]}"do# 获取服务名和端口号name=$(echo "$service" | cut -d":" -f1)port=$(echo "$service" | cut -d":" -f2)cd /export/servers/"$name"/binsh stop.shsleep 2 # 等待一段时间,确保服务已经完全停止# 检查服务是否成功停止if [ "$(curl -sIL -w '%{http_code}\n' http://xxx:$port/ -o /dev/null)" == "000" ]thenecho "Service $name stopped successfully."elseecho "Failed to stop service $name."fidone
}
stop_all_services
十一、日常脚本示例
生产环境centos服务器安全加固脚本
#!/bin/bash
#linux脆弱性加固脚本
system_auth(){echo "---口令锁定策略---"cp -p /etc/pam.d/system-auth /etc/pam.d/system-auth_bakif grep -q 'auth required pam_tally2.so' /etc/pam.d/system-auth;thensed -i 's/^auth.*required.*pam_tally2.so$/&\nauth required pam_tally2.so deny=5 unlock_time=300 even_deny_root root_unlock_time=10/g' /etc/pam.d/system-authelseecho "auth required pam_tally2.so deny=5 unlock_time=300 even_deny_root root_unlock_time=10" >> /etc/pam.d/system-authfi if grep -q 'account required pam_tally2.so' /etc/pam.d/system-auth;thensed -i 's/^account.*required.*pam_tally2.so$/&\n/g' /etc/pam.d/system-authelseecho "account required pam_tally2.so" >> /etc/pam.d/system-authfi
}
logindefs(){echo "---口令生存期---"cp -p /etc/login.defs /etc/login.defs_baksed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/g' /etc/login.defssed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 10/g' /etc/login.defssed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE 7/g' /etc/login.defs
}
system_auth_crack(){echo "---口令复杂度---"# 判断 system-auth 配置文件中是否包含 password requisite pam_cracklib.so 的配置if grep -q 'password requisite pam_cracklib.so' /etc/pam.d/system-auth; then# 如果有,则使用 sed 命令替换原有的行sed -i 's/^password.*requisite.*pam_cracklib.so$/& try_first_pass retry=3 dcredit=-1 lcredit=-1 ucredit=-1 ocredit=-1 minlen=8/g' /etc/pam.d/system-authelse# 如果没有,则添加新的一行echo "password requisite pam_cracklib.so try_first_pass retry=3 dcredit=-1 lcredit=-1 ucredit=-1 ocredit=-1 minlen=8" >> /etc/pam.d/system-authfi
}
file_contro(){echo "文件与目录缺省权限控制"cp -p /etc/profile /etc/profile.bakif grep -q 'umask 027' /etc/profile;thenecho "已存在umask 027"elseecho "umask 027" >>/etc/profilefi
}
user_control(){filename1="/etc/passwd"filename2="/etc/shadow"filename3="/etc/group"filename4="/etc/services"filename5="/etc/xinetd.conf"filename6="/etc/security"filename7="/var/log/messages"if [ -e "${filename1}" ]; thenchmod 644 /etc/passwdelseecho "$filename1 不存在"fiif [ -e "${filename2}" ];thenchmod 400 /etc/shadowelseecho "$filename2 不存在"fiif [ -e "${filename3}" ];thenchmod 644 /etc/groupelseecho "$filename3 不存在"fiif [ -e "${filename4}" ];thenchmod 644 /etc/serviceselseecho "$filename4 不存在"fiif [ -e "${filename5}" ];thenchmod 600 /etc/xinetd.confelseecho "$filename5 不存在"fiif [ -e "${filename6}" ];thenchmod 600 /etc/securityelseecho "$filename6 不存在"fiif [ -e "${filename7}" ];then chattr +a /var/log/messageselseecho "$filename7 不存在"fi
}
security_log(){filename="/var/adm/messages"if [ -e "${filename}" ]; thenchmod 666 /var/adm/messagessystemctl restart rsyslog
elsetouch ${filename}chmod 666 /var/adm/messagesecho "*.err;kern.debug;daemon.notice /var/adm/messages" >> /etc/rsyslog.confsystemctl restart rsyslog
fi
}
login_timeout(){# 检查 /etc/profile 文件是否存在
if [ ! -f /etc/profile ]; thenecho "/etc/profile 文件不存在"exit 1
fi# 使用 sed 命令检查文件末尾是否已经存在所需的两行内容,不存在则添加
if ! grep -q '^TMOUT=' /etc/profile; thenecho 'TMOUT=300' >> /etc/profile
elsesed -i 's/^TMOUT=.*/TMOUT=300/' /etc/profile
fiif ! grep -q '^export TMOUT' /etc/profile; thenecho 'export TMOUT' >> /etc/profile
fi
}
history_set(){# 检查 /etc/profile 文件是否存在
if [ ! -f /etc/profile ]; thenecho "/etc/profile 文件不存在"exit 1
fi# 使用 sed 命令检查文件中是否已经存在所需的两行内容,不存在则添加上去
if ! grep -q '^HISTFILESIZE=' /etc/profile; thenecho 'HISTFILESIZE=5' >> /etc/profile
elsesed -i 's/^HISTFILESIZE=.*/HISTFILESIZE=5/' /etc/profile
fiif ! grep -q '^HISTSIZE=' /etc/profile; thenecho 'HISTSIZE=5' >> /etc/profile
elsesed -i 's/^HISTSIZE=.*/HISTSIZE=5/' /etc/profile
fi# 让配置生效
source /etc/profile
}
core_dump(){# 检查 /etc/security/limits.conf 文件是否存在
if [ ! -f /etc/security/limits.conf ]; thenecho "/etc/security/limits.conf 文件不存在"exit 1
fi# 使用 sed 命令检查文件末尾是否已经存在所需的两行内容
if ! grep -q '^\*\s\+soft\s\+core\s\+0$' /etc/security/limits.conf; thenecho '* soft core 0' >> /etc/security/limits.conf
elsesed -i 's/^\(\*\s\+soft\s\+core\s\+\).*/\10/' /etc/security/limits.conf
fiif ! grep -q '^\*\s\+hard\s\+core\s\+0$' /etc/security/limits.conf; thenecho '* hard core 0' >> /etc/security/limits.conf
elsesed -i 's/^\(\*\s\+hard\s\+core\s\+\).*/\10/' /etc/security/limits.conf
fi# 检查 /etc/profile 文件是否存在
if [ ! -f /etc/profile ]; thenecho "/etc/profile 文件不存在"exit 1
fi# 使用 grep 命令检查文件中是否存在指定内容,同时注释掉对应的行
if grep -q 'ulimit\s\+-S\s\+-c\s\+0\s\+>\s\+\/dev\/null\s\+2>&1' /etc/profile; thensed -i 's/^ulimit\s\+-S\s\+-c\s\+0\s\+>\s\+\/dev\/null\s\+2>&1/#&/' /etc/profile
fi
}
system_auth
logindefs
system_auth_crack
file_contro
user_control
security_log
login_timeout
history_set
core_dump