1. 程式人生 > 其它 >23 個非常實用的 Shell 拿來就用指令碼例項

23 個非常實用的 Shell 拿來就用指令碼例項

shell指令碼是幫助程式設計師和系統管理員完成費時費力的枯燥工作的利器,是與計算機互動並管理檔案和系統操作的有效方式。區區幾行程式碼,就可以讓計算機接近按照你的意圖行事。

為大家整理了23個例項,通過23個實戰經典指令碼例項,展示了shell指令碼程式設計的實用技術和常見工具用法。大家只需根據自己的需求,將文中這些常見任務和可移植自動化指令碼推廣應用到其他類似問題上,能解決那些三天兩頭碰上的麻煩事。

檢測兩臺伺服器指定目錄下的檔案一致性

#!/bin/bash
#####################################
#檢測兩臺伺服器指定目錄下的檔案一致性
#####################################
#通過對比兩臺伺服器上檔案的md5值,達到檢測一致性的目的
dir=/data/web
b_ip=192.168.88.10
#將指定目錄下的檔案全部遍歷出來並作為md5sum命令的引數,進而得到所有檔案的md5值,並寫入到指定檔案中
find$dir-typef|xargsmd5sum>/tmp/md5_a.txt
ssh$b_ip"find$dir-typef|xargsmd5sum>/tmp/md5_b.txt"
scp$b_ip:/tmp/md5_b.txt/tmp
#將檔名作為遍歷物件進行一一比對
forfin`awk'{print2}/tmp/md5_a.txt'`
do
#以a機器為標準,當b機器不存在遍歷物件中的檔案時直接輸出不存在的結果
ifgrep-qw"$f"/tmp/md5_b.txt
then
md5_a=`grep-w"$f"/tmp/md5_a.txt|awk'{print1}'`
md5_b=`grep-w"$f"/tmp/md5_b.txt|awk'{print1}'`
#當檔案存在時,如果md5值不一致則輸出檔案改變的結果
if[$md5_a!=$md5_b]
then
echo"$fchanged."
fi
else
echo"$fdeleted."
fi
done

定時清空檔案內容,定時記錄檔案大小

#!/bin/bash
################################################################
#每小時執行一次指令碼(任務計劃),當時間為0點或12點時,將目標目錄下的所有檔案內
#容清空,但不刪除檔案,其他時間則只統計各個檔案的大小,一個檔案一行,輸出到以時#間和日期命名的檔案中,需要考慮目標目錄下二級、三級等子目錄的檔案
################################################################
logfile=/tmp/`date+%H-%F`.log
n=`date+%H`
if[$n-eq00]||[$n-eq12]
then
#通過for迴圈,以find命令作為遍歷條件,將目標目錄下的所有檔案進行遍歷並做相應操作
foriin`find/data/log/-typef`
do
true>$i
done
else
foriin`find/data/log/-typef`
do
du-sh$i>>$logfile
done
fi

檢測網絡卡流量,並按規定格式記錄在日誌中

#!/bin/bash
#######################################################
#檢測網絡卡流量,並按規定格式記錄在日誌中
#規定一分鐘記錄一次
#日誌格式如下所示:
#2019-08-1220:40
#ens33input:1234bps
#ens33output:1235bps
######################################################3
while:
do
#設定語言為英文,保障輸出結果是英文,否則會出現bug
LANG=en
logfile=/tmp/`date+%d`.log
#將下面執行的命令結果輸出重定向到logfile日誌中
exec>>$logfile
date+"%F%H:%M"
#sar命令統計的流量單位為kb/s,日誌格式為bps,因此要*1000*8
sar-nDEV159|grepAverage|grepens33|awk'{print$2,"\t","input:","\t",$5*1000*8,"bps","\n",$2,"\t","output:","\t",$6*1000*8,"bps"}'
echo"####################"
#因為執行sar命令需要59秒,因此不需要sleep
done

計算文件每行出現的數字個數,並計算整個文件的數字總數

