目錄
- 1 shell基礎
- 1.1 指令碼安全和除錯
- 1.2 變數
- 範例:使用變數備份檔案
- 範例:使用$*傳參實現alias rm的安全刪除
- 1.3 &|!邏輯運算
- 1.4 []條件
- 範例:[[]]條件判斷
- 範例:&& ||組合條件判斷
- 範例:判斷磁碟空間和inode節點使用情況
- 1.5 ()和{}
- 1.6 if/case條件選擇
- 範例:if判斷作業系統
- 範例:case顯示安裝選單
- 1.7 迴圈
- 範例:for列印sh檔案
- 範例:並行執行掃描網段
- 範例:防止dos攻擊的指令碼
- 範例:讀檔案列印系統使用者
- 1.6 函式
- 範例:初始化部分環境資訊
- 1.7 注意惡意程式,如fork炸彈
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&