前言
本篇文章沒有太多的理論知識,主要分為基礎語法案例、常用工具型命令(重點:幫助我們完成復雜需求)、工作中常見的需求(實戰案例有源碼 , 工作中可以直接套用)模板函數的聲明與實現,建議花十分鐘閱讀一遍收藏即可,當工作中需要編寫 Shell 腳本直接套用案例中的腳本模板,足可滿足后端開發的大部分需求 。
作為一名后端程序員,如果不掌握基礎的 Shell 腳本 , 那么運維編寫的一些簡單的腳本根本無法看懂,也不便于與運維進行溝通交流 。掌握 Shell,可以幫助我們提高日常工作效率,比如快速構建部署項目、管理集群、監控服務器、定時清理日志文件或管理服務器等等 。
概述
Shell 是由 C 語言編寫而成,外號俗稱殼 。開發者如果想操作 Linux 系統內核,必須通過 Shell 腳本進行交互,解釋和執行用戶命令,不可以繞過 Shell 直接操作 Linux 內核 。Shell 是一門強大的編程語言,容易上手功能強大 。
Shell 解析器
Linux 中有幾種常見的解析器,后面的模板都是使用 Bash(最常用的解析器)解析器進行編寫,查看當前系統支持哪些解析器:
cat /etc/shells
查看當前系統使用的 Shell 解析器:
echo $SHELL
基礎語法與實操案例Shell 變量
對于后臺開發者 , 系統環境變量一定不會陌生,這里不做過多贅述 。Shell 變量分為兩種:系統變量、自定義變量 。
系統變量
常見的系統變量如下:
變量名
解釋
$PWD
腳本執行的當前所在目錄
$UID
當前操作的系統用戶 ID
$$
當前操作用戶的 PID
$#
當前腳本的參數個數
$*
當前腳本的所有參數
$0
當前執行程序的名稱
$n
當前程序的第 N 個參數
$HOME
當前程序的 home 目錄
$USER
查詢當前程序使用的操作用戶
自定義變量
1. 變量命令規則
變量名必須是以字母或下劃線字符“_”開頭,后面字母、數字或下劃線字符 。切記不用使用特殊符號 , 給自己帶來不必要的麻煩 。
2. 查看當前 Shell 所有的環境變量
3. 編寫自定義變量
# 變量名=值如:A=1等號兩邊不要有空格,如果值中間存在空格,請使用單引或者雙引號:A='張 三'# 撤銷變量unset A# 定義靜態變量 , 靜態變量不可以二次賦值,靜態變量不可以 unset 撤銷readonly B=2
4. 變量的作用域
普通的變量作用域為當前的執行程序,程序外部不可使用當前定義的變量 。通過可以把變量升級為全局環境變量,這樣當前系統所有程序都可以使用這個環境變量 。
創建測試腳本:
touch test.sh
賦值執行權限:
chmod u+x test.sh
編寫腳本:
vim test.sh
定義全局腳本(腳本內容如下):
export user_name="張三"
#!/bin/bashecho $user_name
5. 由于定義了全局變量,所以執行腳本可以正常輸出 $ 變量的值,反之腳本中定義的局部變量,其它腳本中不可以正常輸出結果 。
./test.sh
運算符
運算符的種類大致可以分為(直接上代碼示例)4 種 。
算數運算符
#!/bin/basha=10b=20# 加法val=`expr $a + $b`echo "a + b : $val"# 減法val=`expr $a - $b`echo "a - b : $val"# 乘法val=`expr $a * $b`echo "a * b : $val"# 除法val=`expr $b / $a`echo "b / a : $val"# 取余val=`expr $b % $a`echo "b % a : $val"# 等于if [ $a == $b ]thenecho "a 等于 b"fiif [ $a != $b ]thenecho "a 不等于 b"fi
關系運算符
#!/bin/basha=10b=20# 等于if [ $a -eq $b ]thenecho "$a -eq $b : a 等于 b"elseecho "$a -eq $b: a 不等于 b"fi# 不等于if [ $a -ne $b ]thenecho "$a -ne $b: a 不等于 b"elseecho "$a -ne $b : a 等于 b"fi# 大于if [ $a -gt $b ]thenecho "$a -gt $b: a 大于 b"elseecho "$a -gt $b: a 不大于 b"fi# 小于if [ $a -lt $b ]thenecho "$a -lt $b: a 小于 b"elseecho "$a -lt $b: a 不小于 b"fi# 大于等于if [ $a -ge $b ]thenecho "$a -ge $b: a 大于或等于 b"elseecho "$a -ge $b: a 小于 b"fi# 小于等于if [ $a -le $b ]thenecho "$a -le $b: a 小于或等于 b"elseecho "$a -le $b: a 大于 b"fi
布爾運算符
#!/bin/basha=10b=20# !非運算,跟 java 一樣if [ $a != $b ]thenecho "$a != $b : a 不等于 b"elseecho "$a == $b: a 等于 b"fi# 與運算,跟 java 里面的 && 一樣if [ $a -lt 100 -a $b -gt 15 ]thenecho "$a 小于 100 且 $b 大于 15 : 返回 true"elseecho "$a 小于 100 且 $b 大于 15 : 返回 false"fi# 或運算,與 java 里面的 || 同理if [ $a -lt 100 -o $b -gt 100 ]thenecho "$a 小于 100 或 $b 大于 100 : 返回 true"elseecho "$a 小于 100 或 $b 大于 100 : 返回 false"fiif [ $a -lt 5 -o $b -gt 100 ]thenecho "$a 小于 5 或 $b 大于 100 : 返回 true"elseecho "$a 小于 5 或 $b 大于 100 : 返回 false"fi
字符串運算符
#!/bin/basha="abc"b="efg"# 判斷字符串是否相等if [ $a = $b ]thenecho "$a = $b : a 等于 b"elseecho "$a = $b: a 不等于 b"fi# 判斷字符串不相等if [ $a != $b ]thenecho "$a != $b : a 不等于 b"elseecho "$a != $b: a 等于 b"fi# -n 判斷字符串長度是否不為 0if [ -n "$a" ]thenecho "-n $a : 字符串長度不為 0"elseecho "-n $a : 字符串長度為 0"fi# 與 -n 相反if [ -z $a ]thenecho "-z $a : 字符串長度為 0"elseecho "-z $a : 字符串長度不為 0"fi# $ 表示檢查字符串是否為空if [ $a ]thenecho "$a : 字符串不為空"elseecho "$a : 字符串為空"fi
流程控制
if else 不再做介紹,上述運算符案例中有大量使用,對于后端開發及其簡單,流程控制在程序用使用非常頻繁 。
case 語法直接套用
最后的 *) 表示默認模式,相當于 Java 中的 ,;; 表示命令序列結束 , 相當于 Java 中的 break 。
!/bin/bashcase $1 in"1")echo "張三";;"2")echo "李四";;*)echo "王二";;esac
for 循環
案例:從 1 加到 100 。
#!/bin/bashs=0for((i=0;i<=100;i++))dos=$[$s+$i]doneecho $s
while 循環
案例:從 1 加到 100 。
#!/bin/bashs=0i=1while [ $i -le 100 ]dos=$[$s+$i]i=$[$i+1]done# 輸出值echo $s
函數
Shell 腳本和其它編程語言類似,分為系統函數和自定義函數 。
系統函數
1.基本語法
basename 路徑 后綴
功能描述: 命令會刪掉所有的前綴包括最后一個(‘/’)字符,然后將字符串顯示出來 。
不加后綴:
加后綴:
如果腳本中需要獲取當前路徑的后綴名稱:
2.基本語法
dirname 文件絕對路徑
功能描述:從給定的包含絕對路徑的文件名中去除文件名(非目錄的部分),然后返回剩下的路徑(目錄的部分) 。
自定義函數
1. 基本語法:
[ function ] funname[()]{Action;[return int;]}
2. 經驗技巧
3. 案例實操
函數無返回值:計算兩個輸入參數的和 。
腳本源碼:
#!/bin/bashfunction sum(){s=0s=$[ $1 + $2 ]echo "$s"}# read 讀取控制臺的輸入,n1,n2 用于接收輸入內容 , -p:指定讀取值時的提示符; -t:指定讀取值時等待的時間(秒)read -p "Please input the number1: " n1;read -p "Please input the number2: " n2;# 調用方法sum $n1 $n2;
函數有返回值:計算兩個輸入參數的和(函數返回值,只能通過$?系統變量獲得) 。
#!/bin/bashfunction sum(){# read 讀取控制臺的輸入 , n1,n2 用于接收輸入內容,-p:指定讀取值時的提示符; -t:指定讀取值時等待的時間(秒)read -p "Please input the number1: " n1;read -p "Please input the number2: " n2;return $(($n1+$n2))}# 調用方法sumecho "計算兩個數字之和為 $? !"
常用的 Shell 工具
下面列舉的幾個命令非常實用,命令的具體使用方法請閱讀:Linux 命令大全,非常重要且命令參數太多,這里不做過多贅述 。
開箱即用的 Shell 腳本
請用 Shell 腳本寫出查找當前文件夾(/home)下所有的文本文件內容中包含有字符“shen”的文件名稱 。
grep -r "shen" /home | cut -d ":" -f 1
判斷用戶輸入的是否為 IP 地址:
#!/bin/bashfunction check_ip(){IP=$1VALID_CHECK=$(echo $IP|awk -F. '$1< =255&&$2<=255&&$3<=255&&$4/dev/null; thenif [ $VALID_CHECK == "yes" ]; thenecho "$IP available."elseecho "$IP not available!"fielseecho "Format error!"fi}check_ip 192.168.1.1check_ip 256.1.1.1
定時清空文件內容 , 定時記錄文件大?。?
#!/bin/bash#每小時執行一次腳本(任務計劃) , 當時間為 0 點或 12 點時,將目標目錄下的所有文件內#容清空 , 但不刪除文件 , #其他時間則只統計各個文件的大?。?桓鑫募?恍?,輸出到以時#間和日期命名的文件中,需要考慮目標目錄下二級、三級等子目錄的文件logfile=/tmp/`date +%H-%F`.logn=`date +%H`if [ $n -eq 00 ] || [ $n -eq 12 ]then#通過 for 循環,以 find 命令作為遍歷條件,將目標目錄下的所有文件進行遍歷并做相應操作for i in `find /data/log/ -type f`dotrue > $idoneelsefor i in `find /data/log/ -type f`dodu -sh $i >> $logfiledonefi
檢測網卡流量,并按規定格式記錄在日志中:
#!/bin/bash########################################################檢測網卡流量 , 并按規定格式記錄在日志中#規定一分鐘記錄一次#日志格式如下所示:#2019-08-12 20:40#ens33 input: 1234bps#ens33 output: 1235bps######################################################3while :do#設置語言為英文 , 保障輸出結果是英文,否則會出現 bugLANG=enlogfile=/tmp/`date +%d`.log#將下面執行的命令結果輸出重定向到 logfile 日志中exec >> $logfiledate +"%F %H:%M"#sar 命令統計的流量單位為 kb/s,日志格式為 bps,因此要*1000*8sar -n DEV 1 59|grep Average|grep ens33|awk '{print $2,"t","input:","t",$5*1000*8,"bps","n",$2,"t","output:","t",$6*1000*8,"bps"}'echo "####################"#因為執行 sar 命令需要 59 秒,因此不需要 sleepdone
計算文檔每行出現的數字個數 , 并計算整個文檔的數字總數:
#!/bin/bash##########################################################計算文檔每行出現的數字個數,并計算整個文檔的數字總數#########################################################使用 awk 只輸出文檔行數(截取第一段)n=`wc -l a.txt|awk '{print $1}'`sum=0#文檔中每一行可能存在空格,因此不能直接用文檔內容進行遍歷for i in `seq 1 $n`do#輸出的行用變量表示時,需要用雙引號line=`sed -n "$i"p a.txt`#wc -L 選項,統計最長行的長度n_n=`echo $line|sed s'/[^0-9]//'g|wc -L`echo $n_nsum=$[$sum+$n_n]doneecho "sum:$sum"
殺死所有腳本:
#!/bin/bash#################################################################有一些腳本加入到了 cron 之中 , 存在腳本尚未運行完畢又有新任務需要執行的情況 , #導致系統負載升高,因此可通過編寫腳本,篩選出影響負載的進程一次性全部殺死 。################################################################ps aux|grep 指定進程名|grep -v grep|awk '{print $2}'|xargs kill -9
從 FTP 服務器下載文件:
#!/bin/bashif [ $# -ne 1 ]; thenecho "Usage: $0 filename"fidir=$(dirname $1)file=$(basename $1)ftp -n -v << EOF# -n 自動登錄open 192.168.1.10# ftp 服務器user admin passwordbinary# 設置 ftp 傳輸模式為二進制,避免 MD5 值不同或.tar.gz 壓縮包格式錯誤cd $dirget "$file"EOF
監測 Nginx 訪問日志 404 情況:
#場景:#1.訪問日志文件的路徑:/data/log/access.log#2.腳本死循環 , 每 10 秒檢測一次,10 秒的日志條數為 300 條,出現 404 的比例不低于 10%(30 條)則需要重啟 php-fpm 服務#3.重啟命令為:/etc/init.d/php-fpm restart#!/bin/bash############################################################監測 Nginx 訪問日志 404 情況,并做相應動作###########################################################log=/data/log/access.logN=30 #設定閾值while :do#查看訪問日志的最新 300 條 , 并統計 404 的次數err=`tail -n 300 $log |grep -c '404" '`if [ $err -ge $N ]then/etc/init.d/php-fpm restart 2> /dev/null#設定 60s 延遲防止腳本 bug 導致無限重啟 php-fpm 服務sleep 60fisleep 10done
自動屏蔽訪問網站頻繁的 IP
方法 1:根據訪問日志(Nginx 為例) 。
#!/bin/bashDATE=$(date +%d/%b/%Y:%H:%M)ABNORMAL_IP=$(tail -n5000 access.log |grep $DATE |awk '{a[$1]++}END{for(i in a)if(a[i]>100)print i}')#先 tail 防止文件過大,讀取慢,數字可調整每分鐘最大的訪問量 。awk 不能直接過濾日志 , 因為包含特殊字符 。for IP in $ABNORMAL_IP; doif [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; theniptables -I INPUT -s $IP -j DROPfidone
方法 2:通過 TCP 建立的連接 。
#!/bin/bashABNORMAL_IP=$(netstat -an |awk '$4~/:80$/ && $6~/ESTABLISHED/{gsub(/:[0-9]+/,"",$5);{a[$5]++}}END{for(i in a)if(a[i]>100)print i}')#gsub 是將第五列(客戶端 IP)的冒號和端口去掉for IP in $ABNORMAL_IP; doif [ $(iptables -vnL |grep -c "$IP") -eq 0 ]; theniptables -I INPUT -s $IP -j DROPfidone
實現 SSH 免交互執行命令:
登錄腳本:# cat login.exp#!/usr/bin/expectset ip [lindex $argv 0]set user [lindex $argv 1]set passwd [lindex $argv 2]set cmd [lindex $argv 3]if { $argc != 4 } {puts "Usage: expect login.exp ip user passwd"exit 1}set timeout 30spawn ssh $user@$ipexpect {"(yes/no)" {send "yesr"; exp_continue}"password:" {send "$passwdr"}}expect "$user@*"{send "$cmdr"}expect "$user@*"{send "exitr"}expect eof
執行命令腳本:寫個循環可以批量操作多臺服務器 。
#!/bin/bashHOST_INFO=user_info.txtfor ip in $(awk '{print $1}' $HOST_INFO)douser=$(awk -v I="$ip" 'I==$1{print $2}' $HOST_INFO)pass=$(awk -v I="$ip" 'I==$1{print $3}' $HOST_INFO)expect login.exp $ip $user $pass $1done
Linux 主機 SSH 連接信息:
# cat user_info.txt192.168.1.120 root 123456
創建 10 個用戶,并分別設置密碼模板函數的聲明與實現,密碼要求 10 位且包含大小寫字母以及數字,最后需要把每個用戶的密碼存在指定文件中:
#!/bin/bash###############################################################創建 10 個用戶,并分別設置密碼,密碼要求 10 位且包含大小寫字母以及數字#最后需要把每個用戶的密碼存在指定文件中#前提條件:安裝 mkpasswd 命令###############################################################生成 10 個用戶的序列(00-09)for u in `seq -w 0 09`do#創建用戶useradd user_$u#生成密碼p=`mkpasswd -s 0 -l 10`#從標準輸入中讀取密碼進行修改(不安全)echo $p|passwd --stdin user_$u#常規修改密碼echo -e "$pn$p"|passwd user_$u#將創建的用戶及對應的密碼記錄到日志文件中echo "user_$u $p" >> /tmp/userpassworddone
掃描主機端口狀態:
#!/bin/bashHOST=$1PORT="22 25 80 8080"for PORT in $PORT; doif echo &>/dev/null > /dev/tcp/$HOST/$PORT; thenecho "$PORT open"elseecho "$PORT close"fidone用 Shell 打印示例語句中字母數小于 6 的單詞#示例語句:#Bash also interprets a number of multi-character options.#!/bin/bash###############################################################Shell 打印示例語句中字母數小于 6 的單詞##############################################################for s in Bash also interprets a number of multi-character options.don=`echo $s|wc -c`if [ $n -lt 6 ]thenecho $sfidone
【十分鐘帶你學會 Shell 腳本】本文到此結束,希望對大家有所幫助 。
- ?張馨予李晨分手真相 帶你了解清楚真相
- 1分鐘帶你了解絨球花的真正模樣 ?絨球花圖片
- 用1分鐘學會在Word中繪制超整齊斜表頭,快到飛起~
- 職場新人一定要學會這10個經典Word技巧,可以大大提高工作效率
- ?最能打動女生的道歉,學會這15個道歉金句
- 12個PS小技巧教程,學會離PS高手更進一步!
- 職場新人怎么學會調節情緒
- 電腦又卡又慢怎么辦?學會這6個操作,瞬間提升電腦速度
- 六點帶你了解農貿市場管理 ?農貿市場管理
- 盤帶你獵頭和企業HR的本質區別 ?獵頭和hr的區別