#!/bin/bash
#########################################################
#計算文件每行出現的數字個數,並計算整個文件的數字總數
########################################################
#使用awk只輸出文檔行數(擷取第一段)
n=`wc-la.txt|awk'{print$1}'`
sum=0
#文件中每一行可能存在空格,因此不能直接用文件內容進行遍歷
foriin`seq1$n`
do
#輸出的行用變量表示時,需要用雙引號
line=`sed-n"$i"pa.txt`
#wc-L選項,統計最長行的長度
n_n=`echo$line|seds'/[^0-9]//'g|wc-L`
echo$n_n
sum=$[$sum+$n_n]
done
echo"sum:$sum"

殺死所有指令碼

#!/bin/bash
################################################################
#有一些指令碼加入到了cron之中,存在指令碼尚未執行完畢又有新任務需要執行的情況,
#導致系統負載升高,因此可通過編寫指令碼,篩選出影響負載的程序一次性全部殺死。
################################################################
psaux|grep指定程序名|grep-vgrep|awk'{print$2}'|xargskill-9

從FTP伺服器下載檔案

#!/bin/bash
if[$#-ne1];then
echo"Usage:$0filename"
fi
dir=$(dirname$1)
file=$(basename$1)
ftp-n-v<<EOF#-n自動登入
open192.168.1.10#ftp伺服器
useradminpassword
binary#設定ftp傳輸模式為二進位制,避免MD5值不同或.tar.gz壓縮包格式錯誤
cd$dir
get"$file"
EOF

連續輸入5個100以內的數字,統計和、最小和最大

#!/bin/bash
COUNT=1
SUM=0
MIN=0
MAX=100
while[$COUNT-le5];do
read-p"請輸入1-10個整數:"INT
if[[!$INT=~^[0-9]+$]];then
echo"輸入必須是整數!"
exit1
elif[[$INT-gt100]];then
echo"輸入必須是100以內!"
exit1
fi
SUM=$(($SUM+$INT))
[$MIN-lt$INT]&&MIN=$INT
[$MAX-gt$INT]&&MAX=$INT
letCOUNT++
done
echo"SUM:$SUM"
echo"MIN:$MIN"
echo"MAX:$MAX"

使用者猜數字

#!/bin/bash#指令碼生成一個100以內的隨機數,提示使用者猜數字,根據使用者的輸入,提示使用者猜對了,#猜小了或猜大了,直至使用者猜對指令碼結束。# RANDOM 為系統自帶的系統變數,值為0‐32767的隨機數#使用取餘演算法將隨機數變為1‐100的隨機數num=$[RANDOM%100+1]echo"$num"#使用read提示使用者猜數字#使用if判斷使用者猜數字的大小關係:‐eq(等於),‐ne(不等於),‐gt(大於),‐ge(大於等於),#‐lt(小於),‐le(小於等於)while:doread-p"計算機生成了一個1‐100的隨機數,你猜:"caiif[$cai-eq$num]thenecho"恭喜,猜對了"exitelif[$cai-gt$num]thenecho"Oops,猜大了"elseecho"Oops,猜小了"fidone

監測Nginx訪問日誌502情況,並做相應動作

假設伺服器環境為lnmp,近期訪問經常出現502現象,且502錯誤在重啟php-fpm服務後消失,因此需要編寫監控指令碼,一旦出現502,則自動重啟php-fpm服務。

#場景:
#1.訪問日誌檔案的路徑:/data/log/access.log
#2.指令碼死迴圈,每10秒檢測一次,10秒的日誌條數為300條,出現502的比例不低於10%(30條)則需要重啟php-fpm服務
#3.重啟命令為:/etc/init.d/php-fpm restart
#!/bin/bash
###########################################################
#監測Nginx訪問日誌502情況,並做相應動作
###########################################################
log=/data/log/access.log
N=30#設定閾值
while:
do
#檢視訪問日誌的最新300條,並統計502的次數
err=`tail-n300$log|grep-c'502"'`
if[$err-ge$N]
then
/etc/init.d/php-fpmrestart2>/dev/null
#設定60s延遲防止指令碼bug導致無限重啟php-fpm服務
sleep60
fi
sleep10
done

將結果分別賦值給變數

應用場景:希望將執行結果或者位置引數賦值給變數,以便後續使用。

方法1:

foriin$(echo"456");do
evala$i=$i
done
echo$a4$a5$a6
方法2:將位置引數192.168.1.1{1,2}拆分為到每個變數

num=0
foriin$(evalecho$*);do#eval將{1,2}分解為12
letnum+=1
evalnode${num}="$i"
done
echo$node1$node2$node3
#basha.sh192.168.1.1{1,2}
192.168.1.11192.168.1.12
方法3:

arr=(456)
INDEX1=$(echo${arr[0]})
INDEX2=$(echo${arr[1]})
INDEX3=$(echo${arr[2]})

批量修改檔名

示例:

#toucharticle_{1..3}.html
#ls
article_1.htmlarticle_2.htmlarticle_3.html
目的:把article改為bbs

方法1:

forfilein$(ls*html);do
mv$filebbs_${file#*_}
#mv$file$(echo$file|sed-r's/.*(_.*)/bbs\1/')
#mv$file$(echo$file|echobbs_$(cut-d_-f2)
done
方法2:

forfilein$(find.-maxdepth1-name"*html");do
mv$filebbs_${file#*_}
done
方法3:

#renamearticlebbs*.html

把一個文件前五行中包含字母的行刪掉,同時刪除6到10行包含的所有字母

1)準備測試檔案,檔名為2.txt

第1行1234567不包含字母
第2行56789BBBBBB
第3行67890CCCCCCCC
第4行78asdfDDDDDDDDD
第5行123456EEEEEEEE
第6行1234567ASDF
第7行56789ASDF
第8行67890ASDF
第9行78asdfADSF
第10行123456AAAA
第11行67890ASDF
第12行78asdfADSF
第13行123456AAAA

2)指令碼如下:

#!/bin/bash
##############################################################
#把一個文件前五行中包含字母的行刪掉,同時刪除6到10行包含的所有字母
##############################################################
sed-n'1,5'p2.txt|sed'/[a-zA-Z]/'d
sed-n'6,10'p2.txt|seds'/[a-zA-Z]//'g
sed-n'11,$'p2.txt
#最終結果只是在螢幕上列印結果,如果想直接更改檔案,可將輸出結果寫入臨時檔案中,再替換2.txt或者使用-i選項

統計當前目錄中以.html結尾的檔案總大

方法1:

#find.-name"*.html"-execdu-k{}\;|awk'{sum+=$1}END{printsum}'

方法2:

forsizein$(ls-l*.html|awk'{print$5}');do
sum=$(($sum+$size))
done
echo$sum

掃描主機埠狀態

#!/bin/bash
HOST=$1
PORT="2225808080"
forPORTin$PORT;do
ifecho&>/dev/null>/dev/tcp/$HOST/$PORT;then
echo"$PORTopen"
else
echo"$PORTclose"
fi
done

用shell列印示例語句中字母數小於6的單詞

#示例語句:
#Bashalsointerpretsanumberofmulti-characteroptions.
#!/bin/bash
##############################################################
#shell列印示例語句中字母數小於6的單詞
##############################################################
forsinBashalsointerpretsanumberofmulti-characteroptions.
do
n=`echo$s|wc-c`
if[$n-lt6]
then
echo$s
fi
done

輸入數字執行相應命令

#!/bin/bash
##############################################################
#輸入數字執行相應命令
##############################################################
echo"*cmdmenu*1-date2-ls3-who4-pwd0-exit"
while:
do
#捕獲使用者鍵入值
read-p"pleaseinputnumber:"n
n1=`echo$n|seds'/[0-9]//'g`
#空輸入檢測
if[-z"$n"]
then
continue
fi
#非數字輸入檢測
if[-n"$n1"]
then
exit0
fi
break
done
case$nin
1)
date
;;
2)
ls
;;
3)
who
;;
4)
pwd
;;
0)
break
;;
#輸入數字非1-4的提示
*)
echo"pleaseinputnumberis[1-4]"
esac

