Shell從入門到精通

程式設計我的一切發表於2021-01-28

熟悉基本shell操作不僅是運維的基本功,對於開發來說也是多多益善,我在學習的過程中,總結了十個練手的小demo,並附上涉及的知識點,僅供娛樂。


1. 多執行緒ping監控,檢查同一網段的IP是否連通

  • Linux 系統中有一個特殊的裝置/dev/null,這是一個黑洞。無論往該檔案中寫入多少資料,都會被系統吞噬、丟棄。如果有些輸出資訊是我們不再需要的, 則可以使用重定向將輸出資訊匯入該裝置檔案中。注意:資料一旦匯入黑洞將無法找回。
  • 重定向: > 是覆蓋重定向, >> 是累加重定向
  • $0 這個程式的執行名字
    $n 這個程式的第n個引數值,n=1..9
    $* 這個程式的所有引數,此選項引數可超過9個。
    $# 這個程式的引數個數
    $$ 這個程式的PID(指令碼執行的當前程式ID號)
    $! 執行上一個背景指令的PID(後臺執行的最後一個程式的程式ID號)
    $? 執行上一個指令的返回值 (顯示最後命令的退出狀態。0表示沒有錯誤,其他任何值表明有錯誤)
    $- 顯示shell使用的當前選項,與set命令功能相同
    $@ 跟$*類似,但是可以當作陣列用
  • &  表示任務在後臺執行,如要在後臺執行redis-server,則有  redis-server &
    && 表示前一條命令執行成功時,才執行後一條命令 ,如 echo '1‘ && echo '2'
    | 表示管道,上一條命令的輸出,作為下一條命令引數,如 echo 'yes' | wc -l
    || 表示上一條命令執行失敗後,才執行下一條命令,如 cat nofile || echo "fail"

  • ping 命令語法

    ping [-dfnqrRv][-c<完成次數>][-i<間隔秒數>][-I<網路介面>][-l<前置載入>][-p<範本樣式>][-s<資料包大小>][-t<存活數值>][主機名稱或IP地址]
#!/bin/bash
#使用&開啟後臺程式
net="101.200.35"

mult_ping() {
        ping -c2 -i0.2 -W1 $1 &>/dev/null
        if [ $? -eq 0 ];then
                echo "$1 is up"
        else
                echo "$1 is down"
        fi
}

for i in {0..255}
do
        mult_ping $net.$i &
done
wait
                              

2.進度條功能顯示

  • 常見系統預設變數
#!/bin/bash

trap 'kill $!' INT
# 定義寬度為50的進度條
# 輸出完成後將/r游標切換到行首,準備下一次進度條顯示
bar () {
        while :
                pound=""
                for ((i = 47; i>=1; i-- ))
                do      
                        pound += #
                        printf "|%s%${i}s|\r" "$pound"
                        sleep 0.2
                done    
                
        }       
        
# 呼叫函式,顯示進度符號,直到複製結束kill進度函式
bar &
cp -r $1 $2
kill $!
echo "複製結束"

3. Linux建立程式的三種方式

  1. fork
    通常情況下在系統中通過相對路徑或絕對路徑執行一個命令時,都會由父程式開啟一個子程式,當子程式結束後再返回父程式,這種行為過程就叫作fork。當指令碼中正常呼叫一個外部命令 1或其他指令碼時,都會fork一個子Shell程式,我們的命令會執行在這個子Shell中。
  2. exec
    使用 exec 方式呼叫其他命令或指令碼時,系統不會開啟子程式,而是使用新的程式替換當前的 Shell 環境,因為當前 Shell 環境被替換了,所以當 exec 呼叫的程式結束後,當前環境會被關閉。但是有一個特例,當 exec 後面的引數是檔案重定向時,不會替換當前 Shell 環境,指令碼後續的其他命令也不會受到任何影響。
  3. source或 . (點)
    使用 source 命令或.(點)可以不開啟子 Shell,而在當前 Shell 環境中將需要執行的命令載入進來,執行完載入的命令後,繼續執行指令碼中後續的指令。

分析工具:pstree 程式樹

4. 控制程式數量——檔案描述符和命名管道

檔案描述符

