shell總結
菜單
生成菜單1:
#!/bin/bash
# 定義顏色變量
RED='\033[1;31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
NORMAL='\033[0m'
PS3=`echo -e "${GREEN}請選擇一個選項:${NORMAL}" `
options=("選項1" "選項2" "選項3" "退出")
select opt in "${options[@]}"
do
case "$opt" in
"選項1")
echo -e "${YELLOW}你選擇了選項1${NORMAL}"
;;
"選項2")
echo -e "${YELLOW}你選擇了選項2${NORMAL}"
;;
"選項3")
echo -e "${YELLOW}你選擇了選項3${NORMAL}"
;;
"退出")
echo -e "${RED}退出程序${NORMAL}"
break
;;
*) echo -e "${RED}無效選項${NORMAL}";;
esac
done
生成菜單法2:
cat <<-EOF #cat h 顯示命令幫助 f 顯示磁盤分區 d 顯示磁盤掛載 m 查看內存使用 u 查看系統負載 q 退出程序 bag show rosbag EOF while true #死循環 do #選擇操作的內容 read -p "選擇內容(help h):" action #clear case $action in #case語句 h) cat <<-EOF #conmod h 顯示命令幫助 f 顯示磁盤分區 d 顯示磁盤掛載 m 查看內存使用 u 查看系統負載 q 退出程序 bag show rosbag EOF ;; f) fdisk -l ;; d) df -h ;; m) free -m ;; u) uptime ;; q) exit ;; bag) ssh -p 17042 cti@frp.ctirobot.com "ls -lh .ros/cti_all_bag" ;; esac done
#!/usr/bin/env #創建用戶 USER=$1 ROOT_PASSWD="Fpc11100885" INIT_PASSWD="fPc11100885" #免密 echo "" |ssh-keygen -t rsa -P "" while read host do sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root $host "userdel -r $USER;useradd $USER;echo "$INIT_PASSWD"|passwd --stdin $USER" sshpass -p "$INIT_PASSWD" ssh-copy-id $USER@$host done<$2
日志輸出:
要想將腳本中所有的標準輸出1和標準錯誤輸出2都自動寫到log中,可以在腳本開頭加上
exec &>>./a.log 或者exec 1>>a.log 2>&1
shell組命令與子進程
- 1.組命令
- 2.子進程
- 2.1 什么是子進程
- 2.2 創建子進程
- 2.3 子進程總結
- 3.如何檢測子shell與子進程
- 4.當前shell運行
1.組命令
組命令,就是將多個命令劃分為一組,或者看成一個整體。
|
用法
|
區別
|
|---|---|
|
Shell 組命令的寫法有兩種: { command1; command2;. . .; } |
兩種寫法的重要不同:由 所以在子 Shell 環境中的任何更改都會消失(包括給變量賦值)。因此,在大多數情況下,除非腳本要求一個子 Shell, 否則使用 |
| 舉栗 | |
| 將多條命令的輸出重定向到out.txt文件 |
1.普通模式
2.使用組命令 { ls -l ;echo "test432";cat test.txt; }>out.txt (ls -l ;echo "test432";cat test.txt)>out.txt |
| 組命令與管道結合 |
(ls -l ;echo "test432";cat ../test.txt)|wc -l |
| { { } } 和( ( ))組命令區別 |
4 1 多個{}嵌套,{和{要有空格,且多層嵌套也只產生1層子shell
4 3 多個()嵌套,(和(要有空格,且多層嵌套對產生多層子shell |
2.子進程
2.1 什么是子進程
子進程的概念是由父進程的概念引申而來的。在 Linux 系統中,系統運行的應用程序幾乎都是從 init(pid為 1 的進程)進程派生而來的,所有這些應用程序都可以視為 init 進程的子進程,而 init 則為它們的父進程。
Shell 腳本是從上至下、從左至右依次執行的,即執行完一個命令之后再執行下一個。如果在 Shell 腳本中遇到子腳本(即腳本嵌套,但是必須以新進程的方式運行)或者外部命令,就會向系統內核申請創建一個新的進程,以便在該進程中執行子腳本或者外部命令,這個新的進程就是子進程。子進程執行完畢后才能回到父進程,才能繼續執行父腳本中后續的命令及語句。
使用pstree -p命令就可以看到 init 及系統中其他進程的進程樹信息(包括 pid):
systemd(1)─┬─ModemManager(796)─┬─{ModemManager}(821)
│ └─{ModemManager}(882)
├─NetworkManager(975)─┬─{NetworkManager}(1061)
│ └─{NetworkManager}(1077)
├─abrt-watch-log(774)
├─abrt-watch-log(776)
├─abrtd(773)
├─accounts-daemon(806)─┬─{accounts-daemon}(839)
│ └─{accounts-daemon}(883)
├─alsactl(768)
├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───{dbus-daemon}(1960)
│ ├─{at-spi-bus-laun}(1955)
│ ├─{at-spi-bus-laun}(1957)
│ └─{at-spi-bus-laun}(1959)
├─at-spi2-registr(1962)───{at-spi2-registr}(1965)
├─atd(842)
├─auditd(739)─┬─audispd(753)─┬─sedispatch(757)
│ │ └─{audispd}(759)
│ └─{auditd}(752)
2.2 創建子進程
|
創建子進程的方式
|
說明
|
|
|---|---|---|
|
|
組命令、命令替換、管道 |
|
舉栗: |
1.以新進程的方式運行腳本文件,比如 2.在當前 Shell 中使用 bash 命令啟動新的 Shell |
2.3 子進程總結
子 Shell 雖然能使用父 Shell 的的一切,但是如果子 Shell 對數據做了修改,比如修改了全局變量,這種修改也只能停留在子 Shell,無法傳遞給父 Shell。不管是子進程還是子 Shell,都是“傳子不傳父”。
子 Shell 才是真正繼承了父進程的一切,這才像“一個模子刻出來的”;普通子進程和父進程是完全不同的兩個程序,只是維持著父子關系而已。
3.如何檢測子shell與子進程
echo $$輸出當前進程ID (當前shell的pid,也就是腳本運行的當前進程號),echo $PPID輸出父shell ID
|
命令 |
結果 |
結論 | |
|---|---|---|---|
|
輸出當前進程與父進程ID |
echo $$;echo $PPID |
34451 34450 |
|
|
子進程形式輸出進程ID 子進程 |
bash echo $$;echo $PPID exit |
52886 34451 |
在普通的子進程中,$ 被展開為子進程的 ID |
|
組命令形式輸出進程ID 子shell |
(echo $$;echo $PPID)
|
34451 34450 |
子shell和父shell中的ID是一樣的 這是因為$ 變量在子 Shell 中無效!Base 官方文檔說,在普通的子進程中,$ 確實被展開為子進程的 ID; 但是在子 Shell 中,$ 卻被展開成父進程的 ID |
|
管道形式輸出進程ID 子shell |
echo "test" | { echo $$;echo $PPID; } |
34451 34450 |
|
|
進程替換形式輸出進程ID |
read < <(echo $$ $PPID) $ echo $REPLY |
34451 34450 |
除了 $,Bash 還提供了另外兩個環境變量——SHLVL 和 BASH_SUBSHELL,用它們來檢測子 Shell 非常方便。
SHLVL 是記錄多個 Bash 進程實例嵌套深度的累加器,每次進入一層普通的子進程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是記錄一個 Bash 進程實例中多個子 Shell(sub shell)嵌套深度的累加器,每次進入一層子 Shell,BASH_SUBSHELL 的值就加 1。
| 命令 | 結果 | 知識點 | |
|---|---|---|---|
| 輸出變量 |
echo "$SHLVL $BASH_SUBSHELL" |
1 0 |
|
|
子進程形式輸出變量 子進程 |
創建一個腳本文件,命名為 test.sh,內容如下: #!/bin/bash echo "$SHLVL $BASH_SUBSHELL" ***************************************** bash echo "$SHLVL $BASH_SUBSHELL"#2 0 bash ./test.sh #3 0 echo "$SHLVL $BASH_SUBSHELL"#2 0 chmod +x ./test.sh;./test.sh #3 0 echo "$SHLVL $BASH_SUBSHELL"#2 0 exit #退出內層Shell echo "$SHLVL $BASH_SUBSHELL"#1 0 |
這兩種運行腳本的方式,在腳本運行期間會開啟一個子進程, 運行結束后立即退出子進程 產生新進程時,SHLVL的值加1 |
|
|
組命令形式輸出變量 子shell |
(echo "$SHLVL $BASH_SUBSHELL") |
1 1 | 組命令、管道、命令替換這幾種方式都會產生子 Shell |
|
管道形式輸出變量 子shell |
echo "test" | { echo "$SHLVL $BASH_SUBSHELL"; } |
1 1 | |
|
命令替換形式輸出變量 子shell |
var=$(echo "$SHLVL $BASH_SUBSHELL") echo $var |
1 1 | |
|
四層組命令形式輸出變量 子shell |
( ( ( (echo "$SHLVL $BASH_SUBSHELL") ) ) ) |
1 4 | |
|
進程替換形式輸出變量
|
read < <(echo "$SHLVL $BASH_SUBSHELL") echo $REPLY
echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL")
|
1 0 1 0 |
進程替換只是借助文件在()內部和外部命令之間傳遞數據, 并沒有創建子shell, (也就是當前進程)中執行的
|
4 當前shell運行,是內置命令
調用所有普通的程序(包括另一個shell程序、其他sh腳本)都是子進程。
只有在命令中寫明(),$(),``(反引號) 都是用來執行命令替換的,它們會在子 shell 中執行。比如echo $(ls),這里先用$()產生一個子shell,在調用ls時,又在這個子shell內部產生了一個專門執行ls的子進程,這里存在嵌套
內建命令就是包含在bash Shell工具包中的命令,是bash Shell的骨干部分,除此之外,保留字(reserved words)也是bash Shell的骨干部分,保留字對于bash Shell具有特殊的含義,用來構建Shell語法結構,for、if、then、while、until等都是保留字,但是,保留字本身不是一個命令,而是命令結構的一部分。
Bash Shell的內建命令和保留字:
$(( )) $[] expr等 行算術運算時,不需要啟動新的 shell 進程,所有的計算都是在當前的 shell 環境
! #保留字,邏輯非
: #不做任何事,只做參數展開
. #讀取文件,并在當前Shell中執行它
alias #設置命令或命令行的別名
bg #將作業置于后臺運行
bind #將關鍵字序列與readline函數或宏綁定
break #保留字,跳出for、while、until、select循環
builtin #調用命令的內建命令格式,而禁用同名的函數,或者同名的擴展命令
case #保留字,多重選擇
cd #切換當前工作目錄
command #找出內建和外部命令;尋找內建命令而非同名函數
continue #保留字,到達下一次for、while、until、select循環
declare #聲明變量,定義變量屬性
dirs #顯示當前存儲目錄的列表
disown #將作業從表中移除
do #保留字,for、while、until、select循環的一部分
done #保留字,for、while、until、select循環的一部分
echo #打印參數
elif #保留字,if結構的一部分
else #保留字,if結構的一部分
enable #開啟和關閉內建命令
esac #保留字,case的一部分
eval #將參數作為命令再處理一遍
exec #以特定程序取代Shell或為Shell改變I/O
exit #退出Shell
export #將變量聲明為環境變量
fc #與命令歷史一起運行
fg #將作業置于前臺運行
fi #保留字,if結構的一部分
for #保留字,for結構的一部分
function #定義一個函數
getops #處理命令行選項
hash #記錄并指定命令的路徑名
help #顯示內建命令的幫助信息
history #顯示歷史命令信息
if #保留字,if結構的一部分
in #保留字,case的一部分
jobs #顯示在后臺運行的作業
kill #向進程傳送信號
let #使變量執行算術運算
local #定義局部變量
logout #從shell中注銷
popd #從目錄棧中彈出目錄
pushd #將目錄壓入目錄棧
pwd #顯示當前工作目錄
read #從標準輸入中讀入一行
readonly #將變量定義為只讀
return #從函數或腳本中返回
select #保留字,生成選擇菜單
set #設置Shell選項
shift #變換命令行參數
suspend #中止Shell的執行
test #評估條件表達式
then #保留字,if結構的一部分
time #保留字,輸出統計出來的命令執行時間,其輸出格式由TIMEFORMAT變量來控制
times #針對Shell及其子Shell,顯示用戶和系統CPU的時間之和
trap #設置信號捕捉程序
type #確認命令的源
typeset #聲明變量,定義變量屬性,與declare等價
ulimit #設置和顯示進程占用資源的限制
umask #設置和顯示文件權限碼
unalias #取消別名定義
unset #取消變量或函數的定義
until #保留字,一種循環結構
wait #等待后臺作業完成
while #保留字,一種循環結構
冒號是bash Shell中一個特殊的符號,可表示永真,相當于TRUE關鍵字。
例:冒號表示永真的用法
#!/bin/bash
i=0
while : #冒號相當于TRUE
do
if ((i >= 3 ))
then
break
fi
echo $((++i))
done
結果:1
2
3
冒號可以清空一個文件。:>命令是常用的清空文件的命令
命令:cat loggg
結果:Positional Parameter is NULL
命令::>loggg #將冒號重定向到文件并將文件清空
cat loggg
結果:
冒號最重要的用法是:不做任何事,只做參數展開。
12.1.2、圓括號結構
圓括號結構能夠強制將其中的命令運行在子Shell中,其基本格式為:
(
command1
command2
...
commandn
)
該結構表示圓括號內的n條命令在子Shell中運行,bash版本3之后定義的內部變量BASH_SUBSHELL記錄了子Shell的層次。
例:圓括號結構用法和BASH_SUBSHELL變量
#!/bin/bash
echo "The level of father Shell is: $BASH_SUBSHELL" #打印父Shell的層次
outervar=OUTER #定義一個變量
( #進入子Shell
echo "The level of SubShell is: $BASH_SUBSHELL"
innervar=INNER #在子Shell內定義一個變量
echo "innervar=$innervar"
echo "outervar=$outervar"
)
#回到父Shell
echo "The level of father Shell is: $BASH_SUBSHELL"
if [ -z "$innervar" ] #測試子Shell中定義的變量是否為空
then
echo "The \$innervar is not defined in main body."
else
echo "The \$innervar is defined in main body."
fi
結果:The level of father Shell is: 0 #父Shell的BASH_SUBSHELL值
The level of SubShell is: 1 #子Shell的BASH_SUBSHELL值
innervar=INNER
outervar=OUTER #子Shell能使用父Shell定義的變量
The level of father Shell is: 0
#子Shell定義的變量innervar在父Shell中為空,說明子Shell中變量對父Shell是不可見的
The $innervar is not defined in main body.
BASH_SUBSHELL是從0開始計數的整數,它依次記錄子Shell的層次。
例:測試子Shell定義環境變量是否對父Shell有效
#!/bin/bash
#在父Shell中定義變量outervar
echo "-----------------IN MAINSHELL--------------------"
outervar=OUTER
echo "outervar=$outervar"
( #進入子Shell
echo "-----------------IN SUBSHELL---------------------"
innervar=INNER
echo "innervar=$innervar"
outervar=OUTER-INNER #更改父Shell所定義的outervar變量值
echo "outervar=$outervar"
#將innervar和outervar聲明為環境變量
export innervar
export outervar
)
#回到父Shell,測試innervar和outervar的值是否與子Shell中的定義一樣
echo "-----------------RETURN TO MAINSHELL-------------"
echo "innerver=$innervar"
echo "outervar=$outervar"
結果:-----------------IN MAINSHELL--------------------
outervar=OUTER
-----------------IN SUBSHELL---------------------
#下面打印子Shell中innervar和outervar變量的值
innervar=INNER
outervar=OUTER-INNER
-----------------RETURN TO MAINSHELL-------------
innerver=
outervar=OUTER
#父shell中innervar為空,outervar仍為原來的值,這說明子Shell對兩個變量
的定義和更改對父Shell不起作用
子Shell只能繼承父Shell的一些屬性,但是,子Shell不可能反過來改變父Shell的屬性。
子Shell能夠從父Shell繼承得來的屬性有:
當前工作目錄
環境變量
標準輸入、標準輸出和標準錯誤輸出
所有已打開的文件標識符
忽略的信號
子Shell不能從父Shell繼承得來的屬性有:
除了環境變量和 .bashrc文件中定義變量之外的Shell變量
未被忽略的信號處理
因此,子Shell能夠設置獨立于父Shell的子環境。
例:子Shell設定bash Shell選項的用法
#!/bin/bash
(
set -C #開啟-C選項防止重定向時覆蓋文件
:> outputnull #試圖用冒號清空outputnull文件
)
#在父Shell覆蓋一個文件,測試子Shell開啟的-C選項是否對父Shell生效
cat subsenv.sh > outputnull
執行:./subsenv.sh
結果:./subsenv.sh:行4: outputnull: 無法覆蓋已存在的文件
#提示執行腳本時出錯,不能覆蓋已存在的文件,這說明set -C在子Shell中已經起作用
執行:cat outputnull
#查看outputnull內容,為subsenv.sh腳本本身,這說明父Shell仍能覆蓋文件,set -C不起作用
結果:#!/bin/bash
(
set -C
:> outputnull
)
cat subsenv.sh > outputnull
例:利用子Shell測試變量是否定義過
#!/bin/bash
if (set -u; : $var)
then
echo "Variable is set."
fi
執行:var=2012
export var #將var聲明為環境變量
結果:Variable is set. #該腳本能測試出var已經定義
set -u命令用于設置Shell選項,u選項是nounset的意思,表示當使用未定義的變量時,輸出錯誤信息,并強制退出。
子Shell還可以接收到父Shell從管道傳送來的數據。
例:打印/etc/passwd文件中與root關鍵字所匹配的行
執行:cat /etc/passwd | (grep 'root')
結果:root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
子Shell可以將一個計算量較大的任務分成若干個小任務并行執行。
例:子Shell用于并行計算的用法
#!/bin/bash
#用圓括號結構創建三個子Shell同時執行,每個子Shell都是搜索某個目錄下與root關鍵字匹配的行,排序后輸出到某文件
(grep -r "root" /etc/* | sort > part1) &
(grep -r "root" /usr/local/* | sort > part2) &
(grep -r "root" /lib/* | sort > part3) &
wait #等待后臺執行的作業全部完成后,再執行下面的命令
#將part1、part2和part3三個臨時文件合并,排序后重定向到parttotal文件
cat part1 part2 part3 | sort > parttotal
echo "Run time of this script is: $SECONDS" #輸出該腳本的執行時間
結果:Run time of this script is: 22 #腳本運行了22秒
執行:wc -l parttotal #對結果文件的行計數
結果:1492 parttotal
grep的-r選項表示遞歸搜索。每個圓括號結構之外都有一個&符號,這表示將此命令放在后臺執行,繼續執行下一條命令,如果去掉&符號,腳本需要將(grep -r "root" /etc/* | sort > part1)命令執行完畢后才能執行下一條命令。wait
命令是一個內建命令,用于等待后臺執行的作業全部完成后再執行下面的命令。假如沒有這條命令,腳本將三個子Shell放到后臺執行后,將直接執行合并臨時文件的命令,此時,三個子Shell可能并未執行完畢。因此,臨時文件中的結果是不完整的,合并后也將產生不完整的最終結果。
子Shell是允許嵌套調用的,可在函數或圓括號結構內再次調用圓括號結構創建子Shell。
12.2、Shell的限制模式
Shell的限制模式簡稱RSH(Restricted Shell),處于限制模式的Shell下運行一個腳本或腳本片段,將會禁用一些命令或操作。Shell的限制模式是Linux系統基于安全方面的考慮,目的是為了限制腳本用戶的權限,并盡可能地減小腳本所帶來的危害。
Shell的限制模式限制的命令和操作有:
用cd命令更改當前工作目錄的命令。
更改重要環境變量的值,包括$PATH、$SHELL、$BASH_ENV、$ENV和$SHELLOPTS
輸出重定向符號,包括>、>>、>|、>&、<>和&>符號。
調用含有一個或多個斜杠(/)的命令名稱。
使用內建命令exec。
使用set +r等命令關閉限制模式。
例:Shell的正常模式和限制模式的區別
#!/bin/bash
#在正常模式下改變當前工作目錄
echo "Changing current work directory"
cd /etc
echo "Now in $PWD"
set -r #利用Shell選項使下面的代碼運行在限制模式下,r是restricted的簡寫
echo
echo "------------IN RESTRICTED MODE-------------"
#驗證在限制模式下能否改變當前工作目錄
echo "Trying to change current work directory"
cd /usr/local
echo "Now in `pwd`"
echo
#驗證在限制模式下能否改變$SHELL變量的值
echo "Trying to change \$SHELL"
SHELL="/bin/sh"
echo "\$SHELL=$SHELL"
echo
#驗證在限制模式下能否執行重定向操作
echo "Trying to redirect output to a file"
who > outputnull
ls -l outputnull
結果:Changing current work directory
Now in /etc #Shell在正常模式下改變當前工作目錄
------------IN RESTRICTED MODE-------------
#開始在限制模式下運行
Trying to change current work directory
./resshell.sh: 第 9 行:cd: 受限的 #cd命令出錯,被限制了
Now in /etc #當前工作目錄沒有改變
Trying to change $SHELL
./resshell.sh:行13: SHELL: 只讀變量 #SHELL變量在限制模式下是只讀的
$SHELL=/bin/bash
Trying to redirect output to a file
./resshell.sh:行17: outputnull: 受限的: 無法重定向輸出 #redirect操作出錯
ls: 無法訪問outputnull: 沒有那個文件或目錄 #outputnull沒有被創建
還有一種以限制模式運行腳本的方式,就是將Sha-bang符號(#!)后的語句改成/bin/bash -r,-r表示在限制模式下運行該腳本。
例:以/bin/bash -r方式進入限制模式
#!/bin/bash -r
#驗證在限制模式下能否讀取$SHELLOPTS變量的值
echo "\$SHELLOPTS=$SHELLOPTS"
echo
echo "Changing current work directory"
cd /etc
echo "Now in $PWD"
echo
echo "Trying to change \$SHELL"
SHELL="/bin/sh"
echo "\$SHELL=$SHELL"
echo
echo "Trying to redirect output to a file"
who > outputnull
ls -l outputnull
結果:$SHELLOPTS=braceexpand:hashall:interactive-comments
#能夠讀取$SHELLOPTS的值
Changing current work directory
./anotherres.sh: 第 5 行:cd: 受限的
Now in /home/xiaomiao/test
Trying to change $SHELL
./anotherres.sh:行9: SHELL: 只讀變量
$SHELL=/bin/bash
Trying to redirect output to a file
./anotherres.sh:行13: outputnull: 受限的: 無法重定向輸出
-rw-rw-r-- 1 xiaomiao xiaomiao 65 8月 14 11:49 outputnull
12.3、進程處理
fork是Linux系統的一種系統調用(system calls),系統調用用于請求內核服務,這也是進程訪問硬件的唯一方法。fork是創建新進程的系統調用,fork創建的子進程是父進程的副本,兩個進程具有同樣的環境、打開的文件、用戶標志符、當前工作目錄和信號等。
UNIX是第一個允許每個系統用戶控制多個進程的操作系統,這種機制稱為用戶控制的多任務(user-controlled multitasking),Linux操作系統延續了此特性。
12.3.1、進程和作業
進程和作業是有區別的:一個正在執行的進程稱為作業,一個作業可以包含多個進程。因此,作業是用戶層面的概念,而進程是操作系統層面的概念。
進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動,進程在運行中不斷地改變其運行狀態。
通常,一個運行進程必須具有以下三種基本狀態:就緒(Ready)狀態、運行(Running)狀態和阻塞(Blocked)狀態。
引起進程阻塞的事件可有多種,例如,等待I/O完成、申請緩沖區不能滿足、等待信號等。
一個進程在運行期間,不斷地從一種狀態轉換到另一種狀態,它可以多次處于就緒狀態和執行狀態,也可以多次處于阻塞狀態。
Linux系統為每個進程分配一個數字以標識這個進程,這個數字就是進程號。同時,創建該進程的Shell為此進程創建一個數字,也用于標識這個進程,這個數字稱為作業號。
作業號屬于Shell,進程號屬于Linux。
作業號標識的是在此Shell下運行的所有進程。
后臺運行一個作業,方括號中的[1]是作業號,方括號后面的6355是進程號
執行:grep -r "root" /etc/* | sort > part1 &
結果:[1] 6355
執行:grep -r "root" /usr/local/* | sort > part2 &
結果:[2] 6409
[1] 完成 grep --color=auto -r "root" /etc/* | sort > part1
#提示[1]號作業已經完成
默認情況下,當我們輸入下一條命令時,Shell才提示后臺運行的作業已經結束,而實際上,該作業可能已經早就運行結束。如果需要當后臺運行的作業一結束,Shell就顯示信息,就需要開啟norify選項,簡寫為b。
norify選項的功能
執行:set -b #開啟norify選項
grep -r "root" /etc/* | sort > part1 &
結果:[1] 6545
結果:[1]+ 完成 grep --color=auto -r "root" /etc/* | sort > part1
12.3.2、作業控制
進程是針對整個Linux系統而言的,作業是針對Shell而言的。
作業有兩種運行方式:前臺運行和后臺運行。前臺運行的作業指作業能夠控制當前終端或窗口,且能接收用戶的輸入;而后臺運行的作業則不在當前激活的終端或窗口中運行,是在用戶“看不見”的情況下運行。內建命令fg可將后臺運行的作業放到前臺,而&符號使得作業在后臺運行。
Sleep10.sh腳本,休眠10秒后結束
#!/bin/bash
sleep 10
執行:./sleep10.sh &
結果:[1] 6707
執行:fg #將[1]號作業放到前臺運行
結果:./sleep10.sh
#Shell等待[1]號作業運行完畢,才顯示下一行提示符
只有一個作業在后臺運行,fg命令不帶任何參數就能將該作業放到前臺運行。當有多個作業在后臺運行時,不帶任何參數的fg命令將最近提交的那個后臺作業放置到前臺。fg命令如果要在多個后臺作業中挑選符合條件的作業,可使用作業號、作業的命令字符等參數。
fg命令利用作業號指定作業
執行:./sleep20.sh & #提交第1個后臺作業
結果:[1] 6819
執行:./sleep10.sh & #提交第2個后臺作業
結果:[2] 6821
執行:fg %1 #將[1]號作業放到前臺運行
結果:./sleep20.sh #[1]號作業是sleep20.sh腳本
[2] 完成 ./sleep10.sh
#在等待[1]號作業的過程中,[2]號作業運行完畢
指定作業方法及其意義:
%n #n為后臺作業的作業號
%string #命令以string字符串開始的后臺作業
%?string #命令包含string字符串的后臺作業
%+或%% #最近提交的后臺作業
%- #最近第二個提交的后臺作業
jobs命令用于顯示所有的后臺作業。
jobs命令的用法:
執行:./sleep20.sh &
結果:[1] 6992
執行:./sleep20.sh &
結果:[2] 6994
執行:./sleep10.sh &
結果:[3] 6996
執行:jobs #顯示所有后臺運行的作業
結果:[1] 運行中 ./sleep20.sh &
[2]- 運行中 ./sleep20.sh &
[3]+ 運行中 ./sleep10.sh &
執行:jobs -l #帶上-l參數,顯示作業的進程號
結果:[1] 6992 運行中 ./sleep20.sh &
[2]- 6994 運行中 ./sleep20.sh &
[3]+ 6996 運行中 ./sleep10.sh &
在作業運行時按下“Ctrl+Z”組合鍵即可將正在運行的作業阻塞。
將正在運行的作業阻塞的用法:
執行:vim sleep10.sh #用vim打開文件
#!/bin/bash
sleep 10
~
#按下“Ctrl+Z”組合鍵,出現如下信息,說明vim sleep10.sh作業進入阻塞狀態
[1]+ 已停止 vim sleep10.sh
執行:jobs
結果:[1]+ 已停止 vim sleep10.sh
執行:fg #fg命令使vim sleep10.sh作業重新轉到前臺
在“Ctrl+Z”組合鍵之后輸入bg命令可使阻塞狀態的作業轉入后臺運行。
執行:./sleep20.sh
^Z #Ctrl+Z組合鍵
結果:[1]+ 已停止 ./sleep20.sh
執行:bg
結果:[1]+ ./sleep20.sh &
執行:jobs
結果:[1]+ 運行中 ./sleep20.sh &
[1]+ 完成 ./sleep20.sh
注意,fg、bg和jobs命令只能以作業號為參數來指定作業,這三個命令是不能使用進程號的。而kill、disown和wait命令既能以作業號指定作業,也可以用進程號指定作業。
disown命令用于從Shell的作業表中刪除作業,作業表就是由jobs命令所列出的作業列表,disown可以指定刪除作業表中的作業。
disown命令的用法:
執行:vi input &
結果:[1] 2856
執行:vi loggg &
結果:[2] 2857
[1]+ 已停止 vim input
執行:jobs
結果:[1]- 已停止 vim input
[2]+ 已停止 vim loggg
執行:disown %- #刪除最近第2個提交的作業
結果:bash: 警告:刪除進程組 2856 中已停止的任務 1
#Shell提示已經刪除進程號為2856的作業,即最近第2個提交的vi input作業
執行:disown 2857 #以進程號的方式指定所要刪除的作業
結果:bash: 警告:刪除進程組 2857 中已停止的任務 2
#Shell提示已經刪除進程號為2857的作業
執行:jobs #作業列表已經為空
結果:
wait命令用于等待后臺作業完成。
例:wait命令的用法
#!/bin/bash
ls /etc | grep "rc[0-9]" &
echo "The Scirpt quits now!"
wait
執行:./backls.sh #將wait命令注釋掉后的運行結果
結果:The Scirpt quits now! #打印完這行語句后,腳本就已經結束
rc0.d #但是后臺的ls作業仍然在運行
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
#輸入Enter鍵退出
執行:./backls.sh #wait命令存在時的運行結果
結果:The Scirpt quits now!
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d #腳本等待后臺的ls作業運行結束后才結束
12.3.3、信號
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。信號事件的發生有兩個來源:硬件來源和軟件來源。信號是進程間通信機制中唯一的異步通信機制,可以看做是異步通知,通知接收信號的進程有哪些事情發生了。信號機制經過POSIX實時擴展后,除了基本通知功能外,還可以傳遞附加信息。
向進程發送信號大多通過“Ctrl”鍵加上一些功能鍵來實現的。
Ctrl組合鍵、信號類型及其意義:
Ctrl+C INT信號,即interrupt信號 停止當前運行的作業
Ctrl+Z TSTP信號,即terminal stop信號 使當前運行的作業暫時停止(進入阻塞狀
態)
Ctrl+\ QUIT信號 Ctrl+C的強化版本,當Ctrl+C無法停止
作業時,使用此組合鍵
Ctrl+Y TSTP信號,即terminal stop信號 當進程從終端讀取輸入數據時,暫時停止
該進程
“Ctrl+Z”組合鍵實際上與“Ctrl+Y”是類似的,都是向進程發送TSTP信號,表示將進程暫時停止,但是它們的區別在于:“Ctrl+Y”組合鍵僅可以在進程從終端讀取輸入數據時,暫時停止該程序,而“Ctrl+Z”組合鍵則可以隨時暫時停止進程。
除了利用組合鍵發送信號之外,內建命令kill可用于向進程發送TERM(即terminal)信號,功能與INT信號類似,也用于停止進程。而kill命令可以通過進程號、作業號或進程命令名向任何作業發送信號。
kill命令的用法:
執行:./sleep20.sh &
結果:[1] 6101
執行:kill %1 #通過作業號殺死進程
jobs
結果:[1]+ 已終止 ./sleep20.sh
例:kill命令通過用進程號殺掉自己的進程的用法
#!/bin/bash
kill $$ #位置參數$$表示本身的進程號
echo "Does this line appear?"
結果:已終止 #一運行腳本就終止了
執行:echo $?
結果:143
退出碼143大于128表示腳本是被系統強行結束的,然而,該腳本的退出碼還有其他含義的:當Shell腳本收到信號時,退出碼是128+N,N是該腳本所收到信號的標號,此時,腳本收到的是TERM信號,TERM信號的標號正好是15。
Kill -l命令還可以列出kill命令能發出的所有信號及其標號。
12.3.4、trap命令
trap是Linux的內建命令,它用于捕捉信號,trap命令可以指定收到某種信號時所執行的命令。trap命令的格式為:trap command sig1 sig2 ... sigN
該格式表示當trap命令收到sig1 sig2 ... sigN中任意一個信號時,執行command命令,command命令完成后,腳本繼續收到信號前的操作,直到腳本執行結束。
例:trap命令捕捉INT信號的用法
#!/bin/bash
#一旦收到INT信號,執行雙引號內的echo命令
trap "echo 'You hit CONTROL+C!'" INT
while :; do #使用冒號表示永真,無限循環
let count=count+1 #記錄進入循環的次數
echo "This is the $count sleep"
sleep 5 #每次循環休眠5秒
done
結果:This is the 1 sleep
This is the 2 sleep
^CYou hit CONTROL+C! #第1次輸入“Ctrl+C”
This is the 3 sleep #第2次休眠停止,立即進入第3次休眠
This is the 4 sleep
^CYou hit CONTROL+C! #第2次輸入“Ctrl+C”
This is the 5 sleep #第4次休眠停止,立即進入第5次休眠
This is the 6 sleep
^Z #輸入“Ctrl+Z”結束腳本
[1]+ 已停止 ./traploop.sh
trap命令還可以忽略某些信息,即進程收到某些信息后不做任何處理。只要將trap命令的command用空字符串代替即可(””或'')。
例:trap命令忽略信號的用法
#!/bin/bash
trap "" TERM INT #忽略對TERM和INT 兩種信號的處理,如果還要忽略其他信
號,將它們添加到INT之后
while :; do
sleep 5
done
結果:./nokillme.sh &
[3] 6470
執行:kill %3 #試圖殺死nokillme.sh進程
jobs #nokillme.sh仍然運行
結果:[3] 運行中 ./nokillme.sh &
執行:kill -9 %3 #用更為強勁的命令殺死nokillme.sh進程
jobs #nokillme.sh已殺死
結果:[3] 已殺死 ./nokillme.sh
由于kill %3命令發送的是TERM信號,腳本的trap命令忽略了對TERM信號的處理。因此,kill %3命令不能殺死nokillme.sh進程。而kill -9 %3命令kill向3號作業發送9號信號殺死進程,9號信號實際上就是KILL信號,因此,kill -9 %3命令等價于kill -KILL %3命令。
子Shell能繼承父Shell所忽略的信號,但是,不能繼承父Shell未忽略的信號。
forever.sh腳本:
#!/bin/bash
while :; do
sleep 5
done
subsig.sh腳本:
#!/bin/bash
trap "" QUIT #忽略QUIT信號
trap "echo 'You want to kill me'" TERM #捕捉到TERM信號后,打印提示信息
#TERM捕捉,但沒有忽略
( #將forever.sh腳本作為子Shell,子Shell將無限休眠
./forever.sh
)
執行:./subsig.sh &
結果:[3] 6679 #返回父Shell的作業號和進程號
執行:kill -3 6679 #向父Shell發送3號信號,即QUIT信號
執行:ps -a
結果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh #父Shell未退出,說明QUIT信號被忽略
6680 pts/0 00:00:00 forever.sh
6685 pts/0 00:00:00 sleep
6686 pts/0 00:00:00 ps
執行:kill -3 6680 #向子Shell發送3號信號,即QUIT信號
ps -a
結果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh
6680 pts/0 00:00:00 forever.sh#子Shell也未退出,說明QUIT信號被忽略
6701 pts/0 00:00:00 sleep
6702 pts/0 00:00:00 ps
執行:kill 6679 #向父Shell發送TERM信號
ps -a
結果: PID TTY TIME CMD
6679 pts/0 00:00:00 subsig.sh #父Shell仍未被殺掉
6680 pts/0 00:00:00 forever.sh
6713 pts/0 00:00:00 sleep
6714 pts/0 00:00:00 ps
執行:kill 6680 #向子Shell發送TERM信號
結果:已終止 #子Shell立刻被終止
You want to kill me #并打印出父Shell對TERM信號的響應信息
[3] 退出 143 ./subsig.sh #父shell隨著子Shell的終止而終止
跳板機:
[fpc@localhost script]# cat jumpserver.sh
#!/bin/bash
trap '' INT
echo -e "\e[1;31m-----------------------------------------------------"
cat <<-EOF
1)optical modem
2)master DNS
3)slave DNS
4)router
5)virtual host1
6)virtual host2
EOF
echo -e "-----------------------------------------------------\e[0m"
while :
do
echo -ne "\e[1;32mplease input one number: (1,2,3,4,5): \e[0m"
read -p "" num
case $num in
1)
ssh fpc@192.168.6.3
;;
2)
ssh fpc@192.168.6.66
;;
3)
exit
;;
4)
ssh fpc@10.10.10.254
;;
5)
ssh fpc@10.10.10.11
;;
6)
ssh fpc@10.10.10.12
;;
*)
echo "input error,input again"
esac
done
分發公鑰:
注意:
- 并發執行{}& ,不常用,
parallel命令來并發執行命令可以提高效率。首先,您需要確保安裝了parallel工具 - shell中調用expect,EOF中必須是tab鍵
- 并發控制:文件句柄中增加$thead行
#!/bin/bash
thread=10
tmp_fifofile=/tmp/$$.fifo
mkfifo $tmp_fifofile
exec 8<> $tmp_fifofile
rm -f $tmp_fifofile
for i in `seq $thread`
do
echo >&8
done
password="Fpc11100885"
user="root"
>distributed_hosts
rpm -q expect &>/dev/null
if [ $? -eq 0 ];then
echo "install expect"
sudo yum install -y expect
fi
if [ ! -f "~/.ssh/id_rsa" ];then
ssh-keygen -P "" -f ~/.ssh/id_rsa
fi
for host in `cat ./network_hosts`
do
read -u 8
{
ping -c 1 -w 2 $host &>/dev/null
if [ $? -eq 0 ];then
echo $host >> distributed_hosts
/usr/bin/expect <<-EOF
spawn ssh-copy-id ${user}@$host
expect {
"yes/no" {send "yes\r";exp_continue}
"password" {send "$password\r";exp_continue}
eof {send_tty "eof"}
}
EOF
fi
echo >&8
}&
wait
echo "distribute finished"
done
# 并發執行 for host in $hosts; do # 后臺執行命令 sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root$host "userdel -r $USER; useradd$USER; echo \"$INIT_PASSWD\" | passwd --stdin$USER" & sshpass -p "$INIT_PASSWD" ssh-copy-id$USER@$host & sshpass -p "$ROOT_PASSWD" ssh-copy-id root@$host & # 等待所有后臺命令完成 wait # 執行其他操作 # ... done 每個 ssh 命令都被發送到后臺執行,wait 命令用于等待所有后臺命令完成。這種方法簡單且高效,是 Bash 中實現并發執行的常見方式。
傳輸文件:
[root@iZuf6eblqtavferh526stwZ ~]# vim transferFile.sh
exec 1>transfer.log 2>failed.log
transfer(){
echo "transfer: $SHLVL"
srcFile=$1
dstFile=$2
if [ ! -e "$srcFile" ];then
echo "$srcFile文件不存在,檢查文件" >&2
exit 1
fi
cat hosts|while read host
do
echo "*********tranfer to $host*********"
ssh -n -o StrictHostKeyChecking=no fpc@$host "if [ ! -e "$dstFile" ];then mkdir -p $dstFile;fi;"
scp -r -o StrictHostKeyChecking=no $srcFile fpc@$host:$dstFile
if [ $? -eq 0 ];then
echo "transfer $srcFile to $host:$dstFile success"
else
echo "transfer $srcFile to $host:$dstFile failed"
fi
done
}
transfer $1 $2
部署kafka,zookeeper: #!/usr/bin/env bash #創建用戶 #exec >init_system.log 2>&1 USER=$1 ROOT_PASSWD="Fpc11100885" INIT_PASSWD="fPc11100885" SRC_DIR="/root/srcdata" DST_DIR="/opt/dstdata" JAVA_PACKAGE="jdk-8u211-linux-x64.tar.gz" ZOOKEEPER_PACKAGE="zookeeper-3.4.14.tar.gz" SCALA_PACKAGE="scala-2.13.0-M5.tgz" KAFKA_PACKAGE="kafka_2.13-3.0.0.tgz" REMOTE_INSTALL_DIR="/opt/install_dir" REMOTE_JAVA_DIR="/opt/java" REMOTE_ZOOKEEPER_DIR="/opt/zookeeper" REMOTE_SCALA_DIR="/opt/scala" REMOTE_KAFKA_DIR="/opt/kafka" #免密 remote_hosts=" 192.168.1.10 192.168.1.11 192.168.1.12" remote_exec(){ for host in $remote_hosts do for cmd in "$@" do #echo $#:$cmd ssh -o StrictHostKeyChecking=no root@$host "$cmd" & done wait echo "remote exec并發執行結束" done } remote_transfer(){ for host in $remote_hosts do echo "**********$host:remote tranfer file:************BASH_SUBSHELL:$BASH_SUBSHELL*****************SHLVL:$SHLVL*************************" ssh -n -o StrictHostKeyChecking=no root@$host "if [ ! -e \"$DST_DIR\" ];then mkdir -p $\"$DST_DIR\";fi" if [ ! -e "$SRC_DIR" -a ! -e "$SRC_DIR/${JAVA_PACKAGE}" ];then echo "檢查目錄或者文件是否存在" exit 1 fi for file in "$@" do echo $#:$file scp -r "$SRC_DIR"/"${file}" root@"${host}":"${DST_DIR}" & done wait echo "remote transfer并發執行結束" done } if [ -d /root/.ssh ];then rm -rf /root/.ssh;fi echo "" |ssh-keygen -t rsa -P "" for host in $remote_hosts do sshpass -p "$ROOT_PASSWD" ssh -n -o StrictHostKeyChecking=no -l root $host "userdel -r $USER;useradd $USER;echo "$INIT_PASSWD"|passwd --stdin $USER" sshpass -p "$INIT_PASSWD" ssh-copy-id $USER@$host sshpass -p "$ROOT_PASSWD" ssh-copy-id root@$host done # 關閉防火墻和firellwalld #for host in $hosts #do # ssh -o StrictHostKeyChecking=no root@$host "systemctl stop firewalld || systemctl disabled firewalld" # ssh -o StrictHostKeyChecking=no root@$host "setenforce 0 || sed -ir \"s#SELINUXTYPE=targeted#SELINUXTYPE=disabled#g\" /etc/selinux/config" #done cmd1="systemctl stop firewalld || systemctl disabled firewalld" cmd2="setenforce 0 || sed -ir \"s#SELINUXTYPE=targeted#SELINUXTYPE=disabled#g\" /etc/selinux/config" commands=("$cmd1" "$cmd2") #remote_exec "${cmd1}" "${cmd2}" remote_exec "${commands[@]}" #安裝jdk remote_exec "yum remove java-11-openjdk* java-1.8.0-openjdk* -y" remote_exec "yum list installed | grep java &>/dev/null && echo \"卸載jdk失敗\" || echo \"卸載jdk成功\" " remote_exec "if [ -e $REMOTE_INSTALL_DIR ];then rm -rf ${REMOTE_INSTALL_DIR};fi" remote_exec "mkdir -p ${REMOTE_INSTALL_DIR}" remote_exec "if [ -e $REMOTE_JAVA_DIR ];then rm -rf ${REMOTE_JAVA_DIR};fi" remote_exec "if [ -e $REMOTE_ZOOKEEPER_DIR ];then rm -rf ${REMOTE_ZOOKEEPER_DIR};fi" remote_transfer "${JAVA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${JAVA_PACKAGE} -C ${REMOTE_INSTALL_DIR}/" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/jdk1.8.0_211 ${REMOTE_JAVA_DIR}" cat >${SRC_DIR}/export_java_env.sh <<-EOF export JAVA_HOME=${REMOTE_JAVA_DIR} export PATH=\$PATH:\${JAVA_HOME}/bin:\${JAVA_HOME}/jre EOF remote_transfer "export_java_env.sh" remote_exec "mv ${DST_DIR}/export_java_env.sh /etc/profile.d/" remote_exec "source /etc/profile.d/export_java_env.sh" remote_exec "which java;java -version" #停止舊kafka和zookeeper進程,刪除zookeerp上的broker節點 remote_exec "ps -ef|grep -v grep|grep kafka|awk '{print \$2}' > /tmp/kfz_pid" remote_exec 'if [ -s /tmp/kfz_pid ];then kill -9 `cat /tmp/kfz_pid`;rm -f /tmp/kfz_pid;fi' remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkCli.sh rmr /brokers/ids/" remote_exec "jps|grep QuorumPeerMain|grep -v grep|cut -d\" \" -f1 > /tmp/zoo.pid;cat /tmp/zoo.pid" remote_exec 'if [ -s /tmp/zoo.pid ];then kill -9 `cat /tmp/zoo.pid`;rm -f /tmp/zoo.pid;fi ' #安裝zookeeper remote_transfer "${ZOOKEEPER_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${ZOOKEEPER_PACKAGE} -C ${REMOTE_INSTALL_DIR}/" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/zookeeper-3.4.14 ${REMOTE_ZOOKEEPER_DIR}" cat > ${SRC_DIR}/hosts <<-EOF 192.168.1.10 zookeeper1 192.168.1.11 zookeeper2 192.168.1.12 zookeeper3 EOF remote_transfer "hosts" remote_exec "\mv ${DST_DIR}/hosts /etc/hosts -f" #remote_exec "bash;hostname" remote_exec "\cp -f ${REMOTE_ZOOKEEPER_DIR}/conf/{zoo_sample,zoo}.cfg" cat >${SRC_DIR}/zoo.tmp <<-EOF server.1=192.168.1.10:2888:3888 server.2=192.168.1.11:2888:3888 server.3=192.168.1.12:2888:3888 EOF remote_transfer zoo.tmp remote_exec "cat ${DST_DIR}/zoo.tmp >> ${REMOTE_ZOOKEEPER_DIR}/conf/zoo.cfg" remote_exec "if [ ! -e /tmp/zookeeper ];then mkdir /tmp/zookeeper;fi" remote_exec 'if [ `hostname` == "zookeeper1" ];then echo 1 > /tmp/zookeeper/myid;fi' remote_exec 'if [ `hostname` == "zookeeper2" ];then echo 2 > /tmp/zookeeper/myid;fi' remote_exec 'if [ `hostname` == "zookeeper3" ];then echo 3 > /tmp/zookeeper/myid;fi' remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkServer.sh start" remote_exec "${REMOTE_ZOOKEEPER_DIR}/bin/zkServer.sh status" sleep 10 #安裝scala remote_transfer "${SCALA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${SCALA_PACKAGE} -C ${REMOTE_INSTALL_DIR}" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/scala-2.13.0-M5 ${REMOTE_SCALA_DIR}" cat > ${SRC_DIR}/export_scala_env.sh<<-EOF export SCALA_HOME=${REMOTE_SCALA_DIR} export PATH=\$PATH:\${SCALA_HOME}/bin EOF remote_transfer "export_scala_env.sh" remote_exec "mv ${DST_DIR}/export_scala_env.sh /etc/profile.d/" remote_exec "source /etc/profile.d/export_scala_env.sh" remote_exec "which scala;scala --version" #安裝kafka remote_exec "if [ -e /tmp/kafka-logs ];then rm -rf /tmp/kafka-logs;fi" remote_transfer "${KAFKA_PACKAGE}" remote_exec "tar xvf ${DST_DIR}/${KAFKA_PACKAGE} -C ${REMOTE_INSTALL_DIR}" remote_exec "ln -sf ${REMOTE_INSTALL_DIR}/kafka_2.13-3.0.0 ${REMOTE_KAFKA_DIR}" remote_exec 'if [ `hostname` == "zookeeper1" ];then sed -i "s#broker.id=0#broker.id=1#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'if [ `hostname` == "zookeeper2" ];then sed -i "s#broker.id=0#broker.id=2#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'if [ `hostname` == "zookeeper3" ];then sed -i "s#broker.id=0#broker.id=3#g" '"$REMOTE_KAFKA_DIR/config/server.properties;"'fi' remote_exec 'sed -i "/^\#listeners=/a\listeners=PLAINTEXT:\/\/`hostname -i`:9092" '"$REMOTE_KAFKA_DIR"'/config/server.properties' remote_exec 'sed -i "s#localhost:2181#192.168.1.10:2181,192.168.1.11:2181,192.168.1.12:2181#g" '"$REMOTE_KAFKA_DIR"'/config/server.properties' remote_exec "${REMOTE_KAFKA_DIR}/bin/kafka-server-start.sh -daemon ${REMOTE_KAFKA_DIR}/config/server.properties" sleep 30 #驗證kafka,分布式,只要在一臺執行即可 remote_exec "if [ \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --list --topic fpc |grep -v '^\$' >/tmp/topicIsAlive;fi" remote_exec "if [ -s /tmp/topicIsAlive -a \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --delete --topic fpc;fi" remote_exec "if [ \`hostname\` == \"zookeeper1\" ];then ${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --create --topic fpc --partitions 3 --replication-factor 3;fi" remote_exec "${REMOTE_KAFKA_DIR}/bin/kafka-topics.sh --describe --bootstrap-server 192.168.1.10:9092,192.168.1.11:9092,192.168.1.12:9092 --topic fpc"
集群一鍵啟停通用腳本:
設計集群的一鍵啟停,檢測,所有主機要同時檢測
1、進程起來很慢,花費30分鐘后進程起來后持續5分鐘掛掉(可能進程配置錯誤,進程所在服務器磁盤滿了等)
2、進程起來很快,花費1分鐘起來持續10分鐘掛掉
3、進程起來很快,然后一直持續正常
4、進程起來很慢,然后一直持續正常
[root@localhost ~]# cat kafka_tools.sh
hosts="192.168.1.10 192.168.1.11 192.168.1.12"
stop(){
for host in $hosts
do
status $host
if [ $? -ne 0 ];then
echo "$host:Kafka is already stopped"
continue
else
ssh -o StrictHostKeyChecking=no $host "/opt/kafka/bin/kafka-server-stop.sh"
count=0
while (( $count < 5 ))
do
status $host
if [ $? -ne 0 ];then
echo "$host:Kafka is stopped"
break
fi
echo "$host:Kafka is stopping,..please wait..."
sleep 3
let count++
done
if [ $count -eq 5 ];then
echo "進程停止失敗,請登錄$host排查"
fi
fi
done
}
start(){
for host in $hosts
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is already started"
continue
else
ssh -o StrictHostKeyChecking=no $host "/opt/kafka/bin/kafka-server-start.sh -daemon /opt/kafka/config/server.properties"
count=0
while (( $count < 5 ))
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is started"
break
fi
echo "$host:Kafka is starting,..please wait..."
sleep 3
let count++
#放在這里也可以
#if [ $count -eq 5 ];then
#echo "進程停止失敗,請登錄$host排查"
#fi
done
#放在這里也可以,因為都是上面if條件不滿足的時候才自增
if [ $count -eq 5 ];then
echo "進程停止失敗,請登錄$host排查"
fi
done
}
#要循環5次檢測進程都是正常,進程才算啟動起來了
status(){
sts_count=0
res_count=0
while [ $sts_count -lt 5 ]
do
ssh -o StrictHostKeyChecking=no $1 "jps|grep -w Kafka"
if [ $? -eq 0 ];then
res_count=`expr $res_count + 1`
fi
sts_count=`expr $sts_count + 1`
done
if [ $res_count -eq 5 ];then
return
else
return 100
fi
}
case $1 in
start)
start
;;
stop)
stop
;;
status)
#這里設計目的:是因為想要在start,stop里面去調用status函數,同時又能直接sh $0 status檢查進程狀態,所以將for循環放在這里
for host in $hosts
do
status $host
if [ $? -eq 0 ];then
echo "$host:Kafka is started"
else
echo "$host:Kafka is stopped"
fi
done
;;
*)
echo "USAGE: sh $0 (start,stop,status)"
;;
esac
ansible:
http://www.rzrgm.cn/michael-xiang/p/10462749.html
https://getansible.com/
https://www.zsythink.net/archives/tag/ansible/page/6/
http://www.rzrgm.cn/f-ck-need-u/p/7576137.html#ansible
https://www.bilibili.com/video/av33611758/?from=search&seid=7420958755659258683
https://ithelp.ithome.com.tw/users/20031776/ironman/1022
https://ansible-tran.readthedocs.io/en/latest/index.html
復雜示例#
寫出好的 ansible-playbook 還是要多閱讀優秀的 playbook,這里先列出幾個可供學習的示例資源:
- https://github.com/ansible/ansible-examples 一個面向初學者的 ansible playbook 收集倉庫
- https://galaxy.ansible.com/ui/ 這里就有很多流行的應用示例了,進階看
列出一個感覺比較清晰簡潔的例子#
- https://github.com/ansible/ansible-examples/tree/master/mongodb
- https://sourcegraph.com/github.com/ansible/ansible-examples/-/blob/mongodb/site.yml 對上面 mongodb 項目的查看,方便的工具,推薦
.
├── LICENSE.md
├── README.md
├── group_vars
│ └── all
├── hosts
├── images
│ ├── check.png
│ ├── nosql_primer.png
│ ├── replica_set.png
│ ├── scale.png
│ ├── sharding.png
│ └── site.png
├── playbooks
│ └── testsharding.yml
├── roles
│ ├── common
│ │ ├── files
│ │ │ ├── 10gen.repo.j2
│ │ │ ├── RPM-GPG-KEY-EPEL-6
│ │ │ └── epel.repo.j2
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ ├── hosts.j2
│ │ └── iptables.j2
│ ├── mongoc
│ │ ├── files
│ │ │ └── secret
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ ├── adduser.j2
│ │ ├── mongoc.conf.j2
│ │ └── mongoc.j2
│ ├── mongod
│ │ ├── files
│ │ │ └── secret
│ │ ├── tasks
│ │ │ ├── main.yml
│ │ │ └── shards.yml
│ │ └── templates
│ │ ├── mongod.conf.j2
│ │ ├── mongod.j2
│ │ ├── repset_init.j2
│ │ └── shard_init.j2
│ └── mongos
│ ├── files
│ │ └── secret
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ ├── enablesharding.j2
│ ├── mongos.conf.j2
│ ├── mongos.j2
│ └── testsharding.j2
├── site.yml
└── tree.txt
21 directories, 38 files
site.yml 文件的內容如下:
---
# This Playbook would deploy the whole mongodb cluster with replication and sharding.
- hosts: all
roles:
- role: common
- hosts: mongo_servers
roles:
- role: mongod
- hosts: mongoc_servers
roles:
- role: mongoc
- hosts: mongos_servers
roles:
- role: mongos
- hosts: mongo_servers
tasks:
- include: roles/mongod/tasks/shards.yml
https://www.junmajinlong.com/ansible/index/

浙公網安備 33010602011771號