Expect實現SSH免互動執行命令

Expect是一個自動互動式應用程式的工具,如telnet,ftp,passwd等。

需先安裝expect軟體包。

方法1:EOF標準輸出作為expect標準輸入

#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect<<EOF
settimeout30
spawnssh$USER@$IP
expect{
"(yes/no)"{send"yes\r";exp_continue}
"password:"{send"$PASS\r"}
}
expect"$USER@*"{send"$1\r"}
expect"$USER@*"{send"exit\r"}
expecteof
EOF
方法2:

#!/bin/bash
USER=root
PASS=123.com
IP=192.168.1.120
expect-c"
spawnssh$USER@$IP
expect{
\"(yes/no)\"{send\"yes\r\";exp_continue}
\"password:\"{send\"$PASS\r\";exp_continue}
\"$USER@*\"{send\"df-h\rexit\r\";exp_continue}
}"
方法3:將expect指令碼獨立出來

登入指令碼:

#catlogin.exp
#!/usr/bin/expect
setip[lindex$argv0]
setuser[lindex$argv1]
setpasswd[lindex$argv2]
setcmd[lindex$argv3]
if{$argc!=4}{
puts"Usage:expectlogin.expipuserpasswd"
exit1
}
settimeout30
spawnssh$user@$ip
expect{
"(yes/no)"{send"yes\r";exp_continue}
"password:"{send"$passwd\r"}
}
expect"$user@*"{send"$cmd\r"}
expect"$user@*"{send"exit\r"}
expecteof
執行命令指令碼:寫個迴圈可以批量操作多臺伺服器