檔案描述符是一個非負整數,而核心需要通過這個檔案描述符才可以訪問檔案。當我們在系統中開啟已有的檔案或新建檔案時,核心每次都會給特定的程式返回一個檔案描述符,當程式需要對檔案進行讀或寫操作時,都要依賴這個檔案描述符進行。檔案描述符就像一本書的目錄頁數(也叫索引),通過這個索引可以找到需要的內容。在 Linux 或類 UNIX系統中核心預設會為每個程式建立三個標準的檔案描述符,分別是 0(標準輸入)、 1(標準輸出)和 2(標準錯誤)。通過檢視/proc/PID 號/fd/目錄下的檔案,就可以檢視每個程式擁有的所有檔案描述符。

建立檔案描述符:

exec 檔案描述符 <> 檔名

呼叫檔案描述符語法格式:

&檔案描述符

關閉檔案描述符:

exec 檔案描述符<&-
exec 檔案描述符>&-

命名管道

管道是程式間通訊的一種方式,匿名管道,使用|符號就可以建立一個匿名管道,顧名思義,系統會自動建立一個可以讀寫資料的管道,但是這個管道並沒有名稱。一個程式往管道中寫資料,另一個程式就可以從管道中讀取資料。但是匿名管道僅可以實現父程式與子程式之間的資料交換,能不能實現任意兩個無關的程式之間的通訊呢?答案是肯定的,使用命名管道,也叫FIFO1檔案。

命名管道的特徵:

  •  FIFO 檔案由命令建立(mknod 或 mkfifo 命令),可以在檔案系統中直接看到。
  • 寫入管道的資料一旦被讀取後,就不可以再重複讀取。
  • 程式往命名管道中寫資料時,如果沒有其他程式讀取資料,則寫程式會被阻塞。
  • 程式嘗試從命名管道中讀取資料時,如果管道中沒有資料,則讀程式會被阻塞。
  • 命名管道中的資料常駐記憶體,並不實際寫入磁碟,讀寫效率會更高。

5. 可任意控制程式數量的多執行緒ping

第一個demo中,通過 & 開啟任意數量執行緒進行ping,但是這裡的執行緒不可控。我們用上面的檔案描述符和命名管道的知識,寫一段可控的多執行緒ping。

#!/bin/bash
  
pipefile=/tmp/procs_$$.temp
num=10
net="101.200.35"

multi_ping() {
        ping -c2 -i0.2 -W1 $1 &>/dev/null
        if [ $? -eq 0 ];then
                echo "$1 is up"
        else
                echo "$1 is down"
        fi
}
# 建立命名管道檔案,建立其檔案描述符,通過重定向將資料匯入管道檔案
mkfifo $pipefile
exec 12<>$pipefile
for i in `seq $num`
do
        echo "" >&12 &
done
# 成功讀取命名管道中的資料後開啟新的程式
# 所有內容讀取完之後read被阻塞,無法再啟動新的程式
# 等待前面啟動的執行緒結束後,繼續往管道檔案中寫入資料,釋放阻塞,再次開啟新的執行緒
for j in {1..254}
do
        read -u12
        {
                multi_ping $net.$j
                echo "" >&12
        } &
done
wait
rm -rf $pipfile

6. sed爬蟲批量下載美女圖片

  • sed命令彙總




  • sed 是逐行處理軟體,我們可能僅輸入了一條 sed 指令,但系統會將該指令應用在所有匹配的資料行上,因此相同的指令會被反覆執行 N 次,這取決於匹配到的資料有幾行。
  • 預設 sed 不支援擴充套件正則,如果希望使用擴充套件正則匹配資料,可以使用-r 引數。
  • sed 程式使用=指令可以顯示行號,結合條件匹配,可以顯示特定資料行的行號。
  • 在 sed 中支援使用感嘆號(!)對匹配的條件進行取反操作。

下載思路:用curl獲取網站原始碼+sed資料清洗獲取圖片地址+wget下載儲存

#!/bin/bash
# 爬取美女圖片

