shell 基礎學習
Shell工具
以下所有工具命令不會直接改變原文件,需要用重定向到文件中才能保存
grep
行過濾工具:grep [選項] '關鍵字' 文件名
-n 行號
-i 忽略大小寫ignore
-v 取反
^key 以...開頭
key$ 以...結尾
^$ 匹配空行
-A 后幾行after
-B 前幾行before
-C 上下文context
-w 匹配單詞 ('hello' 不會匹配到'helloworld') 精確匹配
-o 只打印出關鍵字本身
-c 統計匹配的行數
-e 使用正則
cut
列截取工具:cut 選項 文件名
-c 以字符為單位截取
-d 自定義分隔符,默認為'\t'
-f 與-d一起使用,指定截取哪個區域
cut -d: -f1,6,7 文件 以:分割,截取第1,6,7列
cut -c1-4 文件 截取文件中每行的第1-4個字符(同理5-表示第5開始到結尾)
(head tail) -1
sort
sort用于排序,它將文件的每一行作為一個單位,從首字符向后,依次按ASCII碼值進行比較,最后將他們升序輸出
-u 去除重復行(不管重復行連續與否)
-r 降序排列,默認升序
-o 將排序結果輸出到文件中,類似重定向符號>
-n 以數字排序,默認是按字符排序
-t 分隔符
-k 第N列(與-t配合類似于cut的-d -f)
-b 忽略前導空格
-R 隨機排序,每次運行結果不同
uniq
用于去除連續的重復行
-i 忽略大小寫
-c 統計重復行次數
-d 只顯示重復行
tee
從標準輸入讀取并寫入到標準輸出和文件,即:雙向覆蓋重定向(屏幕輸出|文本輸入)
-a 雙向追加重定向
echo hello world | tee 文件
屏幕顯示hello world 文件內容也是hello world
diff
用于逐行比較文件的不同
注意:diff描述兩個文件不同的方式是告訴我們怎樣改變第一個文件之后與 第二個文件匹配
diff [選項] 文件1 文件2
-b 不檢查空格
-B 不檢查空白行
-i 不檢查大小寫
-W 忽略所有空格
--normal 正常格式顯示(默認)
-c 上下文格式顯示
-u 合并格式顯示
正常格式顯示:c=change d=delete a=add
上下文格式:!改變 -刪除 +增加
合并格式:+ - 只顯示第一個文件需要增加和刪除的內容
比較兩個目錄的不同:
- 默認情況下也會比較兩個目錄里相同文件的內容
- 如果只需要比較兩個目錄里文件的不同,不需要進一步比較文件內容,需要加-q選項
注意touch dir/file{1..5}會在該目錄生成5個文件
以一個文件為標準,修改其他文件,并且修改的地方較多時,我們可以通過打補丁的方式將文件1更新成文件2
- 先找出文件不同,然后輸出到一個文件 diff -uN (-N 將不存在的文件當做空文件) file1 file2 > file.patch
- 將不同內容打補丁到文件 patch file1 file.patch
- 測試驗證
- paste
用于合并文件行
-d 自定義間隔符,默認是tab
-s 串行處理,非并行
tr
用于字符轉換,替換和刪除,主要用于刪除文件中的控制字符或進行字符轉換
用法1:命令的執行結果交給tr處理,其中string1用于查詢,string2用于轉換處理
# commands | tr 'string1' 'string2'
用法2:tr處理的內容來自文件,記住要使用"<"標準輸入
# tr 'string1' 'string2' < filename
用法3:匹配string1進行相應操作,如刪除操作
# tr options 'string1' < filename
options
-d 刪除字符串1中所有輸入字符
-s 刪除所有連續重復出現的字符序列,只保留第一個,即將重復出現的字符串壓縮為一個字符 aaaaabbbbb ab
壓縮空格有奇效
常用的匹配字符串:一個對一個的替換
a-z或[:lower:] 匹配所有小寫字母
A-Z或[:upper:] 匹配所有大寫字母
0-9或[:digit:] 匹配所有數字
[:alnum:] 匹配所有字母和數字
[:alpha:] 匹配所有字母
[:blank:] 匹配所有水平空白
[:punct:] 匹配所有標點符號
[:space:] 匹配所有水平或垂直的空格
[:cntrl:] 匹配所有控制字符 \f \n \r \t
:set list 在vim中會顯示控制字符
'' 或'[]'包住多個要替換的字符都是一樣的
常用快捷鍵
^c 終止前臺運行的程序
^z 將前臺運行的程序掛起到后臺 stopped (命令 & 表示后臺運行)
^d 退出 等價exit
^l 清屏
^a |home 光標移到命令行的最前端
^e |end 光標移到命令行的后端
^u 刪除光標前所有字符
^k 刪除光標后所有字符
^r 搜索歷史命令
常用通配符
* 匹配0或多個任意字符
? 匹配任意單個字符
[list] 匹配list中的任意單個字符,或一組單個字符 eg: file[1-12]表示file1和file2
[!list] 匹配除了list中的任意單個字符
{string1,string2,...} 匹配string1,string2或更多字符串 file{1..12}表示file1到file12
{1..6..2}表示1到6的數間隔為2的列表
bash中的引號
雙引號"" 會把引號的內容當成整體來看待,允許通過$符號引用其他變量值
單引號'' 會把引號的內容當成整體來看待,禁止引用其他變量值,shell中特殊符號都被視為普通字符
反撇號`` 反撇號和$()一樣,引號或括號里的命令會優先執行,如果存在嵌套,反撇號不能用,只能用$()
sed
-i 原地刪除 eg: sed -i "/line_number/d" file_name
Shell變量
- shell的基本語法結構
變量定義,條件判斷,循環語句(for, until, while),分支語句,函數和數組
- 基本正則表達式
- 文件處理三劍客:grep, sed, awk的使用
- 使用shell腳本完成一些較復雜的任務,如服務搭建,批量處理等
以上為基本內容,還有很多更深更難的語法需要擴充學習
#! /bin/env bash 指定解釋器,env bash可以找到環境變量
標準執行方式:
(1) chmod +x 腳本名字
(2) 絕對/相對路徑
非標準的:沒有可執行權限也可以執行腳本
bash/sh/dash 腳本名字
+x 可以顯示執行過程 (用于調試!!!)
+n 用于查看腳本語法問題
source命令 # 讀取該文件,獲知其內容,使其在當前終端定義并生效
變量名區分大小寫
變量名和值不要加入特殊字符(有空格的用括號括起來)
變量名不能以數字開頭
等號兩邊不能有任何空格
定義變量基本方式
A=1234567
echo A
echo {A}
echo {A:2:4} 3456 切出片段,以第3個字母為起點,長度為4 切片
命令執行結果賦值給變量
A=`hostname`
A=$(hostname)
交互式定義變量
讓用戶自己給變量賦值,或來自文件( < filename)
read [選項] 變量名
-p 定義提示用戶的信息
-n 定義字符數(限制變量值的長度)
-s 不顯示(不顯示用戶輸入的內容)
-t 定義超時時間,默認單位為妙(限制用戶輸入變量值的超時時間)
declare 定義有類型的變量
給變量做一些限制,固定變量的類型,比如:整型、只讀
用法:declare 選項 變量名=變量值
-i 將變量看成整數 declare -i A=123
-r 定義只讀變量 declare -r B=hello
-a 定義普通數組;查看普通數組
-A 定義關聯數組;查看關聯數組
-x 將變量通過環境導出 declare -x AAA=123456 等于 export AAA=123456
unset 取消變量
變量的分類
本地變量:當前用戶自定義的變量,當前進程中有效,其他進程及當前進程的子進程無效。
環境變量:當前進程有效,并且能被子進程調用
- env查看當前用戶的環境變量
- set查詢當前用戶的所有變量(臨時變量與環境變量)
- export 變量名=變量值 或者 變量名=變量值; export 變量名
全局變量:全局所有的用戶和程序都能調用,且繼承,新建的用戶也默認能調用
$HOME/.bashrc 當前用戶的bash信息,用戶登錄時讀取 定義別名、umask、函數等
$HOME/.bash_profile 當前用戶的環境變量,用戶登錄時讀取
$HOME/.bash_logout 當前用戶退出當前shell時最后讀取 定義用戶退出時執行的程序等
$etc/bashrc 全局的bash信息,所有用戶都生效
$etc/profile 全局環境變量信息 系統和所有用戶都生效
以上文件修改后,都需要重新source讓其生效或者退出重新登錄
用戶登錄系統讀取相關文件的順序:
1. /etc/profile
2. $HOME/.bash_profile
3. $HOME/.bashrc
4. $etc/.bashrc
5. $HOME/.bash_logout
一般全局與局部沖突,以局部為主,如果在讀取全局profile前設置局部變量,則局部變量被全局覆蓋
系統變量(內置bash中的變量):shell本身已經固定好了它的名字和作用
$? 上一條命令執行后返回的狀態,狀態值為0表示執行正常,非0表示執行異常或錯誤
$0 當前執行的程序或腳本名
$# 腳本后面接的參數的個數
$* 腳本后面所有參數,參數當成一個整體輸出,每一個變量參數之間以空格隔開
$@ 腳本后面所有參數,參數是獨立的,也是全部輸出
$1-$9 腳本后面的位置參數,$1表示第1個位置參數,依次類推
$(10)-$(n) 擴展位置參數,第10個位置變量必須用{}大括號括起來(2位數字以上括起來)
$$ 當前所在進程的進程號,如echo $$
$! 后臺運行的最后一個進程號(當前終端)
!$ 調用最后一條命令歷史中的參數
jobs 查看后臺進程
kill -9 %數字 # 終止這個后臺進程(數字為job number)
簡單四則運算
$(()) echo $((1+1))
$[] echo $[10-5]
expr expr 10 / 5 # 要有空格且*要轉義 不能求**冪
let n=1;let n+=1 等價于 let n=n+1 # 省略$
當要求小數運算時 只能有bc eg:echo 1+1.5|bc 或 bc模型輸入
條件判斷語法
- 格式1:test 條件表達式
- 格式2:[ 條件表達式 ]
- 格式3:[[ 條件表達式 ]] 支持正則
更多的判斷,man test去查看,很多的參數都用來進行條件判斷
(一)判斷文件類型
-e 判斷文件是否存在(任何類型文件)
-f 判斷文件是否存在并且是一個普通文件
-d 判斷文件是否存在并且是一個目錄
-L 判斷文件是否存在并且是一個軟連接文件
-b 判斷文件是否存在并且是一個塊設備文件
-S 判斷文件是否存在并且是一個套接字文件
-c 判斷文件是否存在并且是一個字符設備文件
-p 判斷文件是否存在并且是一個命名管道文件
(二)判斷文件權限
-r 當前用戶對其是否可讀
-w 當前用戶對其是否可寫
-x 當前用戶對其是否可執行
-u 是否有suid,高級權限冒險位
-g 是否sgid,高級權限強制位
-k 是否有t位,高級權限粘滯位
-s 判斷文件是否存在并且不為空
! -s 判斷文件是否存在并且為空
(三)判斷文件新舊
這里的新舊是指文件的修改時間
file1 -nt file2 比較file1是否比file2新
file1 -ot file2 比較file1是否比file2舊
file1 -ef file2 比較是否為同一個文件,或者用于判斷硬鏈接,是否指向同一個inode
(四)判斷整數
-eq 相等 如:if [ "$a" -eq "$b" ]
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
< 小于(需要雙括號),如:(("$a" < "$b"))
<= 小于等于(需要雙括號),如:(("$a" <= "$b"))
> 大于(需要雙括號),如:(("$a" > "$b"))
>= 大于等于(需要雙括號),如:(("$a" >= "$b"))
(五)判斷字符串
-z 判斷是否為空字符串,字符串長度為0則成立
-n 判斷是否為非空字符串,字符串長度不為0則成立
string1 = string2 判斷字符串是否相等
string != string2 判斷字符串是否不等
字符串最好用""括起來,形成一個整體
(六)多重條件判斷
-a 和 && 邏輯與 [ 1 -eq 1 -a 1 -ne 0 ] [ 1 -eq 1 ] && [ 1 -ne 0 ]
-o 和 || 邏輯或 [ 1 -eq 1 -o 1 -ne 1 ] [ 1 -eq 1 ] || [ 1 -ne 1 ]
&& 前面的表達式為真,才會執行后面的代碼
|| 前面的表達式為假,才會執行后面的代碼
; 只用于分割命令或表達式,不考慮前面的語句是否正確執行,都會執行;號后面的內容
多個條件在一起時要從左往右依次按&&和||的方式判斷
類c風格的數值比較
在(( ))中,=表示賦值,==表示判斷,!=表示不等于,<
字符串比較
注意:雙引號引起來,看作一個整體;= 和 == 在 [ 字符串 ] 比較中都表示判斷(用于字符串的判斷)
[ ] 和 [[ ]]的區別
當字符串為空時,[[ ]]可以不將字符串用雙引號括起來且不報錯,[]不行 eg [ $A = hell ]
&& 寫在 [[ ]]之間不會報錯
流程控制語句
if [ condition ];then
command
elif [ condition2 ];then
command
else
command
fi
[ 條件 ] && command
上圖選擇的路很多,能走的只有一條
pgrep命令:以名稱為依據從運行進程隊列中查找進程,并顯示查找到的進程id
選項:
-o 僅顯示找到的最小(起始)進程號
-n 僅顯示找到的最大(結束)進程號
-l 顯示進程名稱
-p 指定父進程號;pgrep -p 4764 查看父進程下的子進程id
-g 指定進程組
-t 指定開啟進程的終端
-u 指定進程的有效用戶id
&>/dev/null 可以停止將輸出顯示到屏幕上
先執行,再用$?判斷是否執行成功
ps 和 pgrep 可以用來判斷一個進程是否存在
wget , curl 和 elinks --dump 用于判斷一個服務是否可以正常訪問
循環控制語句
for循環
固定次數的循環
(一)列表循環
for variable in {list}
do
command
command
done
或者
for variable in a b c
do
command
command
done
可以用{1..10}或sep或枚舉 進行循環
(二)不帶列表循環
由用戶指定參數和參數的個數
for variable # 實際是 for i in '"$@"'
do
command
command
done
(三)類C風格的for循環
for ((expr1;expr2;expr3))
do
command
command
done
for ((i=1;i<=5;i++))
do
echo $i
done
continue 跳出當前循環
break 跳出循環
exit 跳出程序
練習:
- 如果目錄不存在就新建
test -d /tmp/dir1 && mkdir /tmp/dir1 -p
或者
if [ ! -d /tmp/dir1 ];then mkdir /tmp/dir1 -p fi
邏輯運算符與條件語句,這兩者是等價的可以相互轉換
- 判斷所輸整數是否為質數
#!/bin/env bash
read -p "請輸入一個正整數數字:" number
[ $number -eq 1 ] && echo "$number不是質數" && exit
[ $number -eq 2 ] && echo "$number是質數" && exit
for i in `seq 2 $[$number-1]`
do
[ $[$number%$i] -eq 0 ] echo "$number不是質數" && exit
done
echo "$number是質數" && exit
- 批量創建用戶
批量添加5個新用戶,以u1到u5命名,并統一加一個新組,組名為class,統一改密碼為123
#!/bin/env bash
grep -w ^class /etc/group &>/dev/null # 精確判斷是否存在class這個組
test $? -ne 0 && groupadd class
for ((i=1;i<=5;i++))
do
useradd -G class u$i
echo 123|passwd --stdin u$i
done
批量創建5個用戶stu1-stu5,要求這幾個用戶的家目錄都在/rhome
#!/bin/bash
#判斷rhome是否存在
[ -f /rhome ] && mv /rhome /rhome.bak
test ! -d /rhome && mkdir /rhome
for ((i=1;i<=5;i++))
do
useradd -d /rhome/stu$i stu$i
echo 123|password --stdin stu$i
done
- 局域網內腳本檢查主機網絡通訊
局域網內,把能ping通的IP和不能ping通的IP分類,并保存到兩個文本文件中
10.1.1.1 - 10.1.1.10
#!/bin/bash
ip=10.1.1
for ((i=1;i<=10;i++))
do
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt # >>是追加
else
echo "$ip.$i is down" >> /tmp/ip_down.txt # >>是追加
done
time ./path/demo.sh #可以用來記錄執行時間
并發執行以上任務
#!/bin/bash
ip=10.1.1
for ((i=1;i<=10;i++))
do
{ # (1) 花括號包起來
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt # >>是追加
else
echo "$ip.$i is down" >> /tmp/ip_down.txt # >>是追加
}& #(2) 在后臺執行
done
wait #(3) 等待所有線程執行完畢
echo "IP is ok"
while循環
while 表達式
do
command
done
死循環
while true
while : #等價
tail -f 循環讀取文件尾部,動態顯示更新的內容
while read ip passwd
do
command
done < ip.txt # 用循環從文件中讀取
until語法結構
條件為假就進入循環,條件為真就退出循環
until expression [ 1 -eq 1 ] (( 1 >= 1 ))
do
command
command
done
新建10個user,其中后5個的home目錄在rhome下
#!/bin/env bash
if [ -d /rhome ];then
echo "/rhome目錄已存在"
else
mkdir /rhome
echo "/rhome目錄不存在,已完成創建"
fi
i=1
until [ $i gt 10 ]
do
if [ $i -le 5 ];then
useradd -u $[1000+$i] stu$i
echo 123|passwd --stdin stu$i
else
useradd -d /rhome/stu$i stu$i
echo 123|passwd --stdin stu$i
fi
let i++
done
隨機數
系統變量:RANDOM,默認會產生0~32767的隨機整數
前言:要想調用變量,不管是什么變量,都要用$
#產生0~100之間的隨機數
echo $[ $RONDOM%101 ]
#產生10~99之間的隨機數
echo $[ $RONDOM%90+10 ] 0~89 + 10
head -隨機產生的行號 file_name|tail -1
嵌套循環
for while until 都可以相互嵌套
echo默認有換行的屬性,-n 取消換行
shift位移
shift 使位置參數向左移動,默認移動一位,可以使用shift 2
eg: ./demo.sh 1 2 3 4
for i
do
echo $1 # 依次輸出 1 2 3 4
shift
done
expect
expect自動應答 tcl語言
需求1:A遠程登錄到server上什么都不做
#!/usr/bin/expect
#開啟一個程序
set ip 10.1.1.1 # 定義變量 [ lindex $argv 0 ] 可以從腳本的參數中讀取
set pass 123456
spawn ssh root@$ip # 執行程序
捕獲相關內容
expect {
"(yes/no)?" { send "yes\r";exp_continue } # exp_continue 的作用是當匹配不到yes/no時繼續
"password:" { send "$pass\r" } # 一定要回車才能繼續往下走 \r 或 \n 都可以
}
interact # 不加會退出ssh登錄
expect "#" # 期待匹配到登錄的遠端服務器終端出現#開頭
send "rm -rf /tmp/*\r"
send "touch /tmp/file{1..3}\r"
send "date\r"
send "exit\r"
expect eof
- 循環 useradd username
- 登錄遠程主機->ssh->從ip.txt文件讀取IP和密碼分別賦值給兩個變量
- 使用expect程序解決交互問題
#!/bin/bash
while read ip pass
do
/usr/bin/expect <<-END &>/dev/null # 聲明一個結束符(什么都行),其中-表示后面的END不用頂格可以添加制表符
spawn ssh root@ip
expect {
"(yes/no)?" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }
expect eof
END # 退出expect
done < ip.txt
案例分析
- 跳板機上的yunwei用戶生成鑰對
- 判斷賬號是否存在(id yunwei)
- 判斷該用戶是否有密鑰對文件 [ -f xxx ]
- 判斷expect是否安裝
- su - yunwei
- 判斷局域網內的主機是否ping通(循環判斷|for while until)
- 循環判斷 for while
- 循環體do ... done ping 主機 如果ping通,調用expect程序自動應答推送公鑰
- 測試驗證是否免密登錄成功
- 功能1:管理員root創建yunwei用戶和安裝expect軟件包
#!/bin/env bash
# 實現批量推送公鑰
# 判斷jumper上的yunwei賬號是否存在
{
id yunwei &>/dev/null
[ $? -ne 0 ] && useradd yunwei && echo 123|passwd --stdin yunwei
} &>/dev/null
# 判斷expect程序是否安裝
rpm -q expect
[ $? -ne 0 ] && yum -y install expect && echo "expect軟件已經安裝成功"
- 功能2:判斷主機是否ping通且yunwei用戶推送公鑰
#!/bin/env bash
# 判斷yunwei用戶密鑰對是否存在
home_dir=/home/yunwei
[ ! -f $home_dir/.ssh/id_rsa.pub ] && ssh-keygen -P '' -f $home_dir/.ssh/id_rsa &>/dev/null
#循環檢查主機的網絡并且進行公鑰推送
ip_txt=/path_to_txt/ip.txt
# 文件中很多行,每一列用:分隔
# tr ':' ' ' < $ip_txt|while read ip passwd
# do
# {
# }&
# done
for i in `cat $ip_txt` # 一行一行讀取
do
ip=`echo $i|cut -d: -f1`
pass=`echo $i|cut -d: -f2`
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip >> ~/ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect "(yes/no)" { send "yes\n";exp_continue }
expect "password:" { send "$pass\n" }
expect eof
END
else
echo $ip >> $home_dir/ip_down.txt
fi
done
# 測試驗證
remote_ip=`head -1 ~/ip_up.txt`
ssh root@$remote_ip hostname
[ $? -eq 0 ] && echo "公鑰推送成功"
sudo
yunwei用戶sudo授權:
visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
yunwei ALL=(root) NOPASSWD:ALL,!/sbin/shutdown,!/sbin/init,!/bin/rm -rf /
解釋說明:
1)第一個字段yunwei指定的是用戶:可以是用戶名,也可以是別名。每個用戶設置一行,多個用戶設置多行,也可以將
多個用戶設置成一個別名后再進行設置
2)第二個字段ALL指定的是用戶所在的主機:可以是ip,也可以是主機名,表示該sudo設置只在該主機上生效,
ALL表示所有主機上都生效。!限制的都是本機,也就是限制使用這個文件的主機;一般都是指定為ALL表示所有主機,
不管文件拷貝到那里都可以用。比如:10.1.1.1=...則表示只在當前主機生效。
3)第三個字段(root)括號里指定的也是用戶,指定以什么用戶身份執行sudo,即使用sudo后可以享有所有root
賬號下的權限,如果要排除個別用戶,可以在括號內設置,比如ALL=(ALL,!oracle,!pos)。
4)第四個字段ALL指定的是執行的命令:即使用sudo后可以執行所有的命令,除了關機和刪除跟內容以外;也可以設置別名。NOPASSWD:ALL表示使用sudo的不需要輸入密碼。
5)也可以授權給一個用戶組
%admin ALL=(ALL) ALL 表示admin組里的所有成員可以在任何主機上以任何用戶身份執行任何命令
數組
數組分類:
- 普通數組:只能使用整數作為數組索引(元素的下標)
- 關聯數組:可以使用字符串作為數組索引(元素的下標)
普通數組
定義:
- 一次賦予一個值
數組名[索引下標]=值
array[0]=v1
array[1]=v2
- 一次賦予多個值
數組名=(值1 值2 值3 ...)
array=(var1 var2 var3 var4)
array1=(`cat /etc/passwd`) # 將文件中每一行賦值給array1數組
array2=(`ls /root`)
array3=(harry amy jack "Miss Hou")
array4=(1 2 3 4 "hello world" [10]=linux)
數組的讀取
${數組名[元素下標]}
echo ${array[0]} # 獲取數組里第一個元素
echo ${array[*]} # 獲取數組里的所有元素
echo ${#array[*]} # 獲取數組里所有元素的個數
echo ${!array[@]} # 獲取所有數組元素的索引下標
echo ${array[@]:1:2} # 訪問指定的元素;1代表從下標為1的元素開始獲取;2代表獲取后面幾個元素
# 查看普通數組信息
declare -a # 不僅可以定義,也可以查看當前已經定義好的數組
關聯數組
聲明關聯數組
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
數組賦值:
- 一次賦一個值
數組名[索引or下標]=變量值
asso_array1[linux]=one
asso_array1[java]=two
asso_array1[php]=three
- 一次賦多個值
asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
- 查看關聯數組
#declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one")'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Miss Hou")'
關聯數組下標無序
關聯數組的讀取方法同普通數組
- 取出一個目錄下的目錄和文件:dirname和basename
# A=/root/Desktop/shell/mem.txt
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A 取出目錄
/root/Desktop/shell
# basename $A 取出文件
mem.txt
- 變量內容的刪除和替換
一個"%"代表從右往左去掉一個/key/
兩個"%%"代表從右往左最大去掉/key/
一個"#"代表從左往右去掉一個/key/
兩個"##"代表從左往右最大去掉/key/
eg:
# url=www.taobao.com
# echo ${#url} 獲取變量的長度
# echo ${url#*.}
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}
任務:統計網站的連接次數
#!/bin/env bash
#count_http_80_state
#統計每個狀態的個數
declare -A array1
states=`ss -ant|grep 80|cut -d' ' -f1` #類似netstat,但LISTEN ESTAB的狀態值顯示在開頭
for i in $states
do
let array1[$i]++
done
#通過遍歷數組里的索引和元素打印出來
for j in ${!array1[@]}
do
echo $j:${array1[$j]}
done
case語句
- case語句為多重匹配語句
- 如果匹配成功,執行相匹配的命令
- 語法結構
說明:pattern表示需要匹配的模式
case var in # 定義變量;var代表是變量名
pattern1) # 模式1;用|分割多個模式,相當于or
command1 # 需要執行的語句
;; # 兩個分號代表命令結束
pattern2)
command2
;;
pattern3)
command3
;;
*) # default, 不滿足以上模式,默認執行*)下面的語句
command4
;;
esac # esac表示case語句結束
案例:菜單提示讓用戶選擇需要做的事
需求:模擬一個多任務界面,當執行程序時先顯示總菜單,然后進行選擇后做相應維護監控操作
#!/bin/env bash
cat <<-EOF
h 顯示命令幫助
f 顯示磁盤分區
d 顯示磁盤掛載
m 查看內存使用
u 查看系統負載
q 退出程序
EOF
while true
do
read -p "請選擇需要操作的內容(help h):" action
clear # 清屏
cat <<-EOF
h 顯示命令幫助
f 顯示磁盤分區
d 顯示磁盤掛載
m 查看內存使用
u 查看系統負載
q 退出程序
EOF
case $action in
h|help)
cat <<-EOF
h 顯示命令幫助
f 顯示磁盤分區
d 顯示磁盤掛載
m 查看內存使用
u 查看系統負載
q 退出程序
EOF
;;
f)
lsblk
;;
d)
df -h
;;
m)
free -m
;;
u)
uptime
;;
q)
exit
;;
esac
done
函數
shell中允許將一組命令集合或語句形成一段可用代碼,這些代碼塊稱為shell函數
給這段代碼起個名字稱為函數名,后續可以直接調用該段代碼的功能
- 定義函數:
函數名()
{
函數體(一堆命令的集合,來實現某個功能)
}
function 函數名()
{
函數體(一堆命令的集合,來實現某個功能)
}
- 函數中return說明:
- return可以結束一個函數,類似于循環控制語句break(結束當前循環,執行循環體后面的代碼)
- return默認返回函數中最后一個命令的狀態值,也可以給定參數值,范圍是0-256之間
- 如果沒有return命令,函數將返回最后一個指令的退出狀態值
- 調用函數
1)當前命令行調用,只在當前終端有效,使用前需要source該腳本
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
2)定義到用戶的環境變量中,~/.bashrc(全局所有用戶是/etc/bashrc)
自登錄時生效,修改后需要source
3)腳本中調用
#!/bin/bash
menu(){
cat <<-EOF
h 顯示命令幫助
f 顯示磁盤分區
d 顯示磁盤掛載
m 查看內存使用
u 查看系統負載
q 退出程序
EOF
}
menu # 調用menu
source /path_to_file/filename.sh # 接下來可以調用其他文件的函數
案例:交互輸入用戶的姓名、性別、年齡
#!/bin/bash
# 該函數實現用戶如果不輸入內容則一直循環直到用戶輸入為止,并且將用戶輸入的內容打印出來
input_fun()
{
input_var=""
output_var=$1
while [ -z $input_vat ]
do
read -p "$output_var" input_var
done
echo $input_var
}
name=`input_fun 請輸入你的姓名`
遞歸寫法
#!/bin/bash
func()
{
read -p "$1" name
if [ -z $name ];then
func $1
else
echo $name
fi
}
#!/bin/bash
# jumper-server
# 定義菜單打印功能的函數
menu(){
cat <<-EOF
歡迎使用Jumper-server, 請選擇你要操作的主機:
1. DB1-Master
2. DB2-Master
3. Web1
4. Web2
h. help
q. exit
EOF
}
# 調用函數來打印菜單
menu
# 循環等待用戶選擇
while true
do
# 菜單選擇,case...esac語句
read -p "請選擇你要訪問的主機:" host
case $host in
1)
ssh root@10.1.1.1
;;
2)
ssh root@10.1.1.2
;;
3)
ssh root@10.1.1.3
;;
h)
clear;menu
;;
q)
exit
;;
esac
done
#將腳本放到yunwei用戶家目錄里的.bashrc里執行:
bash ~/jumper-server.sh
exit
進一步完善:
增強跳板機的安全性,工作人員通過跳板機訪問生產環境,但是不能在跳板機上停留
#!/bin/bash
# 公鑰推送成功(免密)
trap '' 1 2 3 19 # 屏蔽ctrl-C的信號
#打印菜單用戶選擇
menu(){
cat <<-EOF
}
# 回顧信號
1) SIGUP # 重新加載配置
2) SIGINT # 鍵盤終端^C
3) SIGQUIT # 鍵盤退出
9) SIGKILL # 強制終止
15) SIGTERM # 終止(正常結束),缺省信號
18) SIGCONT # 繼續
19) SIGSTOP # 停止
20) SIGTSTP # 暫停^Z
正則表達式
(Regular Expression、regex或regexp,縮寫為RE)是一種字符模式,用于在查找過程中匹配指定的字符
許多程序語言都支持利用正則表達式進行字符串操作
正則表達式這個概念最初是由Unix中的工具軟件(例如sed和grep)普及開的
支持正則表達式的程序如:locate | find | vim | grep | sed |awk
正則能干什么?
- 匹配郵箱、匹配身份證號碼、手機號、銀行卡號等
- 匹配某些特定字符串,做特定處理等等
正則當中名詞解釋
- 元字符
正則表達式中具有特殊意義的專用字符,如:點(.) 星(*) 問號(?)等 和通配符中的星號和問號是不一樣的
- 前導字符
位于元字符前面的字符, abd* aooo.
第一類正則表達式
正則中普通常用的元字符
. 匹配除了換行符以外的任意單個字符
* 前導字符出現0次或連續多次
.* 任意長度的字符 ab.*
^ 行首(以...開頭) ^root
$ 行尾(以...結尾) bash$
^$ 空行
[] 匹配括號里任意單個字符或一組單個字符 [abc]
[^] 匹配不包含括號里任一單個字符或一組單個字符 [^abd]
^[] 匹配以括號里任意單個字符或一組單個字符開頭 ^[abc]
^[^] 匹配不以括號里任意單個字符或一組單個字符開頭 ^[^abc]
# 正則中的其他元字符
\< 取單詞的頭
\> 取單詞的尾
\< \> 單詞精確匹配 等價于 grep -w
\{n\} 匹配前導字符連續出現n次
\{n,\} 匹配前導字符至少出現n次
\{n,m\} 匹配前導字符出現n次與m次之間
\{ \} 保存被匹配的字符
# vim中:%s/\{10.1.1\}.1/\1.254/g 將前{}起來的用標簽\1表示 10.1.1.1 -> 10.1.1.254
\d 匹配數字(grep -P) [0-9]
\w 匹配字母數字下劃線(grep -P) [a-zA-Z0-9_]
\s 匹配空格、制表符、換頁符(grep -P) [\t\r\n]
擴展類正則常用元字符
grep你要用我,必須加-E或者讓你兄弟egrep來找我
sed你要用我,必須加-r
+ 匹配一個或多個前導字符 bo+匹配boo bo
? 匹配零個或一個前導字符 bo?匹配b bo
| 或 匹配a或b
() 組字符(看成整體) (my|your)self: 表示匹配myself或匹配yourself
{n} 前導字符重復n次
{n,} 前導字符重復至少n次
{n,m} 前導字符重復n到m次
第二類正則表達式
[:alnum:] 字母與數字字符 [[:alnum:]]+
[:alpha:] 字母字符(包括大小寫字母) [[:alpha:]]{4}
[:blank:] 空格與制表符 [[:blank:]]*
[:digit:] 數字 [[:digit:]]?
[:lower:] 小寫字母 [[:lower:]]{4,}
[:upper:] 大寫字母 [[:upper:]]+
[:punct:] 標點符號 [[:punct:]]
[:space:] 包括換行符,回車等在內的所有空白 [[:space:]]+
正則表達式總結
- 我要找什么?
- 找數字 [0-9] \d
- 找字母 [a-zA-Z]
- 找標點符號 [[:punct:]]
- 我要如何找?看心情找
- 以什么為首 ^key
- 以什么結尾 key$
- 包含什么不包含什么 [abc] [1] [^abc] [abc]
- 我要找多少呀?
- 找前導字符出現0次或連續多次 ab*
- 找任意單個(一次)字符 ab.
- 找任意字符 ab.*
- 找前導字符連續出現幾次 {n}
- 找前導字符出現1次或多次 go+
去掉空行:
1.查找不以大寫字母開頭的行(三種寫法)
grep '^[^A-Z]' 2.txt
grep -v '^[A-Z]' 2.txt
grep '^[^[:upper:]]' 2.txt
2.查找有數字的行(兩種寫法)
grep '[0-9]' 2.txt
grep -P '\d' 2.txt
3. 查找一個數字和一個字母連起來的
grep -E '[0-9][a-zA-Z]|[a-zA-Z][0-9]' 2.txt
4. 查找不以r開頭的行
grep '^[^r]' 2.txt
5. 查找以點結束的
grep '\.$' 2.txt
6. 去掉空行
grep -v '^$' 2.txt
grep '^[^$]' 2.txt # 錯誤寫法,[]中的只表示單個字符
7. 查找完全匹配abc的行
grep '\<abc\>' 2.txt
8. 查找A后有三個數字的行
grep -E 'A[0-9]{3}' 2.txt
grep 'A[0-9]\{3\}' 2.txt
9. 統計root在/etc/passwd里出現了幾次
grep -o 'root' 1.txt|wc -l
10. 用正則表達式找出自己的IP地址、廣播地址、子網掩碼
ifconfig eth0|grep Bcast|grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\'
ifconfig eth0|grep Bcast|grep -E -o '([0-9]{1,3}.){3}[0-9]{1,3}'
ifconfig eth0|grep Bcast|grep -P -o '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' # -P后不用添加-E
ifconfig eth0|grep Bcast|grep -P -o '(\d{1,3}.){3}\d{1,3}'
ifconfig eth0|grep Bcast|grep -P -o '(\d+.){3}\d+'
也可以使用egrep 配合 [:digit:]
11. 找出文件中的ip地址并且打印替換成172.16.2.254
grep -o -E '([0-9]{1,3}.){3}[0-9]{1,3}' 1.txt|sed -n 's/192.168.0.\(254\)/172.16.2.\1/p'
12. 找出文件中的ip地址
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
13. 找出全部是數字的行
grep -E '^[0-9]+$' test
14.找出郵箱地址
grep -E '^[0-9]+@[a-z0-9]+\.[a-z]+$'
abc ??

浙公網安備 33010602011771號