#!/bin/bash
HOST_INFO=user_info.txt
foripin$(awk'{print$1}'$HOST_INFO)
do
user=$(awk-vI="$ip"'I==$1{print$2}'$HOST_INFO)
pass=$(awk-vI="$ip"'I==$1{print$3}'$HOST_INFO)
expectlogin.exp$ip$user$pass$1
done
Linux主機SSH連線資訊:

#catuser_info.txt
192.168.1.120root123456

建立10個使用者,並分別設定密碼,密碼要求10位且包含大小寫字母以及數字,最後需要把每個使用者的密碼存在指定檔案中

#!/bin/bash
##############################################################
#建立10個使用者,並分別設定密碼,密碼要求10位且包含大小寫字母以及數字
#最後需要把每個使用者的密碼存在指定檔案中
#前提條件:安裝mkpasswd命令
##############################################################
#生成10個使用者的序列(00-09)
foruin`seq-w009`
do
#建立使用者
useradduser_$u
#生成密碼
p=`mkpasswd-s0-l10`
#從標準輸入中讀取密碼進行修改(不安全)
echo$p|passwd--stdinuser_$u
#常規修改密碼
echo-e"$p\n$p"|passwduser_$u
#將建立的使用者及對應的密碼記錄到日誌檔案中
echo"user_$u$p">>/tmp/userpassword
done

監控httpd的程序數,根據監控情況做相應處理

#!/bin/bash
###############################################################################################################################
#需求:
#1.每隔10s監控httpd的程序數,若程序數大於等於500,則自動重啟Apache服務,並檢測服務是否重啟成功
#2.若未成功則需要再次啟動,若重啟5次依舊沒有成功,則向管理員傳送告警郵件,並退出檢測
#3.如果啟動成功,則等待1分鐘後再次檢測httpd程序數,若程序數正常,則恢復正常檢測(10s一次),否則放棄重啟並向管理員傳送告警郵件,並退出檢測
###############################################################################################################################
#計數器函式
check_service()
{
j=0
foriin`seq15`
do
#重啟Apache的命令
/usr/local/apache2/bin/apachectlrestart2>/var/log/httpderr.log
#判斷服務是否重啟成功
if[$?-eq0]
then
break
else
j=$[$j+1]
fi
#判斷服務是否已嘗試重啟5次
if[$j-eq5]
then
mail.py
exit
fi
done
}
while:
do
n=`pgrep-lhttpd|wc-l`
#判斷httpd服務程序數是否超過500
if[$n-gt500]
then
/usr/local/apache2/bin/apachectlrestart
if[$?-ne0]
then
check_service
else
sleep60
n2=`pgrep-lhttpd|wc-l`
#判斷重啟後是否依舊超過500
if[$n2-gt500]
then
mail.py
exit
fi
fi
fi
#每隔10s檢測一次
sleep10
done