# 定義要爬取的網站和儲存的檔案
page="https://tieba.baidu.com/p/4420470629"
URL="beau.txt"
# 將網站原始碼儲存到檔案中
curl -s https://tieba.baidu.com/p/4420470629 > $URL
# 對原始碼資料過濾清洗,獲取種子的URL連結
echo -e "\033[32m 正在獲取種子 URL,請稍後...\033[0m"
sed -i '/<img/!d' $URL #刪除不包含<img 的行
sed -i 's/.*src="//' $URL #刪除 src="及其前面的所有內容
sed -i 's/".*//' $URL #刪除雙引號及其後面的所有內容
echo

#利用迴圈批量下載所有圖片資料
#wget 為下載工具,其引數選項描述如下:
# -P 指定將資料下載到特定目錄(prefix)
# -c 支援斷點續傳(continue)
# -q 不顯示下載過程(quiet)
echo -e "\033[32m 正在批量下載種子資料,請稍後...\033[0m"
for i in $(cat $URL)
do
        wget -P tempPhoto/ -c $i
done

這種知識最基本的爬蟲,對於反爬蟲的網站就嗝屁了,對於那種非同步載入的也沒辦法,總之,就是比較弱。

7. sed隨機點名器

做一個網際網路大佬的隨機點名器

#!/bin/bash
#按 Ctrl+C 組合鍵時:恢復游標,恢復終端屬性,清屏,退出指令碼
#防止程式意外中斷導致的終端混亂
trap 'tput cnorm;stty $save_property;clear;exit' 2
#定義變數:人員列表檔名,檔案的行數,螢幕的行數,螢幕的列數
name_file="name.txt"
line_file=$(sed -n '$=' $name_file)
line_screen=`tput lines`
column_screen=`tput cols`
#設定終端屬性
save_property=$(stty -g) #儲存當前終端所有屬性
tput civis #關閉游標
#隨機抽取一個人名(隨機點名)
while :
do
        tmp=$(sed -n "$[RANDOM%line_file+1]p" $name_file)
        #隨機獲取檔案的某一行人名
        tput clear #清屏
        tput cup $[line_screen/4] $[column_screen/4]
        echo -e "\033[3;5H 隨機點名器(按 P 停止): "
        echo -e "\033[4;5H#############################"
        echo -e "\033[5;5H# #"
        echo -e "\033[6;5H#\t\t$tmp\t\t#"
        echo -e "\033[7;5H# #"
        echo -e "\033[8;5H#############################"
        sleep 0.1
        stty -echo
        read -n1 -t0.1 input
        if [[ $input == "p" || $input == "P" ]];then
                break
        fi
done
tput cnorm #恢復游標
stty $save_property #恢復終端屬性

8.系統效能監控指令碼

  • awk語法格式
  • awk變數
  • awk條件匹配
  • awk 可以通過-v(variable) 選項設定或者修改變數的值,我們可以使用-v 定義新的變數,也可以使用該選項修改內建變數的值。
  • 使用[]定義分隔符集合,同時設定多個分隔符。比如使用[:,-]表示以冒號(:)、逗號(,)或者橫線(-)為分隔符
  • [-F|-f|-v]   大引數,-F指定分隔符,-f呼叫指令碼,-v定義變數 var=value

 

9.監控網路連線狀態

  • ss語法格式
