熟悉基本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創建進程的三種方式
- fork
通常情況下在系統中通過相對路徑或絕對路徑執行一個命令時,都會由父進程開啟一個子進程,當子進程結束后再返回父進程,這種行為過程就叫作fork。當腳本中正常調用一個外部命令 1或其他腳本時,都會fork一個子Shell進程,我們的命令會運行在這個子Shell中。 - exec
使用 exec 方式調用其他命令或腳本時,系統不會開啟子進程,而是使用新的程序替換當前的 Shell 環境,因為當前 Shell 環境被替換了,所以當 exec 調用的程序結束后,當前環境會被關閉。但是有一個特例,當 exec 后面的參數是文件重定向時,不會替換當前 Shell 環境,腳本后續的其他命令也不會受到任何影響。 - 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 "------------------------------------------------------"
參考