批量修改伺服器使用者密碼

Linux主機SSH連線資訊:舊密碼

#catold_pass.txt
192.168.18.217root12345622
192.168.18.218root12345622
內容格式:IP User Password Port

SSH遠端修改密碼指令碼:新密碼隨機生成
https://www.linuxprobe.com/books
#!/bin/bash
OLD_INFO=old_pass.txt
NEW_INFO=new_pass.txt
forIPin$(awk'/^[^#]/{print$1}'$OLD_INFO);do
USER=$(awk-vI=$IP'I==$1{print$2}'$OLD_INFO)
PASS=$(awk-vI=$IP'I==$1{print$3}'$OLD_INFO)
PORT=$(awk-vI=$IP'I==$1{print$4}'$OLD_INFO)
NEW_PASS=$(mkpasswd-l8)#隨機密碼
echo"$IP$USER$NEW_PASS$PORT">>$NEW_INFO
expect-c"
spawnssh-p$PORT$USER@$IP
settimeout2
expect{
\"(yes/no)\"{send\"yes\r\";exp_continue}
\"password:\"{send\"$PASS\r\";exp_continue}
\"$USER@*\"{send\"echo\'$NEW_PASS\'|passwd--stdin$USER\rexit\r\";exp_continue}
}"
done
生成新密碼檔案:

#catnew_pass.txt
192.168.18.217rootn8wX3mU%22
192.168.18.218rootc87;ZnnL22

iptables自動遮蔽訪問網站頻繁的IP

場景:惡意訪問,安全防範

1)遮蔽每分鐘訪問超過200的IP

方法1:根據訪問日誌(Nginx為例)

#!/bin/bash
DATE=$(date+%d/%b/%Y:%H:%M)
ABNORMAL_IP=$(tail-n5000access.log|grep$DATE|awk'{a[$1]++}END{for(iina)if(a[i]>100)printi}')
#先tail防止檔案過大,讀取慢,數字可調整每分鐘最大的訪問量。awk不能直接過濾日誌,因為包含特殊字元。
forIPin$ABNORMAL_IP;do
if[$(iptables-vnL|grep-c"$IP")-eq0];then
iptables-IINPUT-s$IP-jDROP
fi
done
方法2:通過TCP建立的連線

#!/bin/bash
ABNORMAL_IP=$(netstat-an|awk'$4~/:80$/&&$6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(iina)if(a[i]>100)printi}')
#gsub是將第五列(客戶端IP)的冒號和埠去掉
forIPin$ABNORMAL_IP;do
if[$(iptables-vnL|grep-c"$IP")-eq0];then
iptables-IINPUT-s$IP-jDROP
fi
done

2)遮蔽每分鐘SSH嘗試登入超過10次的IP

方法1:通過lastb獲取登入狀態:

#!/bin/bash
DATE=$(date+"%a%b%e%H:%M")#星期月天時分%e單數字時顯示7,而%d顯示07
ABNORMAL_IP=$(lastb|grep"$DATE"|awk'{a[$3]++}END{for(iina)if(a[i]>10)printi}')
forIPin$ABNORMAL_IP;do
if[$(iptables-vnL|grep-c"$IP")-eq0];then
iptables-IINPUT-s$IP-jDROP
fi
done
方法2:通過日誌獲取登入狀態

