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&
posted @   szlhwei  阅读(25)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示