03 shell基礎

szlhwei發表於2024-06-26

目錄
  • 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&