#!/bin/bash
DATE=$(date+"%b%d%H")
ABNORMAL_IP="$(tail-n10000/var/log/auth.log|grep"$DATE"|awk'/Failed/{a[$(NF-3)]++}END{for(iina)if(a[i]>5)printi}')"
forIPin$ABNORMAL_IP;do
if[$(iptables-vnL|grep-c"$IP")-eq0];then
iptables-AINPUT-s$IP-jDROP
echo"$(date+"%F%T")-iptables-AINPUT-s$IP-jDROP">>~/ssh-login-limit.log
fi
done

根據web訪問日誌,封禁請求量異常的IP,如IP在半小時後恢復正常,則解除封禁

#!/bin/bash
####################################################################################
#根據web訪問日誌,封禁請求量異常的IP,如IP在半小時後恢復正常,則解除封禁
####################################################################################
logfile=/data/log/access.log
#顯示一分鐘前的小時和分鐘
d1=`date-d"-1minute"+%H%M`
d2=`date+%M`
ipt=/sbin/iptables
ips=/tmp/ips.txt
block()
{
#將一分鐘前的日誌全部過濾出來並提取IP以及統計訪問次數
grep'$d1:'$logfile|awk'{print$1}'|sort-n|uniq-c|sort-n>$ips
#利用for迴圈將次數超過100的IP依次遍歷出來並予以封禁
foriin`awk'$1>100{print$2}'$ips`
do
$ipt-IINPUT-ptcp--dport80-s$i-jREJECT
echo"`date+%F-%T`$i">>/tmp/badip.log
done
}
unblock()
{
#將封禁後所產生的pkts數量小於10的IP依次遍歷予以解封
forain`$ipt-nvLINPUT--line-numbers|grep'0.0.0.0/0'|awk'$2<10{print$1}'|sort-nr`
do
$ipt-DINPUT$a
done
$ipt-Z
}
#當時間在00分以及30分時執行解封函式
if[$d2-eq"00"]||[$d2-eq"30"]
then
#要先解再封,因為剛剛封禁時產生的pkts數量很少
unblock
block
else
block
fi

判斷使用者輸入的是否為IP地址

方法1:

#!/bin/bash
functioncheck_ip(){
IP=$1
VALID_CHECK=$(echo$IP|awk-F.'$1<=255&&$2<=255&&$3<=255&&$4<=255{print"yes"}')
ifecho$IP|grep-E"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$">/dev/null;then
if[$VALID_CHECK=="yes"];then
echo"$IPavailable."
else
echo"$IPnotavailable!"
fi
else
echo"Formaterror!"
fi
}
check_ip192.168.1.1
check_ip256.1.1.1
方法2:

#!/bin/bash
functioncheck_ip(){
IP=$1
if[[$IP=~^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$]];then
FIELD1=$(echo$IP|cut-d.-f1)
FIELD2=$(echo$IP|cut-d.-f2)
FIELD3=$(echo$IP|cut-d.-f3)
FIELD4=$(echo$IP|cut-d.-f4)
if[$FIELD1-le255-a$FIELD2-le255-a$FIELD3-le255-a$FIELD4-le255];then
echo"$IPavailable."
else
echo"$IPnotavailable!"
fi
else
echo"Formaterror!"
fi
}
check_ip192.168.1.1
check_ip256.1.1.1
增加版:

加個死迴圈,如果IP可用就退出,不可用提示繼續輸入,並使用awk判斷。

#!/bin/bash
functioncheck_ip(){
localIP=$1
VALID_CHECK=$(echo$IP|awk-F.'$1<=255&&$2<=255&&$3<=255&&$4<=255{print"yes"}')
ifecho$IP|grep-E"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$">/dev/null;then
if[$VALID_CHECK=="yes"];then
return0
else
echo"$IPnotavailable!"
return1
fi
else
echo"Formaterror!Pleaseinputagain."
return1
fi
}
whiletrue;do
read-p"PleaseenterIP:"IP
check_ip$IP
[$?-eq0]&&break||continue
done