03 shell基础
目录
1 shell基础
1.1 脚本安全和调试
##bash -n **.sh,只检测语法错误,无法检查命令错误
##bash -x **.sh,调试并执行
##语法错误,会导致后续的命令不继续执行,可以用bash -n检查错误,提示的出错行数不一定是准确的
##命令错误,默认后续的命令还会继续执行,用bash -n无法检查出来 ,可以使用 bash -x 进行观察
##逻辑错误:只能使用 bash -x 进行观察
[root@anolis-31 ~]$cat -A eof.sh
#!/bin/bash$
aaa bbb$
touch b.txt$
cat >test.txt<<EOF $
a b c EOF$
$
##aaa bbb没有检测异常;但EOF结束检测出
[root@anolis-31 ~]$bash -n eof.sh
eof.sh:行5: 警告:立即文档在第 3 行被文件结束符分隔 (需要 `EOF')
##调试并执行,b.txt已经创建
[root@anolis-31 ~]$bash -x eof.sh
+ aaa bbb
eof.sh:行2: aaa: 未找到命令
+ touch b.txt
eof.sh:行6: 警告:立即文档在第 4 行被文件结束符分隔 (需要 `EOF')
+ cat
[root@anolis-31 ~]$ll b.txt
##脚本安全
##set -o nounset:变量没有设置,报错
##set -o errexit:报错就退出,不能继续执行
##创建~/.vim/templates/bash.template 的模板文件
#!/bin/bash
set -o nounset
set -o errexit
##修改~/.vimrc,默认模板
autocmd BufNewFile *.sh,*.bash 0r ~/.vim/templates/bash.template
1.2 变量
##内置变量
##PS1 , PATH , UID , HOSTNAME ,$$ , BASHPID , PPID ,$? , HISTSIZE
##自定义变量,不支持短横线 “ - ” ,和主机名相反
##直接字串: name='root'
##变量引用: name="$USER"
##命令引用: name=`COMMAND` 或者 name=$(COMMAND)
##位置变量
$1, $2, ... 对应第1个、第2个等参数
$0 命令本身 ,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
##状态码
$?的值为0 执行成功
$?的值是1到255 执行失败
范例:使用变量备份文件
##${}调用或$调用,但避免字符串未结束问题,最好使用${}调用变量
##备份文件
[root@anolis-31 ~]$cat backup.sh
#!/bin/bash
set -o nounset
set -o errexit
color='echo -e \e[1;35m'
end='\e[0m'
backup=/data
src=/etc
date=`date +%F-%H%M%S`
${color}starting backup...$end
sleep 2
cp -av ${src} ${backup}${src}_$date
${color}backup is finished$end
范例:使用$*传参实现alias rm的安全删除
##$*所有参数,多个文件也可以
[root@anolis-31 ~]$cat backup_file.sh
#!/bin/bash
set -o nounset
set -o errexit
color='echo -e \e[1;31m'
end='\e[0m'
dir=/data/`date +%F_%H%M%S`
mkdir $dir
mv $* $dir
${color}Move $* to $dir $end
##执行权限,设置alias,删除后确认
[root@anolis-31 ~]$chmod a+x backup_file.sh
[root@anolis-31 ~]$alias rm='/root/backup_file.sh'
[root@anolis-31 ~]$rm sayhello.sh
Move sayhello.sh to /data/2024-06-21_051006
[root@anolis-31 ~]$ll /data/2024-06-21_051006/sayhello.sh
1.3 &|!逻辑运算
##用于判断执行
& 与,全真为真,其它为假
| 或,全假为假,其它为真
! 非,取反
&& 短路与
CMD1 && CMD2 CMD1为真,则执行CMD2;CMD1为假,则不执行CMD2
|| 短路或
CMD1 || CMD2 CMD1为真,则不执行CMD2;CMD1为假,则执行CMD2
短路与和或组合,常用于脚本条件判断执行
CMD1 && CMD2 || CMD3 CMD1为真,则执行CMD2;CMD1为假,则执行CMD3
1.4 []条件
[] 和test等价,真==状态码变量$?返回0;假==状态码变量$?返回1
[[]] 增强版[],支持扩展正则表达式和通配符
[ -v name ] 判断name变量是否定义
[ -eq ] 数值判断,-eq等于,-ne不等于;-gt大于;-lt小于;
[ == ] 算术表达式,==相等;!=不等;<小于;大于
[ -z str ] 字符串,-z string是否为空;-n string是否不空;==相等,!=不等;=~左侧是否与右侧的正则表达式匹配;
[ -e file ] 文件测试,-e存在为真;-d目录;-f文件;
##组合测试条件
如下不支持[[]]
[ exp1 -a exp2 ] 且,都为真结果为真
[ exp1 -o exp2 ] 或,一个真结果为真
[ !exp ] 取反
cmd1 && cmd2
cmd1 || cmd2
cmd1 && cmd2 || cmd3
范例:[[]]条件判断
##注意 [ ] 中需要空格,否则会报下面错误
[root@rocky-41 ~]#[-v path]
-bash: [-v: 未找到命令
[root@rocky-41 ~]#[ -v HOSTNAME ]
[root@rocky-41 ~]#echo $?
0
##判断操作系统版本,后续可以针对不同版本不同操作
[root@anolis-31 ~]$cat /etc/os-release
NAME="Anolis OS"
VERSION="8.9"
ID="anolis"
ID_LIKE="rhel fedora centos"
VERSION_ID="8.9"
PLATFORM_ID="platform:an8"
PRETTY_NAME="Anolis OS 8.9"
ANSI_COLOR="0;31"
HOME_URL="https://openanolis.cn/"
[root@anolis-31 ~]$. /etc/os-release
[root@anolis-31 ~]$[[ $ID_LIKE =~ rhel ]]
[root@anolis-31 ~]$echo $?
0
[root@anolis-31 ~]$[[ $ID_LIKE =~ ubuntu ]]
[root@anolis-31 ~]$echo $?
1
##后续可以判断文件后缀使用不同命令操作
[root@anolis-31 ~]$file=nginx-1.24.0.tar.gz
[root@anolis-31 ~]$[[ $file =~ .tar.gz$ ]]
[root@anolis-31 ~]$echo $?
0
范例:&& ||组合条件判断
##随机数,6中1,打印bingo,不中打印click
[root@anolis-31 ~]$[ $[RANDOM%6] -eq 0 ] && echo "bingo" || echo "click"
##IP能ping通则打印up,否则打印unreachable
[root@anolis-31 ~]$IP=10.0.0.100
[root@anolis-31 ~]$ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || echo "$IP is unreachable"
10.0.0.100 is unreachable
[root@anolis-31 ~]$IP=10.0.0.41
[root@anolis-31 ~]$ping -c1 -W1 $IP &> /dev/null && echo "$IP is up" || echo "$IP is unreachable"
10.0.0.41 is up
范例:判断磁盘空间和inode节点使用情况
##后续考虑邮件发送结果
[root@anolis-31 ~]$cat disk_check.sh
#!/bin/bash
set -o nounset
set -o errexit
warning=80
space_used=`df | awk -F" +|%" 'NR>1{print $5}'|sort -r | head -1`
[ $space_used -ge $warning ] && echo "disk used is $space_used ,will be full" || echo "disk used is good"
inode_used=`df -i |awk -F" +|%" 'NR>1{print $5}'|sort -r |head -1`
[ $inode_used -ge $warning ] && echo "inode used is $inode_used ,will be full" || echo "inode used is good"
1.5 ()和{}
(CMD1;CMD2;...)和 { CMD1;CMD2;...; } 都可以将多个命令组合在一起,批量执行
() 会开启子shell,子shell中变量及内部命令结束后,不再影像后续环境
{} 不会开启子shell
1.6 if/case条件选择
if 判断条件1; then
条件1为真的分支代码
[
elif 判断条件2; then 条件2为真的分支代码
elif 判断条件3; then 条件3为真的分支代码
...
else
以上条件都为假的分支代码
]
fi
case 变量引用 in
PAT1)
分支1 ;;
PAT2)
分支2 ;;
...
*)
默认分支 ;;
esac
范例:if判断操作系统
##后续可以根据不同操作系统,做不同配置
[root@anolis-31 ~]$cat os_release.sh
#!/bin/bash
set -o nounset
set -o errexit
. /etc/os-release
if [[ $ID =~ "rocky" ]] ;then
echo "rocky"
elif [[ $ID =~ "anolis" ]] ;then
echo "anolis"
else
echo "ubuntu"
fi
范例:case显示安装菜单
##case不支持循环,后续可以使用循环询问
[root@anolis-31 ~]$cat install_menu.sh
#!/bin/bash
set -o nounset
set -o errexit
echo -e "\e[1;31m"
cat <<EOF
请选择:
1)安装mysql
2)安装nginx
EOF
echo -e "\e[0m"
read -p "请输入1、2: " menu
case $menu in
1)
echo “安装mysql”
;;
2)
echo "安装nginx"
;;
*)
echo "输入错误"
esac
1.7 循环
##for循环
for NAME [in WORDS ... ] ; do COMMANDS; done
for 变量名 in 列表 ;do
循环体
done
##while循环;while [true|:]无限循环
while COMMANDS; do COMMANDS; done
while CONDITION; do 循环体
done
##while依次读
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
##循环控制语句
continue 结束本轮,进入下一轮
break 结束目前的整个循环
范例:for打印sh文件
##显示所有sh文件
[root@anolis-31 ~]$for file in * ;do [[ $file =~ .sh$ ]] && echo $file ;done
范例:并行执行扫描网段
[root@anolis-31 ~]$cat scan_host.sh
#!/bin/bash
set -o nounset
set -o errexit
net=10.0.0
for id in {1..50};do
{
ping -c1 -w1 ${net}.${id} &> /dev/null && echo ${net}.${id} is up || echo ${net}.${id} is down
}&
done
##因为使用{}&后台执行,所以使用wait等所有ping子进程结束后,脚本才结束
wait
范例:防止dos攻击的脚本
[root@anolis-31 ~]$cat check_link.sh
#!/bin/bash
set -o nounset
set -o errexit
##连接数设置3,方便观察
warning=3
touch deny_hosts.txt
while : ;do
##while read依次读awk排序后的结果
ss -nt | awk -F" +|:" 'NR!=1{print $6}' |sort |uniq -c | sort-r | while read count ip ;do
if [ $count -gt $warning ];then
echo $ip is deny
##grep -q 静默模式,找到匹配返回0,无匹配返回非零
##即文件内无,则添加到文件,且添加防火墙规则拒绝
grep -q "$ip" deny_hosts.txt ||{ echo $ip >>deny_hosts.txt; iptables -A INPUT -s $ip -j REJECT; }
fi
done
##while一直循环,每隔10秒执行一次
sleep 10
done
##验证,服务端执行
[root@anolis-31 ~]$bash check_link.sh
10.0.0.41 is deny
##客户端访问,超出后报错
[root@rocky-41 ~]#ssh 10.0.0.31&
[root@rocky-41 ~]#ssh 10.0.0.31&
[root@rocky-41 ~]#ssh 10.0.0.31&
[1]+ 已停止
范例:读文件打印系统用户
[root@anolis-31 ~]$cat check_sysuser.sh
#!/bin/bash
set -o nounset
set -o errexit
dir_path=/etc/passwd
while read line ;do
[[ ${line} =~ /sbin/nologin$ ]] && echo $line |cut -d: -f1
done < ${dir_path}
1.6 函数
#语法一:
func_name (){ ...函数体 ...
}
#语法二:
function func_name {
...函数体 ...
}
#语法三:
function func_name () {
...函数体 ... }
“.”调用函数文件
范例:初始化部分环境信息
[root@anolis-31 ~]$cat init_sys.sh
#!/bin/bash
set -o nounset
set -o errexit
##各个函数
disable_selinux(){
(grep -q "SELINUX=disabled" /etc/selinux/config) && (echo "已关闭") || (sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config;echo "已修改,已关闭,重启生效")
}
disable_firewalld(){
systemctl disable --now firewalld &> /dev/null
echo "防火墙已关闭"
}
set_ps1(){
echo "PS1='\[\e[1;34m\][\u@\h \W]$\[\e[0m\]'" >>/root/.bashrc
echo "root用户提示符已修改"
}
##主函数
main(){
cat <<EOF
请选择:
1)禁用selinux
2)关闭firewalld
3)修改root提示符
EOF
read -p "请输入1、2、3: " menu
case $menu in
1)
disable_selinux
;;
2)
disable_firewalld
;;
3)
set_ps1
;;
*)
echo "输入错误"
esac
}
##调用主函数
main
1.7 注意恶意程序,如fork炸弹
##fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。 由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
:(){ : |:& };:
bomb() { bomb | bomb & }; bomb
cat Bomb.sh #!/bin/bash ./$0|./$0&
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析