#!/bin/bash
# 監控網路連線狀態
#所有 TCP 連線的個數
TCP_Total=$(ss -s | awk '$1=="TCP"{print $2}')
#所有 UDP 連線的個數
UDP_Total=$(ss -s | awk '$1=="UDP"{print $2}')
#所有 UNIX sockets 連線個數
Unix_sockets_Total=$(ss -ax | awk 'BEGIN{count=0} {count++} END{print
count}')
#所有處於 Listen 監聽狀態的 TCP 埠個數
TCP_Listen_Total=$(ss -antlpH | awk 'BEGIN{count=0} {count++} END{print
count}')
#所有處於 ESTABLISHED 狀態的 TCP 連線個數
TCP_Estab_Total=$(ss -antpH | awk 'BEGIN{count=0} /^ESTAB/{count++}
END{print count}')
#所有處於 SYN-RECV 狀態的 TCP 連線個數
TCP_SYN_RECV_Total=$(ss -antpH | awk 'BEGIN{count=0} /^SYN-RECV/{count++}
END{print count}')
#所有處於 TIME-WAIT 狀態的 TCP 連線個數
TCP_TIME_WAIT_Total=$(ss -antpH | awk 'BEGIN{count=0} /^TIME-WAIT/{count++}
END{print count}')
#所有處於 TIME-WAIT1 狀態的 TCP 連線個數
TCP_TIME_WAIT1_Total=$(ss -antpH | awk 'BEGIN{count=0}
/^TIME-WAIT1/{count++} END{print count}')
#所有處於 TIME-WAIT2 狀態的 TCP 連線個數
TCP_TIME_WAIT2_Total=$(ss -antpH | awk 'BEGIN{count=0}
/^TIME-WAIT2/{count++} END{print count}')
#所有遠端主機的 TCP 連線次數
TCP_Remote_Count=$(ss -antH | awk '$1!~/LISTEN/{IP[$5]++} END{ for(i in
IP){print IP[i],i} }' | sort -nr)
#每個埠被訪問的次數
TCP_Port_Count=$(ss -antH | sed -r 's/ +/ /g' | awk -F"[ :]"
'$1!~/LISTEN/{port[$5]++} END{for(i in port){print port[i],i}}' | sort -nr)
#定義輸出顏色
SUCCESS="echo -en \\033[1;32m" #綠色
NORMAL="echo -en \\033[0;39m" #黑色
#顯示 TCP 連線總數
tcp_total(){
        echo -n "TCP 連線總數: "
        $SUCCESS
        echo "$TCP_Total"
        $NORMAL
} 
#顯示處於 LISTEN 狀態的 TCP 埠個數
tcp_listen(){
        echo -n "處於 LISTEN 狀態的 TCP 埠個數: "
        $SUCCESS
        echo "$TCP_Listen_Total"
        $NORMAL
} 
#顯示處於 ESTABLISHED 狀態的 TCP 連線個數
tcp_estab(){
        echo -n "處於 ESTAB 狀態的 TCP 連線個數: "
        $SUCCESS
        echo "$TCP_Estab_Total"
        $NORMAL
} 
#顯示處於 SYN-RECV 狀態的 TCP 連線個數
tcp_syn_recv(){
        echo -n "處於 SYN-RECV 狀態的 TCP 連線個數: "
        $SUCCESS
        echo "$TCP_SYN_RECV_Total"
        $NORMAL
} 
#顯示處於 TIME-WAIT 狀態的 TCP 連線個數
tcp_time_wait(){
        echo -n "處於 TIME-WAIT1 狀態的 TCP 連線個數: "
        $SUCCESS
        echo "$TCP_TIME_WAIT1_Total"
        $NORMAL
} 
#顯示處於 TIME-WAIT2 狀態的 TCP 連線個數
tcp_time_wait2(){
        echo -n "處於 TIME-WAIT2 狀態的 TCP 連線個數: "
        $SUCCESS
        echo "$TCP_TIME_WAIT2_Total"
        $NORMAL
} 
#顯示 UDP 連線總數
udp_total(){
        echo -n "UDP 連線總數: "
        $SUCCESS
        echo "$UDP_Total"
        $NORMAL
} 
#顯示 UNIX sockets 連線總數
unix_total(){
        echo -n "Unix sockets 連線總數: "
        $SUCCESS
        echo "$Unix_sockets_Total"
        $NORMAL
} 
#顯示每個遠端主機的訪問次數
remote_count(){
        echo "每個遠端主機與本機的併發連線數: "
        $SUCCESS
        echo "$TCP_Remote_Count"
        $NORMAL
} 
#顯示每個埠的併發連線數
port_count(){
        echo "每個埠的併發連線數: "
        $SUCCESS
        echo "$TCP_Port_Count"
        $NORMAL
}

print_info(){
        echo -e "------------------------------------------------------"
        $1
}
print_info tcp_total
print_info tcp_listen
print_info tcp_estab
print_info tcp_syn_recv
print_info tcp_time_wait
print_info tcp_time_wait1
print_info tcp_time_wait2
print_info udp_total
print_info unix_total
print_info remote_count
print_info port_count
echo -e "------------------------------------------------------"

 

 

參考

相關文章