shell學習

海_纳百川發表於2024-10-30

⼀、簡介

1.1 Shell 環境

Shell 程式設計跟 java、 php 程式設計⼀樣,只要有⼀個能編寫程式碼的⽂本編輯器和⼀個能解釋執⾏的指令碼直譯器就可以了。

Shell 的直譯器種類眾多,常⻅的有:

  • sh - 即 Bourne Shell。 sh 是 Unix 標準預設的 shell。
  • bash - 即 Bourne Again Shell。 bash 是 Linux 標準預設的 shell。
  • fish - 智慧和⽤戶友好的命令⾏ shell。
  • xiki - 使 shell 控制檯更友好,更強⼤。
  • zsh - 功能強⼤的 shell 與指令碼語⾔。

1.1.1 指定指令碼直譯器

在 shell 指令碼,#!告訴系統其後路徑所指定的程式即是解釋此指令碼⽂件的 Shell 直譯器。 #! 被稱作shebang(也稱為 Hashbang )

所以,你應該會在 shell 中,⻅到諸如以下的註釋:

  • 指定 sh 直譯器

    #!/bin/sh
    
  • 指定 bash 直譯器

    #!/bin/bash
    

注意

上⾯的指定直譯器的⽅式是⽐較常⻅的,但有時候,你可能也會看到下⾯的⽅式:

#!/usr/bin/env bash

這樣做的好處是,系統會⾃動在 PATH 環境變數中查詢你指定的程式(本例中的 bash )。相⽐第⼀種寫法,你應該儘量⽤這種寫法,因為程式的路徑是不確定的。這樣寫還有⼀個好處,作業系統的 PATH 變數有可能被配置為指向程式的另⼀個版本。⽐如,安裝完新版本的 bash ,我們可能將其路徑新增到 PATH 中,來“隱藏”⽼版本。如果直接⽤#!/bin/bash,那麼系統會選擇⽼版本的 bash 來執⾏指令碼,如果⽤ #!/usr/bin/env bash ,則會使⽤新版本。

1.1.2 模式

shell 有互動和⾮互動兩種模式。

互動模式

簡單來說,你可以將 shell 的互動模式理解為執⾏命令⾏。

看到形如下⾯的東⻄,說明 shell 處於互動模式下:

user@host:~$

接著,便可以輸⼊⼀系列 Linux 命令,⽐如 lsgrepcdmkdirrm 等等。

⾮互動模式

簡單來說,你可以將 shell 的⾮互動模式理解為執⾏ shell 指令碼。

在⾮互動模式下, shell 從⽂件或者管道中讀取命令並執⾏。
當 shell 直譯器執⾏完⽂件中的最後⼀個命令, shell 程序終⽌,並回到⽗程序。

可以使⽤下⾯的命令讓 shell 以⾮互動模式運⾏:

sh /root/shell//script.sh
bash /root/shell/script.sh
source/root/shell/script.sh
/root/shell/script.sh

上⾯的例⼦中, script.sh 是⼀個包含 shell 直譯器可以識別並執⾏的命令的普通⽂本⽂件, sh 和 bash 是 shell直譯器程式。你可以使⽤任何喜歡的編輯器建立script.sh (vim, nano, Sublime Text, Atom 等等)。

其中, source /shell/script.sh 和 ./shell/script.sh 是等價的。

除此之外,你還可以透過 chmod 命令給⽂件新增可執⾏的許可權,來直接執⾏指令碼⽂件:

chmod +x /root/shell/script.sh #使指令碼具有執⾏許可權
/root/shell/script.sh

這種⽅式要求指令碼⽂件的第⼀⾏必須指明運⾏該指令碼的程式,⽐如:

『示例原始碼』

#!/usr/bin/env bash
echo "Hello, world!"

上⾯的例⼦中,我們使⽤了⼀個很有⽤的命令 echo 來輸出字串到螢幕上。

⼆、基本語法

2.1 直譯器

# 以下兩種⽅式都可以指定 shell 直譯器為 bash,第⼆種⽅式更好
#!/bin/bash
#!/usr/bin/env bash

2.2 註釋

  • 單⾏註釋 - 以# 開頭,到⾏尾結束。
  • 多⾏註釋 - 以:<<EOF 開頭,到EOF結束

『示例原始碼』

#--------------------------------------------
# shell 註釋示例
# author: yuan
#--------------------------------------------

# echo '這是單⾏註釋'

########## 這是分割線 ##########

:<<EOF
echo '這是多⾏註釋'
echo '這是多⾏註釋'
echo '這是多⾏註釋'
EOF

echo "learn shell script annotation "

2.3 echo

輸出普通字串:

echo "hello, world"
# Output: hello, world
echo "hello, \"yuan\""
# Output: hello, "yuan"

輸出含變數的字串:

name=wing
echo "hello, \"${name}\""
# Output: hello, "yuan"

輸出含換⾏符的字串:

# 輸出含換⾏符的字串
echo "YES\nNO"
# Output: YES\nNO

echo -e "YES\nNO" # -e 開啟轉義
# Output:
# YES
# NO

輸出含不換⾏符的字串:

echo "YES"
echo "NO"
# Output:
# YES
# NO

echo -e "YES\c" # -e 開啟轉義 \c 不換⾏
echo "NO"
# Output:
# YESNO

輸出重定向⾄⽂件

echo "test" > test.txt

輸出執⾏結果

echo `pwd`
# Output:(當前⽬錄路徑)

2.4 printf

printf ⽤于格式化輸出字串。

預設, printf 不會像 echo ⼀樣⾃動新增換⾏符,如果需要換⾏可以⼿動新增 \n 。

printf 的轉義符

序列 說明
\a 警告字元,通常為 ASCII 的 BEL 字元
\b 後退
\c 抑制(不顯示)輸出結果中任何結尾的換⾏字元(只在%b 格式指示符控制下的引數字串中有 效),⽽且,任何留在引數⾥的字元、任何接下來的引數以及任何留在格式字串中的字元,都被忽略
\f 換⻚(formfeed)
\n 換⾏
\r 回⻋(Carriage return)
\t ⽔平製表符
\v 垂直製表符
\\ ⼀個字⾯上的反斜槓字元
\ddd 表示 1 到 3 位數⼋進位制值的字元。僅在格式字串中有效
\0ddd 表示 1 到 3 位的⼋進位制值字元

**『示例原始碼』 **

# 單引號
printf '%d %s\n' 1 "abc"
# Output:1 abc

# 雙引號
printf "%d %s\n" 1 "abc"
# Output:1 abc

# ⽆引號
printf %s abcdef
# Output: abcdef(並不會換⾏)

# 格式只指定了⼀個引數,但多出的引數仍然會按照該格式輸出
printf "%s\n" abc def
# Output:
# abc
# def

printf "%s %s %s\n" a b c d e f g h i j
# Output:
# a b c
# d e f
# g h i
# j

# 如果沒有引數,那麼 %s ⽤ NULL 代替, %d ⽤ 0 代替
printf "%s and %d \n"
# Output:
# and 0

# 格式化輸出
printf "%-10s %-8s %-4s\n" 姓名 性別 體重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 楊過 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 ⼥ 47.9876
# Output:
# 姓名 性別 體重kg
# 郭靖 男 66.12
# 楊過 男 48.65
# 郭芙 ⼥ 47.99

三、變數

跟許多程式設計語⾔⼀樣,你可以在 bash 中建立變數。

Bash 中沒有資料型別, bash 中的變數可以儲存⼀個數字、⼀個字元、⼀個字串等等。同時⽆需提前宣告變數,給變數賦值會直接建立變數。

3.1 變數命名原則

  • 命名只能使⽤英⽂字⺟,數字和下劃線,⾸個字元不能以數字開頭。
  • 中間不能有空格,可以使⽤下劃線(_)。
  • 不能使⽤標點符號。
  • 不能使⽤ bash ⾥的關鍵字(可⽤ help 命令檢視保留關鍵字)。

3.2 宣告變數

訪問變數的語法形式為:${var}$var

變數名外⾯的花括號是可選的,加不加都⾏,加花括號是為了幫助直譯器識別變數的邊界,所以推薦加花括號。

word="hello"
echo ${word}
echo $word
# Output: hello

3.3 只讀變數

使⽤ readonly 命令可以將變數定義為只讀變數,只讀變數的值不能被改變。

rword="hello"
echo ${rword}
readonly rword
# rword="bye" # 如果放開註釋,執⾏時會報錯

3.4 刪除變數

使⽤ unset 命令可以刪除變數。變數被刪除後不能再次使⽤。 unset 命令不能刪除只讀變數。

dword="hello" # 宣告變數
echo ${dword} # 輸出變數值
# Output: hello

unset dword # 刪除變數
echo ${dword}
# Output: (空)

3.5 變數作用域

  • 區域性變數 - 區域性變數是僅在某個指令碼內部有效的變數。它們不能被其他的程式和指令碼訪問。
  • 環境變數 - 環境變數是對當前 shell 會話內所有的程式或指令碼都可見的變數。建立它們跟建立區域性變數類似,但使⽤的是 export 關鍵字, shell 指令碼也可以定義環境變數。

常見的系統環境變數:

變數 描述
$HOME 當前⽤戶的⽤戶⽬錄
$SHELL 預設 Shell
$LANG 預設語⾔
$PATH ⽤分號分隔的⽬錄列表, shell 會到這些⽬錄中查詢命令
$HOSTNAME 主機名
$PWD 當前⼯作⽬錄
$RANDOM 0 到 32767 之間的整數
$UID 數值型別,當前⽤戶的⽤戶 ID
$PS1 主要系統輸⼊提示符
$PS2 次要系統輸⼊提示符

這⾥ 有⼀張更全⾯的 Bash 環境變數列表。

注意系統環境變數載入順序

執⾏順序: /etc/profile -> ~/.bash_profile -> ~/.bashrc -> /etc/bashrc -> ~/.bash_logout

3.6 位置變數

位置變數指的是函式或指令碼後跟的第n 個引數。
$1-$n,需要注意的是從第 10 個開始要⽤花括號調⽤,例如:

#!/bin/bash
echo "1: $1"
echo "2: $2"
echo "3: $3"
echo "$*"
echo "$@"

[root@localhost shell]# ./test.sh a b c d
1: a
2: b
3: c
a b c d
a b c d
#!/bin/bash
echo "1: $1"
shift
echo "2: $2"
shift
echo "3: $3"

# bash /test01.sh a b c 1: e
1: a
2: c
3: e

[root@localhost shell]# ./test01.sh a a b b c 
1: a
2: b
3: c

每執⾏⼀次 shift 命令,位置變數個數就會減⼀,⽽變數值則提前⼀位。 shift n,可設定向前移動n 位。

3.7 特殊變數 √

$0 指令碼自身名字
$? 返回上⼀條命令是否執⾏成功, 0 為執⾏成功,⾮ 0 則為執⾏失敗
$# 位置引數總數
$* 所有的位置引數被看做⼀個字串 √
$@ 每個位置引數被看做獨⽴的字串 √
$$ 當前程序 PID
$! 上⼀條運⾏後臺程序的 PID
#!/bin/bash
#$0 返回指令碼名稱
echo $0

# $?: 上個命令的退出狀態, 0 表示成功,⾮零值表示失敗
echo "Executing ls command"
ls
echo "Exit status of ls command: $?"

# $#: 命令⾏引數的個數
if [ $# -gt 0 ]; then
echo "Number of input arguments: $#"
echo "Input arguments: $@"
else
echo "No input arguments provided"
fi

# $*: 所有命令⾏引數作為單個字串
echo "Outputting all input arguments as single string: $*"

# $@: 所有命令⾏引數作為單獨的字串
echo "Outputting all input arguments as separate strings:"
for arg in "$@"; do
echo "$arg"
done

# $$: 當前程序的 PID
echo "PID of current process: $$"

# $!: 最後⼀次運⾏的後臺程序的 PID
echo "Starting sleep command in background"
sleep 5 &
echo "PID of sleep command: $!"

執⾏上述指令碼的結果類似於以下輸出:

Executing ls command
file1.txt
file2.txt
Exit status of ls command: 0

Number of input arguments: 3
Input arguments: arg1 arg2 arg3

Outputting all input arguments as single string: arg1 arg2 arg3
Outputting all input arguments as separate strings:
arg1
arg2
arg3

PID of current process: 1234

Starting sleep command in background
PID of sleep command: 5678

注意事項 $* $@區別

################### 系統變數 ###################
echo "UID:$UID"
echo LOGNAME:$LOGNAME
echo User:$USER
echo HOME:$HOME
echo PATH:$PATH
echo HOSTNAME:$HOSTNAME
echo SHELL:$SHELL
echo LANG:$LANG

################### ⾃定義變數 ###################
days=10
user="admin"
echo "$user logged in $days days age"
days=5
user="root"
echo "$user logged in $days days age"
# Output:
# admin logged in 10 days age
# root logged in 5 days age

################### 從變數讀取列表 ###################
colors="Red Yellow Blue"
colors=$colors" White Black"
for color in $colors
do
echo " $color"
done
# Output:
[root@localhost shell]# bash test03.sh 
 Red
 Yellow
 Blue
 White
 Black

四、字串

img

4.1 單引號和雙引號

shell 字串可以⽤單引號'',也可以⽤雙引號“ ”,也可以不⽤引號。

  • 單引號的特點
    • 單引號⾥不識別變數
    • 單引號⾥不能出現單獨的單引號(使⽤轉義符也不⾏),但可成對出現,作為字串拼接使⽤。
  • 雙引號的特點
    • 雙引號⾥識別變數
    • 雙引號⾥可以出現跳脫字元
#!/bin/bash

name='yuan'
echo 'Hello, $name'
# Output: Hello, $name

name='yuan'
echo "Hello, $name"
# Output: Hello, yuan

echo 'I'\''m a programmer'
# Output: I'm a programmer

echo "I'm a programmer"
# Output: I'm a programmer

echo "some text" # Output: some text
echo 'some text' # Output: some text

echo "the date is $(date)"
# Output: the date is Wed May 12 11:47:02 CST 2021
echo 'the date is $(date)'
# Output: the date is $(date)

在上述指令碼中,展示了單引號和雙引號之間的區別,主要包括以下⼏點:

  • 使⽤單引號時,其中的變數不會被展開。例如,在示例1中,由於使⽤了單引號,因此 $name 不會被展開,輸出的結果就是 "Hello, $name"
  • 使⽤雙引號時,其中的變數會被展開。例如,在示例2中,由於使⽤了雙引號,因此 $name 會被展開為
  • John,輸出的結果就是"Hello, John"
  • 使⽤單引號時,需要透過反斜槓來轉義其中的特殊字元,例如示例3。
  • 使⽤雙引號時,⼀些特殊字元不需要轉義,例如單引號和反斜槓,例如示例4。
  • 使⽤雙引號時,可以保留空格和製表符,例如示例5。
  • 使⽤單引號時,可以保留其中的所有字元,包括其中的特殊字元和變數名,例如示例6。

綜上所述,單引號和雙引號之間存在明顯的區別,需要根據具體情況和需求來選擇使⽤哪種引號。

4.2 拼接字串

# 使⽤單引號拼接
name1='white'
str1='hello, '${name1}''
str2='hello, ${name1}'
echo ${str1}_${str2}
# Output:
# hello, white_hello, ${name1}

# 使⽤雙引號拼接
name2="black"
str3="hello, "${name2}""
str4="hello, ${name2}"
echo ${str3}_${str4}
# Output:
# hello, black_hello, black

4.3 獲取字串長度

text="12345"
echo ${#text}
# Output:
# 5

4.4 擷取子字串

text="12345"
echo ${text:2:2}
# Output:
# 34

從第 2 個字元開始,擷取 2 個字元

4.5 從指定字元(子字串)開始擷取 √

1.使用# 號擷取右邊字元

使⽤ # 號可以擷取指定字元(或者⼦字串)右邊的所有字元,具體格式如下:

${string#*chars}
其中, string 表示要擷取的字元, chars 是指定的字元(或者⼦字串), *是萬用字元的⼀種,表示任意⻓度的字串。*chars連起來使⽤的意思是:忽略左邊的所有字元,直到遇見chars(chars 不會被擷取)。

請看下⾯的例⼦:

url="http://www.yuan.com/index.html"
echo ${url#*:}

結果為//www.yuan.com/index.html

以下寫法也可以得到同樣的結果:

echo ${url#*p:}
echo ${url#*ttp:}

如果不需要忽略 chars 左邊的字元,那麼也可以不寫 * ,例如:

url="http://www.yuan.com/index.html"
echo ${url#http://}

結果為 www.yuan.com/index.html
注意,以上寫法遇到第⼀個匹配的字元(⼦字串)就結束了。
例如:

url="http://www.yuan.com/index.html"
echo ${url#*/}

結果為 /www.yuan.com/index.html 。 url 字串中有三個 / ,輸出結果表明, Shell 遇到第⼀個 / 就匹配結束了。
如果希望直到最後⼀個指定字元(⼦字串)再匹配結束,那麼可以使⽤ ## ,具體格式為:

${string##*chars}

請看下⾯的例⼦:

#!/bin/bash
url="http://www.yuan.com/index.html"
echo ${url#*/}
#從左往右第一個/,擷取其右邊的字元
#結果為 /www.yuan.com/index.html

echo ${url##*/}
#結果為 index.html

str="---aa+++aa@@@"
echo ${str#*aa}
#結果為 +++aa@@@

echo ${str##*aa}
#從左往右匹配到最後一個字元aa,擷取其右邊的字元,結果為 @@@

2.使用 % 擷取左邊字元

使⽤ % 號可以擷取指定字元(或者⼦字串)左邊的所有字元,具體格式如下:

${string%chars*}

請注意 * 的位置,因為要擷取 chars 左邊的字元,⽽忽略 chars 右邊的字元,所以 * 應該位於 chars 的右側。其他⽅⾯ %# 的⽤法相同,這⾥不再贅述,僅舉例說明:

#!/bin/bash

url="http://www.yuan.com/index.html"
echo ${url%/*}
#從右往左第一個/,擷取其左邊的字元
#結果為 http://www.yuan.com

echo ${url%%/*}
#從右往左匹配到最後一個字元/,擷取其左邊的字元,結果為 http:

str="---aa+++aa@@@"
echo ${str%aa*}
#結果為 ---aa+++

echo ${str%%aa*}
#結果為 ---

彙總

對以上 8 種格式做⼀個彙總,請看下錶:

格式 說明
$ 從 string 字串的左邊第 start 個字元開始,向右擷取 length 個字元。
$ 從 string 字串的左邊第 start 個字元開始擷取,直到最後。
$ 從 string 字串的右邊第 start 個字元開始,向右擷取 length 個字元。
$ 從 string 字串的右邊第 start 個字元開始擷取,直到最後。
$ 從 string 字串第⼀次出現 *chars 的位置開始,擷取 *chars 右邊的所有字元。
$ 從 string 字串最後⼀次出現 *chars 的位置開始,擷取 *chars 右邊的所有字 符。
$ 從 string 字串第⼀次出現 *chars 的位置開始,擷取 *chars 左邊的所有字元。
$ 從 string 字串最後⼀次出現 *chars 的位置開始,擷取 *chars 左邊的所有字 符。

4.6 查詢子字串

#!/usr/bin/env bash
# expr 是計算命令,index 會返回索引ll第一次

text="hello"
echo `expr index "${text}" ll`

# Execute: ./str-demo5.sh
# Output:
# 3

查詢 ll ⼦字元在 hello 字串中的起始位置。

這個命令調⽤ expr 命令來查詢字串${text} 中⼦串 "ll" 的第⼀次出現位置。 "${text}" ⽤雙引號括起來,表示將變數 $text的值作為⼀個單獨的引數傳遞給 index 函式。注意,為了避免在$text的值中包含空格或其他特殊字元時出現錯誤,我們使⽤了雙引號將${text} 包起來。
最後,我們使⽤ echo 命令將 index 函式的輸出列印到螢幕上。為了調⽤ index 函式並將它的輸出作為引數傳遞給 echo 命令,我們使⽤反引號將整個表示式括起來,這會告訴 shell 執⾏該命令並將其輸出返回給 echo 命令。

在這種情況下, index 函式將返回 3 ,因為 "ll" 第⼀次出現在 ${text} 中的第三個位置(從1開始計數)。因此,命令的輸出為 3 。
這個命令的作⽤是在給定的字串 ${text} 中查詢⼦串 "ll" 第⼀次出現的位置(從1開始)。

『示例原始碼』

################### 判斷字串中是否包含⼦字串 ################### 用的較多 √
str="hello feature/"
result=$(echo "${str}" | grep "feature/")
if [[ "$result" != "" ]]; then
echo "feature/ 是 ${str} 的⼦字串"
else
echo "feature/ 不是 ${str} 的⼦字串"
fi

################### 擷取關鍵字右邊內容 ###################
full_branch="feature/1.0.0"
branch=`echo ${full_branch#feature/}`
echo "branch is ${branch}"
# Output:
# branch is 1.0.0

################### 擷取關鍵字左邊內容 ###################
full_version="0.0.1-SNAPSHOT"
version=`echo ${full_version%-SNAPSHOT}`
echo "version is ${version}"
# Output:
# version is 0.0.1

################### 字串分割成陣列 ###################
str="0.0.0.1"
OLD_IFS="$IFS"
IFS="."
array=( ${str} )
IFS="$OLD_IFS"
size=${#array[*]} 
lastIndex=`expr ${size} - 1`
echo "陣列⻓度: ${size}"
echo "最後⼀個陣列元素: ${array[${lastIndex}]}"
for item in ${array[@]}  #遍歷陣列
do
echo "$item"
done
# Output:
# 4
# 1
# 0
# 0
# 0
# 1

################### 判斷字串是否為空 ###################
#-n 判斷⻓度是否⾮零
#-z 判斷⻓度是否為零
str=testing
str2=''
if [[ -n "$str" ]]
then
echo "The string $str is not empty"
else
echo "The string $str is empty"
fi
if [[ -n "$str2" ]]
then
echo "The string $str2 is not empty"
else
echo "The string $str2 is empty"
fi
# Output:
# The string testing is not empty
# The string is empty

################### 字串⽐較 ###################
str=hello
str2=world
if [[ $str = "hello" ]]; then
echo "str equals hello"
else
echo "str not equals hello"
fi
if [[ $str2 = "hello" ]]; then
echo "str2 equals hello"
else
echo "str2 not equals hello"
fi
# Output:
# str equals hello
# str2 not equals hello

五、陣列

bash 只⽀持⼀維陣列。

陣列下標從 0 開始,下標可以是整數或算術表示式,其值應⼤於或等於 0。

5.1 建立陣列

shell 語⾔中⽀持關聯陣列。關聯陣列是⼀種基於鍵值對(key-value pairs)的資料結構,它允許你使⽤字串或數字作為索引來查詢和訪問陣列元素。在 shell 中,關聯陣列可以使⽤ declare -A 命令或者在陣列賦值時使⽤+= 符號來定義

下⾯是⼀個簡單的例⼦,展示瞭如何定義和使⽤關聯陣列:

# 宣告關聯陣列,併為其指定元素
declare -A fruits
fruits=([apple]="red" [banana]="yellow" [kiwi]="green")

# 訪問關聯陣列元素
echo "Apple is ${fruits[apple]} in color"
# Output:Apple is red in color
echo "Banana is ${fruits[banana]} in color"
# Output:Banana is yellow in color
echo "Kiwi is ${fruits[kiwi]} in color"
# Output:Kiwi is green in color

echo
# 迴圈輸出所有鍵值對
for key in "${!fruits[@]}"
do
echo "index: $key value: ${fruits[$key]}"
done
# Output:
# index: kiwi value: green
# index: apple value: red
# index: banana value: yellow

上⾯的程式碼建立了⼀個名為 fruits 的關聯陣列,並且透過指定元素的⽅式為其賦值。然後使⽤${fruits[apple]}等⽅式訪問元素值,它們的輸出結果即為對應的顏⾊。最後⼀個迴圈⽤於輸出所有鍵值對,加了!使⽤ ${!fruits[@]} 來獲得所有的鍵名,再使⽤ ${fruits[$key]} 來獲得對應的值。

# 建立陣列的不同⽅式
nums=([2]=2 [0]=0 [1]=1)
colors=(red yellow "dark blue")

5.2 訪問陣列元素

  • 訪問陣列的單個元素:
# 訪問下標為1的元素
echo ${nums[1]}
# Output: 1
  • 訪問陣列的所有元素:
echo ${colors[*]}
# Output: red yellow dark blue

echo ${colors[@]}
# Output: red yellow dark blue

上⾯兩⾏有很重要(也很微妙)的區別:

為了將陣列中每個元素單獨⼀⾏輸出,我們⽤ printf 命令:

printf "+ %s\n" ${colors[*]}
# Output:
# + red
# + yellow
# + dark
# + blue

為什麼 darkblue 各佔了⼀⾏?嘗試⽤引號包起來:

printf "+ %s\n" "${colors[*]}"
# Output:
# + red yellow dark blue

現在所有的元素都在⼀⾏輸出 —— 這不是我們想要的!讓我們試試 ${colors[@]}

printf "+ %s\n" "${colors[@]}"
# Output:
# + red
# + yellow
# + dark blue

在引號內, ${colors[@]} 將陣列中的每個元素擴充套件為⼀個單獨的引數;陣列元素中的空格得以保留。

  • 訪問陣列的部分元素:
 echo ${nums[@]:0:2}
 # Output:
 # 0 1

在上⾯的例⼦中, ${array[@]} 擴充套件為整個陣列, :0:2 取出了陣列中從 0 開始,⻓度為 2 的元素。

5.3 訪問陣列長度

echo ${#nums[*]}
# Output:
# 3

5.4 向陣列中新增元素

向陣列中新增元素也⾮常簡單:

colors=(white "${colors[@]}" green black)
echo ${colors[@]}
# Output:
# white red yellow dark blue green black

上⾯的例⼦中, ${colors[@]} 擴充套件為整個陣列,並被置換到複合賦值語句中,接著,對陣列 colors 的賦值覆蓋了它原來的值。

5.5 從陣列中刪除元素

unset 命令來從陣列中刪除⼀個元素:

unset nums[0]
echo ${nums[@]}
# Output:
# 1 2

六、運算子

6.1 算術運算子

下表列出了常⽤的算術運算子,假定變數 x 為 10,變數 y 為 20:

運算子 說明 舉例
+ 加法 expr �+y 結果為 30。
- 減法 expr �−y 結果為 -10。
* 乘法 expr �∗y 結果為 200。
/ 除法 expr �/x 結果為 2。
% 取餘 expr �x 結果為 0。
= 賦值 x=$y 將把變數 y 的值賦給 x。
== 相等 ⽤於⽐較兩個字串,相同則返回 true。 [ �==y ] 返回 false。
!= 不相等 ⽤於⽐較兩個數字,不相同則返回 true。 [ �!=y ] 返回 true。

注意: 條件表示式要放在⽅括號之間,並且要有空格,例如: [$x==$y] 是錯誤的,必須寫成 [ $x == $y ]
『示例原始碼』

x=10
y=20

echo "x=${x}, y=${y}"

val=`expr ${x} + ${y}`
echo "${x} + ${y} = $val"

val=`expr ${x} - ${y}`
echo "${x} - ${y} = $val"

val=`expr ${x} \* ${y}`
echo "${x} * ${y} = $val"

val=`expr ${y} / ${x}`
echo "${y} / ${x} = $val"

val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"

if [[ ${x} == ${y} ]]
then
echo "${x} = ${y}"
fi

if [[ ${x} != ${y} ]]
then
echo "${x} != ${y}"
fi

# Output:
# x=10, y=20
# 10 + 20 = 30
# 10 - 20 = -10
# 10 * 20 = 200
# 20 / 10 = 2
# 20 % 10 = 0
# 10 != 20

6.2 關係運算子 √

關係運算子只⽀持數字 ,不⽀持字串,除⾮字串的值是數字。

下表列出了常⽤的關係運算子,假定變數 a 為 10,變數b 為 20:

運算子 說明 舉例
-eq 檢測兩個數是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 檢測兩個數是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 檢測左邊的數是否⼤於右邊的,如果是,則返回 true。 [ $a -gt $b ] 返回 false。
-lt 檢測左邊的數是否⼩於右邊的,如果是,則返回 true。 [ $a -lt $b ] 返回 true。
-ge 檢測左邊的數是否⼤於等於右邊的,如果是,則返回 true。 [ $a -ge $b ] 返回 false。
-le 檢測左邊的數是否⼩於等於右邊的,如果是,則返回 true。 [ $a -le $b ] 返回 true。

『示例原始碼』

x=10
y=20

echo "x=${x}, y=${y}"

if [[ ${x} -eq ${y} ]]; then
echo "${x} -eq ${y} : x 等於 y"
else
echo "${x} -eq ${y}: x 不等於 y"
fi

if [[ ${x} -ne ${y} ]]; then
echo "${x} -ne ${y}: x 不等於 y"
else
echo "${x} -ne ${y}: x 等於 y"
fi

if [[ ${x} -gt ${y} ]]; then
echo "${x} -gt ${y}: x ⼤於 y"
else
echo "${x} -gt ${y}: x 不⼤於 y"
fi

if [[ ${x} -lt ${y} ]]; then
echo "${x} -lt ${y}: x ⼩於 y"
else
echo "${x} -lt ${y}: x 不⼩於 y"
fi

if [[ ${x} -ge ${y} ]]; then
echo "${x} -ge ${y}: x ⼤於或等於 y"
else
echo "${x} -ge ${y}: x ⼩於 y"
fi

if [[ ${x} -le ${y} ]]; then
echo "${x} -le ${y}: x ⼩於或等於 y"
else
echo "${x} -le ${y}: x ⼤於 y"
fi

# Output:
# x=10, y=20
# 10 -eq 20: x 不等於 y
# 10 -ne 20: x 不等於 y
# 10 -gt 20: x 不⼤於 y
# 10 -lt 20: x ⼩於 y
# 10 -ge 20: x ⼩於 y
# 10 -le 20: x ⼩於或等於 y

6.3 布林運算子

下表列出了常⽤的布林運算子,假定變數 a為 10,變數 b 為 20:

運算子 說明 舉例
! ⾮運算,表示式為 true 則返回 false,否則返回 true。 [ ! false ] 返回 true。
-o 或運算,有⼀個表示式為 true 則返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 與運算,兩個表示式都為 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

『示例原始碼』

x=10
y=20


echo "x=${x}, y=${y}"
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等於 y"
else
echo "${x} != ${y}: x 等於 y"
fi

if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
echo "${x} ⼩於 100 且 ${y} ⼤於 15 : 返回 true"
else
echo "${x} ⼩於 100 且 ${y} ⼤於 15 : 返回 false"
fi

if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
echo "${x} ⼩於 100 或 ${y} ⼤於 100 : 返回 true"
else
echo "${x} ⼩於 100 或 ${y} ⼤於 100 : 返回 false"
fi

if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
echo "${x} ⼩於 5 或 ${y} ⼤於 100 : 返回 true"
else
echo "${x} ⼩於 5 或 ${y} ⼤於 100 : 返回 false"
fi

# Output:
# x=10, y=20
# 10 != 20 : x 不等於 y
# 10 ⼩於 100 且 20 ⼤於 15 : 返回 true
# 10 ⼩於 100 或 20 ⼤於 100 : 返回 true
# 10 ⼩於 5 或 20 ⼤於 100 : 返回 false

6.4 邏輯運算子

以下介紹 Shell 的邏輯運算子,假定變數 x 為 10,變數 y 為 20:

運算子 說明 舉例
&& 邏輯的 AND [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false
|| 邏輯的 OR `[[ ${x} -lt 100

『示例原始碼』

x=10
y=20


echo "x=${x}, y=${y}"
if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi

if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi

# Output:
# x=10, y=20
# 10 -lt 100 && 20 -gt 100 返回 false
# 10 -lt 100 || 20 -gt 100 返回 true

6.5 字串運算子 √

下表列出了常⽤的字串運算子,假定變數 a 為 “abc”,變數 b 為 “efg”:

運算子 說明 舉例
== 檢測兩個字串是否相等,相等返回 true。 [ �==b ] 返回 false。
!= 檢測兩個字串是否相等,不相等返回 true。 [ �!=b ] 返回 true。
-z 檢測字串⻓度是否為 0,為 0 返回 true。 [ -z $a ] 返回 false。
-n 檢測字串⻓度是否為 0,不為 0 返回 true。 [ -n $a ] 返回 true。
str 檢測字串是否為空,不為空返回 true。 [ $a ] 返回 true。

『示例原始碼』

x="abc"
y="xyz"

echo "x=${x}, y=${y}"

if [[ ${x} == ${y} ]]; then
echo "${x} = ${y} : x 等於 y"
else
echo "${x} = ${y}: x 不等於 y"
fi

if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等於 y"
else
echo "${x} != ${y}: x 等於 y"
fi

if [[ -z ${x} ]]; then
echo "-z ${x} : 字串⻓度為 0"
else
echo "-z ${x} : 字串⻓度不為 0"
fi

if [[ -n "${x}" ]]; then
echo "-n ${x} : 字串⻓度不為 0"
else
echo "-n ${x} : 字串⻓度為 0"
fi

if [[ ${x} ]]; then
echo "${x} : 字串不為空"
else
echo "${x} : 字串為空"
fi

# Output:
# x=abc, y=xyz
# abc = xyz: x 不等於 y
# abc != xyz : x 不等於 y
# -z abc : 字串⻓度不為 0
# -n abc : 字串⻓度不為 0
# abc : 字串不為空

6.6 檔案測試運算子 √

⽂件測試運算子⽤於檢測 Unix ⽂件的各種屬性。
屬性檢測描述如下:

運算子 說明 舉例
-b file 檢測⽂件是否是塊裝置⽂件,如果是,則返回 true。 [ -b $file ] 返回 false。
-c file 檢測⽂件是否是字元裝置⽂件,如果是,則返回 true。 [ -c $file ] 返回 false。
-d file 檢測⽂件是否是⽬錄,如果是,則返回 true。 √ [ -d $file ] 返回 false。
-f file 檢測⽂件是否是普通⽂件(既不是⽬錄,也不是裝置⽂件),如果 是,則返回 true。 √ [ -f $file ] 返回 true。
-g file 檢測⽂件是否設定了 SGID 位,如果是,則返回 true。 [ -g $file ] 返回 false。
-k file 檢測⽂件是否設定了粘著位(Sticky Bit),如果是,則返回 true。 [ -k $file ] 返回 false。
-p file 檢測⽂件是否是有名管道,如果是,則返回 true。 [ -p $file ] 返回 false。
-u file 檢測⽂件是否設定了 SUID 位,如果是,則返回 true。 [ -u $file ] 返回 false。
-r file 檢測⽂件是否可讀,如果是,則返回 true。 √ [ -r $file ] 返回 true。
-w file 檢測⽂件是否可寫,如果是,則返回 true。 √ [ -w $file ] 返回 true。
-x file 檢測⽂件是否可執⾏,如果是,則返回 true。 √ [ -x $file ] 返回 true。
-s file 檢測⽂件是否為空(⽂件⼤⼩是否⼤於 0),不為空返回 true。 [ -s $file ] 返回 true。
-e file 檢測⽂件(包括⽬錄)是否存在,如果是,則返回 true。 √ [ -e $file ] 返回 true。
-L file 檢測⽂件(包括⽬錄)是否為符號連結(軟連結),如果是,則返回 true。 √ [ -L $file ] 返回 true。

『示例原始碼』

file="/etc/hosts"

if [[ -r ${file} ]]; then
echo "${file} ⽂件可讀"
else
echo "${file} ⽂件不可讀"
fi

if [[ -w ${file} ]]; then
echo "${file} ⽂件可寫"
else
echo "${file} ⽂件不可寫"
fi

if [[ -x ${file} ]]; then
echo "${file} ⽂件可執⾏"
else
echo "${file} ⽂件不可執⾏"
fi

if [[ -f ${file} ]]; then
echo "${file} ⽂件為普通⽂件"
else
echo "${file} ⽂件為特殊⽂件"
fi

if [[ -d ${file} ]]; then
echo "${file} ⽂件是個⽬錄"
else
echo "${file} ⽂件不是個⽬錄"
fi

if [[ -s ${file} ]]; then
echo "${file} ⽂件不為空"
else
echo "${file} ⽂件為空"
fi

if [[ -e ${file} ]]; then
echo "${file} ⽂件存在"
else
echo "${file} ⽂件不存在"
fi

# Output:(根據⽂件的實際情況,輸出結果可能不同)
# /etc/hosts ⽂件可讀
# /etc/hosts ⽂件可寫
# /etc/hosts ⽂件不可執⾏
# /etc/hosts ⽂件為普通⽂件
# /etc/hosts ⽂件不是個⽬錄
# /etc/hosts ⽂件不為空
# /etc/hosts ⽂件存在

七、控制語句

7.1 條件語句

跟其它程式設計語⾔⼀樣, Bash 中的條件語句讓我們可以決定⼀個操作是否被執⾏。結果取決於⼀個包在 [[ ]]
⾥的表示式。

[[ ]] ( sh 中是 [ ] )包起來的表示式被稱作 檢測命令基元。這些表示式幫助我們檢測⼀個條件的結果。這⾥可以找到有關bash 中單雙中括號區別的答案。

在 Bash Shell 中,⽅括號 ("[]") 和雙⽅括號 ("[[]]") ⽤於條件測試和模式匹配。⽅括號和雙⽅括號在⼤多數情況下是等效的,但是雙⽅括號提供了更多的功能和可讀性。

具體來說,下⾯是它們的區別:

  1. 雙⽅括號⽀持⾼級字串操作,⽐如 =~ 正則匹配和 == 不區分⼤⼩寫的字串⽐較,⽽⽅括號不⽀持這些
    操作。
  2. 雙⽅括號中的變數不需要使⽤雙引號引起來,即使變數值中包含空格或其他特殊字元也可以正確⽐較。但⽅括
    號中的變數需要使⽤雙引號引起來,否則可能會導致錯誤的⽐較結果。
  3. 雙⽅括號⽀持多個條件的邏輯與和邏輯或,⽐如 [[ $name == "John" && $age -eq 20 ]] ,⽽⽅括號則
    需要使⽤ -a 和 -o 選項實現這個功能。
  4. 雙⽅括號中的引數擴充套件(Parameter expansion)和命令替換(Command substitution)需要使⽤跳脫字元
    " \ ",⽽⽅括號則可以直接使⽤。
  5. 雙⽅括號中的模式匹配,預設情況下不進⾏⽂件名擴充套件,⽽⽅括號預設進⾏⽂件名擴充套件。
  6. 雙⽅括號的錯誤處理更友好,可以使⽤ set -o errexit 命令開啟錯誤檢查功能(如果任何⼀個⼦命令返回
    ⾮零退出碼,則整個條件測試就會失敗),⽽⽅括號不⽀持這個功能。

雙⽅括號提供了更多的功能和更直觀的語法,所以⼀般建議在 Bash Shell 中使⽤雙⽅括號進⾏條件測試。

共有兩個不同的條件表示式: ifcase

if

(1)if 語句
if在使⽤上跟其它語⾔相同。如果中括號⾥的表示式為真,那麼thenfi之間的程式碼會被執⾏。fi標誌著條
件程式碼塊的結束。

# 寫成⼀⾏
if [[ 1 -eq 1 ]]; then
echo "1 -eq 1 result is: true";
fi
# Output: 1 -eq 1 result is: true

# 寫成多⾏
if [[ "abc" -eq "abc" ]]
then
echo ""abc" -eq "abc" result is: true"
fi
# Output: abc -eq abc result is: true

(2) if else 語句

同樣,我們可以使⽤ if..else 語句,例如:

if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true

(3) if elif else 語句
有些時候, if..else 不能滿⾜我們的要求。別忘了 if..elif..else ,使⽤起來也很⽅便。

『示例原始碼』

x=10
y=20
if [[ ${x} > ${y} ]]; then
echo "${x} > ${y}"
elif [[ ${x} < ${y} ]]; then
echo "${x} < ${y}"
else
echo "${x} = ${y}"
fi
# Output: 10 < 20

case

如果你需要⾯對很多情況,分別要採取不同的措施,那麼使⽤ case 會⽐巢狀的 if 更有⽤。使⽤ case 來解決複雜
的條件判斷,看起來像下⾯這樣:
『示例原始碼』

x=10
y=20
oper=$1
case ${oper} in
"+")
val=`expr ${x} + ${y}`
echo "${x} + ${y} = ${val}"
;;
"-")
val=`expr ${x} - ${y}`
echo "${x} - ${y} = ${val}"
;;
"*")
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = ${val}"
;;
"/")
val=`expr ${x} / ${y}`
echo "${x} / ${y} = ${val}"
;;
*)
echo "Unknown oper!"
;;
esac

每種情況都是匹配了某個模式的表示式。 | ⽤來分割多個模式, ) ⽤來結束⼀個模式序列。第⼀個匹配上的模式對應的命令將會被執⾏。 * 代表任何不匹配以上給定模式的模式。命令塊⼉之間要⽤ ;; 分隔。

# sh cal.sh '+'
10 + 20 = 30
# sh cal.sh '-'
10 - 20 = -10
# sh cal.sh '/'
10 / 20 = 0
# sh cal.sh '%'
Unknown oper!

7.2 迴圈語句

迴圈其實不⾜為奇。跟其它程式設計語⾔⼀樣, bash 中的迴圈也是隻要控制條件為真就⼀直迭代執⾏的程式碼塊。
Bash 中有四種迴圈: forwhileuntilselect

for 迴圈

for 與它在 C 語⾔中迴圈⾮常像。看起來是這樣:

for arg in elem1 elem2 ... elemN
do
	### 語句
done

在每次迴圈的過程中, arg 依次被賦值為從 elem1elemN。這些值還可以是萬用字元或者⼤括號擴充套件。
當然,我們還可以把 for 迴圈寫在⼀⾏,但這要求do之前要有⼀個分號,就像下⾯這樣:

for i in {1..5}; do echo $i; done

還有,如果你覺得 for..in..do 對你來說有點奇怪,那麼你也可以像 C 語⾔那樣使⽤ for,⽐如:

for (( i = 0; i < 10; i++ )); do
	echo $i
done

當我們想對⼀個⽬錄下的所有⽂件做同樣的操作時, for 就很⽅便了。舉個例⼦,如果我們想把所有的 .sh ⽂件移
動到 script ⽂件夾中,並給它們可執⾏許可權,我們的指令碼可以這樣寫:

『示例原始碼』

DIR=/home/yuan
for FILE in ${DIR}/*.sh; do
/bcp "$FILE" "${DIR}/scripts"
done

# 將 /home/yuan ⽬錄下所有 sh ⽂件拷⻉到 /home/yuan/scripts

#cp 加絕對路徑 或者 前面 \cp 去覆蓋

while 迴圈

while 迴圈檢測⼀個條件,只要這個條件為 真,就執⾏⼀段命令。被檢測的條件跟 if..then 中使⽤的基元並⽆⼆異。因此⼀個 while 迴圈看起來會是這樣:

while [[ condition ]]
do
	### 語句
done

for 迴圈⼀樣,如果我們把 do和被檢測的條件寫到⼀⾏,那麼必須要在do之前加⼀個分號。

『示例原始碼』

### 0到9之間每個數的平⽅
x=0
while [[ ${x} -lt 10 ]]; do
	echo $((x * x))
	x=$((x + 1))
done
# Output:
# 0
# 1
# 4
# 9
# 16
# 25
# 36
# 49
# 64
# 81

⽆限迴圈

while [ 1 ]; do
	#監控某個服務
	ps aux |egrep sshd >/dev/null
	#服務掛掉了
	if [[ $? -ne 0 ]];then
		system start sshd
	fi
	echo "ssh service up"
	sleep 30
done

until迴圈

until 迴圈跟while 迴圈正好相反。它跟 while ⼀樣也需要檢測⼀個測試條件,但不同的是,只要該條件為 假 就⼀直執⾏迴圈:

『示例原始碼』

x=0
until [[ ${x} -ge 5 ]]; do
	echo ${x}
	x=`expr ${x} + 1`
done
# Output:
# 0
# 1
# 2
# 3
# 4

select 迴圈

select迴圈幫助我們組織⼀個⽤戶選單。它的語法⼏乎跟for迴圈⼀致:

select answer in elem1 elem2 ... elemN
do
	### 語句
done

select 會列印`` elem1..elemN 以及它們的序列號到螢幕上,之後會提示⽤戶輸⼊。通常看到的是$? PS3變數)。⽤戶的選擇結果會被儲存到 answer 中。如果 answer 是⼀個在1..N之間的數字,那麼 語句 會被執⾏,緊接著會進⾏下⼀次迭代 —— 如果不想這樣的話我們可以使⽤ break` 語句。

『示例原始碼』

#!/usr/bin/env bash

PS3="Choose the package manager: "
select ITEM in bower npm gem pip
do
echo -n "Enter the package name: " && read PACKAGE
case ${ITEM} in
bower)
echo "bower install ${PACKAGE}" ;;
npm)
echo "npm install ${PACKAGE}" ;;
gem)
echo "gem install ${PACKAGE}" ;;
pip)
echo " pip install ${PACKAGE}" ;;
esac
break # 避免⽆限迴圈
done

這個例⼦,先詢問⽤戶他想使⽤什麼包管理器。接著,⼜詢問了想安裝什麼包,最後執⾏安裝操作。
運⾏這個指令碼,會得到如下輸出:

$ ./select.sh
1) bower
2) npm
3) gem
4) pip
Choose the package manager: 2
Enter the package name: gitbook-cli

breakcontinue

如果想提前結束⼀個迴圈或跳過某次迴圈執⾏,可以使⽤ shell 的 breakcontinue語句來實現。它們可以在任何迴圈中使⽤。

break 語句⽤來提前結束當前迴圈。
continue語句⽤來跳過某次迭代。

『示例原始碼』

# 查詢 10 以內第⼀個能整除 2 和 3 的正整數
i=1
while [[ ${i} -lt 10 ]]; do
if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then
echo ${i}
break;
fi
i=`expr ${i} + 1`
done
# Output: 6

『示例原始碼』

# 列印10以內的奇數
for (( i = 0; i < 10; i ++ )); do
if [[ $((i % 2)) -eq 0 ]]; then
continue;
fi
echo ${i}
done
# Output:
# 1
# 3
# 5
# 7
# 9

⼋、函式

bash 函式定義語法如下:

[ function ] funname [()] {
	action;
	[return int;]
}

說明:

  1. 函式定義時,function關鍵字可有可⽆。
  2. 函式返回值 - return 返回函式返回值,返回值型別只能為整數(0-255)。如果不加 return 語句, shell
    預設將以最後⼀條命令的運⾏結果,作為函式返回值。
  3. 函式返回值在調⽤該函式後透過 $? 來獲得。
  4. 所有函式在使⽤前必須定義。這意味著必須將函式放在指令碼開始部分,直⾄ shell 直譯器⾸次發現它
    時,才可以使⽤。調⽤函式僅使⽤其函式名即可。

『示例原始碼』

#!/usr/bin/env bash

calc(){
PS3="choose the oper: "

select oper in + - \* / # ⽣成運算子選擇選單
do
echo -n "enter first num: " && read x # 讀取輸⼊引數
echo -n "enter second num: " && read y # 讀取輸⼊引數
exec
case ${oper} in
"+")
return $((${x} + ${y}))
;;
"-")
return $((${x} - ${y}))
;;
"*")
return $((${x} * ${y}))
;;
"/")
return $((${x} / ${y}))
;;
*)
echo "${oper} is not support!"
return 0
;;
esac
break
done
}
calc
echo "the result is: $?" # $? 獲取 calc 函式返回值

執⾏結果:

$ ./function-demo.sh
1) +
2) -
3) *
4) /
choose the oper: 3
enter first num: 10
enter second num: 10
the result is: 100

嘗試將上⾯的select修改成while迴圈實現 選單可以透過cat 實現

#!/bin/bash

echo "Welcome to My Menu"
echo "------------------"
cat << EndOfMenu
1. Option 1
2. Option 2
3. Option 3
4. Quit
EndOfMenu

while true; do
read -p "Please select an option: " option
case $option in
1) echo "You selected Option 1";;
2) echo "You selected Option 2";;
3) echo "You selected Option 3";;
4) echo "Quitting..."; exit;;
*) echo "Invalid option. Please try again.";;
esac
done

8.1 函式中如何使用位置引數 √

位置引數是在調⽤⼀個函式並傳給它引數時建立的變數。

位置引數變數表:

變數 描述
$0 指令碼名稱
1…9 第 1 個到第 9 個引數列表
10… 第 10 個到 N 個引數列表
$* or $@ 除了 $0 外的所有位置引數
$# | 不包括 $0 在內的位置引數的個數
$FUNCNAME 函式名稱(僅在函式內部有值)

『示例原始碼』

#!/usr/bin/env bash
# -n 引數長度不為0返回
x=0
if [[ -n $1 ]]; then
echo "第⼀個引數為: $1"
x=$1
else
echo "第⼀個引數為空"
fi

y=0
if [[ -n $2 ]]; then
echo "第⼆個引數為: $2"
y=$2
else
echo "第⼆個引數為空"
fi

paramsFunction(){
echo "函式第⼀個⼊參: $1"
echo "函式第⼆個⼊參: $2"
}
paramsFunction ${x} ${y}

執⾏結果:

$ ./function-demo2.sh
第⼀個引數為空
第⼆個引數為空
函式第⼀個⼊參: 0
函式第⼆個⼊參: 0

$ ./function-demo2.sh 10 20
第⼀個引數為: 10
第⼆個引數為: 20
函式第⼀個⼊參: 10
函式第⼆個⼊參: 20

執⾏ ./variable-demo4.sh hello world ,然後在指令碼中透過 $1$2 … 讀取第 1 個引數、第 2 個參
數。。。

8.2 函式處理引數

另外,還有⼏個特殊字元⽤來處理引數:

引數處理 說明
$# 返回引數個數
$* 返回所有引數
$$ 指令碼運⾏的當前程序 ID 號
$! 後臺運⾏的最後⼀個程序的 ID 號
$@ 返回所有引數
$- 返回 Shell 使⽤的當前選項,與 set 命令功能相同。
$? 函式返回值

『示例原始碼』

runner() {
return 0
}

name=wing
paramsFunction(){
echo "函式第⼀個⼊參: $1"
echo "函式第⼆個⼊參: $2"
echo "傳遞到指令碼的引數個數: $#"
echo "所有引數: "
printf "+ %s\n" "$*"
echo "指令碼運⾏的當前程序 ID 號: $$"
echo "後臺運⾏的最後⼀個程序的 ID 號: $!"
echo "所有引數: "
printf "+ %s\n" "$@"
echo "Shell 使⽤的當前選項: $-"
runner
echo "runner 函式的返回值: $?"
}
paramsFunction 1 "abc" "hello, \"yuan\""

# Output:
# 函式第⼀個⼊參: 1
# 函式第⼆個⼊參: abc
# 傳遞到指令碼的引數個數: 3
# 所有引數:
# + 1 abc hello, "yuan"
# 指令碼運⾏的當前程序 ID 號: 26400
# 後臺運⾏的最後⼀個程序的 ID 號:
# 所有引數:
# + 1
# + abc
# + hello, "yuan"
# Shell 使⽤的當前選項: hB
# runner 函式的返回值: 0


# ps:himBH 每個字母都代表了一個 shell 選項,具體如下
# h - hashall:bash 的 hash 功能,可以實現讓某些 command 和 具體路徑 繫結在一起。      
# i - interactive-comments:配置在互動 shell 模式下,是否允許註釋。	 
# m - monitor:配置是否開啟控制 Job control 功能。        	 
# B - braceexpand:關於括號使用的flag,開啟後可以快捷地實現某些效果    	 
# H-  history:是否允許用 “感嘆號 !+ history number ” 來執行歷史命令      

九、 Shell 擴充套件

擴充套件 發⽣在⼀⾏命令被分成⼀個個的 記號(tokens) 之後。換⾔之,擴充套件是⼀種執⾏數學運算的機制,還可以⽤
來儲存命令的執⾏結果,等等。

感興趣的話可以閱讀關於 shell 擴充套件的更多細節

大括號擴充套件

⼤括號擴充套件讓⽣成任意的字串成為可能。它跟 ⽂件名擴充套件 很類似,舉個例⼦:

echo beg{i,a,u}n ### begin began begun

[root@localhost shell]# echo beg{i,a,u}n | xargs -n1
begin
began
begun

⼤括號擴充套件還可以⽤來建立⼀個可被迴圈迭代的區間。

echo {0..5} ### 0 1 2 3 4 5
echo {00..8..2} ### 00 02 04 06 08

命令置換

命令置換允許我們對⼀個命令求值,並將其值置換到另⼀個命令或者變數賦值表示式中。當⼀個命令被 或 $()
圍時,命令置換將會執⾏。舉個例⼦:

now=`date +%T`
### or
now=$(date +%T)

echo $now ### 19:08:26

算數擴充套件

在 bash 中,執⾏算數運算是⾮常⽅便的。算數表示式必須包在 $(( )) 中。算數擴充套件的格式為 :

result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result ### 9

在算數表示式中,使⽤變數⽆需帶上 $ 字首:

x=4
y=7
echo $(( x + y )) ### 11
echo $(( ++x + y++ )) ### 12
echo $(( x + y )) ### 13

十、流和重定向

Bash 有很強⼤的⼯具來處理程式之間的協同⼯作。使⽤流,我們能將⼀個程式的輸出傳送到另⼀個程式或⽂件,
因此,我們能⽅便地記錄⽇志或做⼀些其它我們想做的事。

管道給了我們建立傳送帶的機會,控制程式的執⾏成為可能。

學習如何使⽤這些強⼤的、⾼級的⼯具是⾮常⾮常重要的。

10.1 輸入、輸出流

Bash 接收輸⼊,並以字元序列或 字元流 的形式產⽣輸出。這些流能被重定向到⽂件或另⼀個流中。

有三個⽂件描述符:

程式碼 描述符 描述
0 stdin 標準輸⼊
1 stdout 標準輸出
2 stderr 標準錯誤輸出

10.2 重定向

重定向讓我們可以控制⼀個命令的輸⼊來⾃哪⾥,輸出結果到什麼地⽅。這些運算子在控制流的重定向時會被⽤到:

Operator Description
> 重定向輸出
&> 重定向輸出和錯誤輸出
&>> 以附加的形式重定向輸出和錯誤輸出
< 重定向輸⼊
<< Here ⽂檔 語法
<<< Here 字串

以下是⼀些使⽤重定向的例⼦:

### ls的結果將會被覆蓋寫到list.txt中
ls -l > list.txt

### 將輸出追加附加到list.txt中
ls -a >> list.txt

### 所有的錯誤資訊會被寫到errors.txt中
grep da * 2> errors.txt

### 從errors.txt中讀取輸⼊
less < errors.txt

10.3 /dev/null ⽂件 √

如果希望執⾏某個命令,但⼜不希望在螢幕上顯示輸出結果,那麼可以將輸出重定向到 /dev/null:

$ command > /dev/null

/dev/null 是⼀個特殊的⽂件,寫⼊到它的內容都會被丟棄;如果嘗試從該⽂件讀取內容,那麼什麼也讀不到。但
是 /dev/null ⽂件⾮常有⽤,將命令的輸出重定向到它,會起到”禁⽌輸出”的效果。

如果希望遮蔽 stdout 和 stderr,可以這樣寫:

$ command > /dev/null 2>&1

十一、 Debug

shell 提供了⽤於 debug 指令碼的⼯具。一般 set -x

如果想採⽤ debug 模式運⾏某指令碼,可以在其中使⽤⼀個特殊的選項:

#!/bin/bash options

options 是⼀些可以改變 shell ⾏為的選項。下表是⼀些可能對你有⽤的選項:

Short Name Description
-f noglob 禁⽌⽂件名展開(globbing)
-i interactive 讓指令碼以 互動 模式運⾏
-n noexec 讀取命令,但不執⾏(語法檢查)
-t 執⾏完第⼀條命令後退出
-v verbose 在執⾏每條命令前,向 stderr 輸出該命令
-x xtrace 在執⾏每條命令前,向 stderr 輸出該命令以及該命令的擴充套件引數 √

舉個例⼦,如果我們在指令碼中指定了 -x 例如:

#!/bin/bash -x

for (( i = 0; i < 3; i++ )); do
	echo $i
done

這會向 stdout 列印出變數的值和⼀些其它有⽤的資訊:

$ ./debug.sh
+ (( i = 0 ))
+ (( i < 3 ))
+ echo 0
0
+ (( i++ ))
+ (( i < 3 ))
+ echo 1
1
+ (( i++ ))
+ (( i < 3 ))
+ echo 2
2
+ (( i++ ))
+ (( i < 3 ))

有時我們值需要 debug 指令碼的⼀部分。這種情況下,使⽤ set 命令會很⽅便。這個命令可以啟⽤或禁⽤選項。使⽤ - 啟⽤選項, + 禁⽤選項:

『示例原始碼』

# 開啟 debug
set -x
for (( i = 0; i < 3; i++ )); do
printf ${i}
done
# 關閉 debug
set +x
# Output:
# + (( i = 0 ))
# + (( i < 3 ))
# + printf 0
# 0+ (( i++ ))
# + (( i < 3 ))
# + printf 1
# 1+ (( i++ ))
# + (( i < 3 ))
# + printf 2
# 2+ (( i++ ))
# + (( i < 3 ))
# + set +x

for i in {1..5}; do printf ${i}; done
printf "\n"
# Output: 12345

11.1 Shell指令碼常用方法總結 √

#!/bin/bash
#Name: by yuan

#設定下載地址
URL='https://cdn.mysql.com//Downloads/MySQL-5.6/mysql-5.6.38.tar.gz'

#設定安裝⽬錄
PREFIX=/usr/local/$(basename $(echo " ${URL%.tar.gz}"))
# 擷取左邊字串為 https://cdn.mysql.com//Downloads/MySQL-5.6/mysql-5.6.38
# basename 命令後為 mysql-5.6.38

#設定資料⽬錄
DATADIR=/usr/local/$(basename ${URL%.tar.gz})/data

#提示⻛格
啟動: Starting sshd:
停⽌: Stopping sshd:
成功: SUCESS
失敗: FAILED

#主程式
PROG=/bin/cp

#程序號
PID="./$PROG.pid"

#家⽬錄
HOME=/home/mysql

#取得當前⽬錄的絕對路徑
$(dirname `readlink -f $0`)

經過試驗,這種⽅法最穩妥,因為它能追蹤軟連線到真實的⽬錄,就算⾃身被軟連線也不影響使⽤。

#檢測root許可權
[ $(id -u) != "0" ] && echo "Error: You must be root to run this script" && exit
# id -u 當前使用者的uid ,不為0則無許可權退出 

#檢測是否已經安裝過
[ -f $PREFIX/bin/mysqld_safe ] && echo "Error:MY-SQL Has been installed" && exit

#安裝EPEL源
if hostnamectl &>/dev/null; then
[[ -f /etc/yum.repos.d/epel.repo ]] || curl -o /etc/yum.repos.d/epel.repo
http://mirrors.aliyun.com/repo/epel-7.repo
else
[[ -f /etc/yum.repos.d/epel.repo ]] || curl -o /etc/yum.repos.d/epel.repo
http://mirrors.aliyun.com/repo/epel-6.repo
fi

#下載函式
#調⽤: download <tar.gz的⽂件連結>,調⽤結果: 1:下載並解壓⽂件到當前⽬錄, 2:得到⽂件名稱
#功能:檢測URL是否正確、本地⽂件存在不再重複下載,本地⽬錄存在不再解壓

function download() {
# 先判斷檔案是否存在(-f 是否是普通檔案)
if [[ -f ${1##*/} ]] ;then
# -d 是否是目錄
FILE=${1##*/} ; [[ -d ${FILE%.tar.gz} ]] || tar vxf ${1##*/}
else
[[ $1 != *.tar.gz ]] && echo 'URL Check Failed' && exit
curl -o ${1##*/} $1 && tar vxf ${1##*/}
fi
}

# 編譯函式
function Construct() {
...
}

# 下載原始碼並進⼊⽬錄
download $URL && cd ${FILE%.tar.gz} || exit 1
# exit 0 代表正常執行程式並退出程式
# exit 1 代表非正常執行導致退出程式

#編譯安裝
Construct && make && make install || exit 1

#顯示成功的樣式
function SUCESS() {
echo -e "$*\033[59G[\033[32;1m OK \033[0m]"
}

# 顯示失敗的樣式
function FAILED() {
echo -e "$*\033[59G[\033[1;31m FAILED \033[0m]"
}

#在當前⽤戶新增⼀個定時任務
#作⽤:新增⼀個定時任務
#優點:免互動,⽆需root許可權,防重複新增
NOTE='#logs clean'
TASK='00 02 * * * /bin/bash /app/yunwei/logs_clean.sh >/dev/null 2>/dev/null'
crontab -l | fgrep -q "${NOTE}" || echo -e "`crontab -l`\n${NOTE}" | crontab -
crontab -l | fgrep -q "${TASK}" || echo -e "`crontab -l`\n${TASK}" | crontab -

#啟動指令碼選單
case "$1" in
start)
starting ;;
stop)
stopping ;;
restart)
stopping && sleep 5 ; starting ;;
status)
statusing ;;
*)
echo "Usage: $0 {start|stop|restart|status}" ;;
esac

11.2 日誌封裝函式 √

#⽇志函式封裝

function wirelog() {
level=$1 # ⽇志級別
message=$2 # ⽇志資訊
timestamp=$(date +"%Y-%m-%d %T") # 時間戳

# 根據⽇志級別選擇輸出顏⾊
case $level in
"error")
color='\033[0;31m'
;;
"warning")
color='\033[0;33m'
;;
"info")
color='\033[0;36m'
;;
*)
color='\033[0m' # 預設顏⾊
;;
esac

# 格式化⽇志輸出
echo -e "$timestamp | $color$level$color | $message\033[0m"
}
echo \033[0;36m'"info" '\033[0;36m'
# 使⽤示例
wirelog 'info' 'This is an info message!'
wirelog 'warning' 'This is a warning message!'
wirelog 'error' 'This is an error'

11.3 獲取隨機字串或數字

⽅法 1:
# echo $RANDOM |md5sum |cut -c 1-8 #471b94f2
⽅法 2:
# openssl rand -base64 4 #vg3BEg==
⽅法 3:
# cat /proc/sys/kernel/random/uuid |cut -c 1-8 ed9e032c

獲取隨機 8 位數字

⽅法 1:
# echo $RANDOM |cksum |cut -c 1-8 #23648321
⽅法 2:
# openssl rand -base64 4 |cksum |cut -c 1-8 38571131
⽅法 3:
# date +%N |cut -c 1-8 69024815

十二、 Shell 指令碼編寫實戰 √

12.1 軟體安裝

nginx安裝指令碼

#!/bin/bash
#Description: nginx install script from yuan
#Release: 1.0
#Auther: yuan
#Email:
#OS: Centos 7

cat <<EOF
#Description: nginx install script
#!/bin/bash
#Description: nginx install script from yuan
#Release: 1.0
#Auther: yuan
#Email:
#OS: Centos 7
EOF
#Description: nginx install script

#nginx原始碼包下載路徑
nginx_pkg_url=http://nginx.org/download/nginx-1.17.8.tar.gz

#nginx安裝路徑,安裝路徑為$nginx_install_doc/nginx
nginx_install_doc=/usr/local

#nginx服務管理⽤戶
nginx_manage_user=www

#統計本機CPU核數
cpu_count=`grep -c "flags" /proc/cpuinfo`

check () {

#安裝nginx需要管理員許可權
[ $UID -ne 0 ] && echo "need to be root so that" && exit 1

#安裝前的依賴包解決
#wget 命令
#gcc 編譯命令
#pcre-devel URL重寫功能
#zlib-devel 壓縮⽀持
#make 編譯命令

if ! (yum -y install wget gcc pcre-devel zlib-devel make &>/dev/null);then
echo "yum install soft package fail"
exit 1
fi

if ! (egrep "^www" /etc/passwd &>/dev/null);then
useradd -s /sbin/nologin -r -M www
fi

}

nginx_install () {
#1、下載軟體包
if wget $nginx_pkg_url &>/dev/null;then
#2、解壓軟體包
echo $nginx_pkg_url|awk -F "/" '{print $5}'|xargs tar xf
nginx_source_doc=`echo $nginx_pkg_url|awk -F "/" '{print $5}'|cut -d "." -f 1-3`
#3、進⼊軟體包
if [ ! -d $nginx_source_doc ];then
echo "unzip `echo $nginx_pkg_url|awk -F "/" '{print $5}'` fail"
exit 1
fi

cd $nginx_source_doc

#4、 configure nginx
./configure --prefix=$nginx_install_doc/nginx --user=$nginx_manage_user --group=$nginx_manage_user 1>/dev/null
[ $? -ne 0 ]&&echo "nginx configure fail"&&exit 1

#5、 make nginx
make -j $cpu_count 1>/dev/null
[ $? -ne 0 ]&&echo "nginx make fail"&&exit 1

#6、 install nginx
make install 1>/dev/null
[ $? -ne 0 ]&&echo "nginx install fail"&&exit 1||echo "`clear`nginx install
success..."

#7、 delete nginx soft package
#cd ..
#rm -rf ${nginx_source_doc}*

else
echo "$nginx_pkg_url download fail"
exit 1
fi

}

#####callable function
check
nginx_install
# sh nginx_install.sh
#Description: nginx install script
#Release: 1.0
#Auther: yuan
#Email:
#OS: Centos 7
nginx install success...

12.2 檢查服務狀態

#!/bin/bash
#Description:
#Author: yuan
#Created Time:
#監控⼀個服務端⼝

#main

temp_file=`mktemp port_status.XXX`
# mktemp 建立臨時檔案的命令

#1、判斷依賴命令telnet是否存在
[ ! -x /usr/bin/telnet ]&& echo "telnet: not found command"&& exit 1

#2、測試端⼝ $1 IP $2 port
( telnet $1 $2 <<EOF
quit
EOF
) &>$temp_file

#3、分析⽂件中的內容,判斷結果
if egrep "\^]" $temp_file &>/dev/null;then

#4、列印結果
echo "$1 $2 is open"
else
echo "$1 $2 is close"
fi

#5、刪除臨時⽂件
rm -f $temp_file
# sh check_service.sh 127.0.0.1 5522
127.0.0.1 5522 is open

[root@localhost shellfile]# sh check_service.sh www.baidu.com 80
www.baidu.com 80 is open

()是Shell中的⼦shell語法。⼦shell是⼀個獨⽴的Shell環境,可以在當前Shell程序中運⾏,並且可以在其中執
⾏命令和操作,⽽不會影響⽗Shell程序中的環境和變數。在這個例⼦中, ()中的命令將在⼦shell中運⾏,並
且這個⼦shell的輸出將被重定向到指定的⽂件中。

監控⽅法:

  1. 透過systemctl status service 服務啟動狀態
  2. lsof 檢視端⼝是否存在
  3. 檢視程序是否存在
  4. 測試端⼝是否有響應 推薦telnet 協議

12.3 檢查主機存活狀態

指令碼思路

1、透過ICMP協議的ping命令ping⽬標主機

⽹絡延遲,假報警如何解決?
3次

2、分析ping結果

3、給出結論

全部ping結果為假,報當機
全部ping成功,報正常
否則報警告
#!/bin/bash
#
#Author: yuan
#Created Time:
#Release:
#Description:

#1、 ping ⽬標主機三次,並接收每次的狀態值,ping成功返回1,不成功返回0
for ((i=1;i<4;i++));do
#測試程式碼
if ping -c1 $1 &>/dev/null;then
#分析結果
export ping_count"$i"=1
else
export ping_count"$i"=0
fi
#時間間隔
sleep 0.3
done

#3、分析結果
# 3次ping失敗報警
#
if [ $ping_count1 -eq $ping_count2 ] && [ $ping_count2 -eq $ping_count3 ] && [ $ping_count1 -eq 0 ];then
echo "$1 當機"
elif [ $ping_count1 -eq $ping_count2 ] && [ $ping_count2 -eq $ping_count3 ] && [ $ping_count1 -eq 1 ];then
echo "$1 正常"
else
echo "warn: $1 ⽹絡延遲"
fi

#4、釋放變數
unset ping_count1
unset ping_count2
unset ping_count3
[root@localhost shellfile]# sh ping.sh www.baidu.com
www.baidu.com 正常

12.4 監控 CPU、記憶體和硬碟利用率

cpu使⽤率⼤於80告警

#!/bin/bash

# 獲取當前的 CPU 使⽤率,並以整數形式輸出
usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}' | cut -d'.' -f1)
# 批處理顯示一次更新,列印"Cpu(s)"這行 使用者態+核心態 的變數,保留整數

# 判斷 CPU 使⽤率是否⼤於 80%
if [ $usage -gt 80 ]; then
# 傳送告警資訊
echo "CPU usage is over 80%!"
# 在這⾥可以新增傳送告警資訊的程式碼
else
echo "CPU usage is normal."
fi
# sh cpu_usage.sh
CPU usage is normal. value:4

記憶體使⽤率監控

#!/bin/bash
#
#Author: yuan
#Created Time:
#Release:
#Description:記憶體使⽤率計算指令碼

#1、透過free命令結合資料處理獲得對應資料
#1.1、獲得記憶體總量
memory_total=`free -m|grep -i "mem"|tr -s " "|cut -d " " -f2`
# tr -s 把連續重複的字元以單獨一個字元表示
swap_total=`free -m|grep -i "swap"|tr -s " "|cut -d " " -f2`
#1.2、獲得記憶體使⽤的量
memory_use=`free -m|grep -i "mem"|tr -s " "|cut -d " " -f3`
swap_use=`free -m|grep -i "swap"|tr -s " "|cut -d " " -f3`
#1.3、 buff/cache
buff_cache=`free -m|grep -i "mem"|tr -s " "|cut -d " " -f6`

#2、計算輸出
#運算的時候是否需要⼩數點 浮點運算,要考慮使⽤的命令 (難點 重點)
#echo "記憶體使⽤率: $((memory_use*100/memory_totle))%"
#難點:浮點運算中,同優先順序的情況下,⼤數除以⼩數 儘可能保證精確
echo "記憶體使⽤率: `echo "$memory_use*100/$memory_total"|bc`%,buff&cache:$buff_cache MB"
echo "Swap使⽤率: `echo "$swap_use*100/$swap_total"|bc`%"
# bc算數使用工具
# sh mem_usage.sh
記憶體使⽤率: 76%,buff&cache:3144 MB
Swap使⽤率: 26%

磁碟監控

#!/bin/bash
#
#Author: yuan
#Created Time:
#Release:
#Description:

#指定for條件的分割符為回⻋,⼀⾏⼀個條件
IFS=$'\n'

#1、遍歷符合條件的每⼀⾏磁碟資料
for i in `df -Th|egrep -v "(tmpfs|sr0)"|tail -n +2|tr -s " "`;do
size=`echo $i|cut -d " " -f6|tr -d "%"`
name=`echo $i|cut -d " " -f1`

#2、判斷每⾏中的磁碟使⽤率並輸出結果
if [ $size -ge 95 ];then
#3、輸出⽇志並關機,不許在寫⼊資料
logger "ERROR:$name use is $size.halt"
#halt -p
elif [ $size -ge 90 ];then
echo -e "\033[31m $name use is ${size}%\033[0m"
elif [ $size -ge 80 ];then

echo -e "\033[33m $name use is ${size}%\033[0m"
else
echo -e "\033[32m$name use is ${size}%\033[0m"
fi
done
# sh disk_usage.sh
/dev/mapper/vg0-root use is 40%
/dev/mapper/vg0-usr use is 43%
/dev/sda1 use is 38%
/dev/mapper/vg0-opt use is 4%
/dev/mapper/vg0-app use is 64%
/dev/mapper/vg0-var use is 61%
overlay use is 64%
overlay use is 64%
overlay use is 64%
overlay use is 64%
overlay use is 64%

12.5 監控介面

監控服務的接⼝是否正常

#!/bin/bash
#
#Author: yuan
#Created Time:
#Release:
#Description: URL監控指令碼
#監控閾值可以是: 狀態碼、⻚⾯字串、⻚⾯所有內容
#本例以狀態碼為例

#variables
init_url_status=200
temp_file=`mktemp /tmp/check_url.XXX`

#help
if [ -z "$1" ]||[ "$1" == "--help" ];then
echo "$0 url"
echo "--help: 列印該幫助"
fi

#如果⽤戶沒有傳參則退出
[ $# -lt 1 ]&&exit 1
#main

#1、使⽤curl命令訪問⼀次URL
#1.1 判斷指令碼依賴命令是否存在
[ ! -x /usr/bin/curl ]&&echo "curl: not found command"&&exit 1

#1.2 訪問⼀次URL
curl -I $1 &> $temp_file

#2、從輸出中擷取狀態碼
url_status=`grep "HTTP/1.1" $temp_file|cut -d " " -f2`

#2.1如果取值失敗直接報錯(測試發現當⽆法訪問URL時會在第三步中報⽐較錯誤,所以這⾥取不到值就不往下⾛了)
[ -z "$url_status" ]&&echo -e "\033[31mstatus:NULL\033[0m"&&exit 1

#3、判斷狀態碼是否和預設的⼀致
#3.1 ⼀致 輸出綠⾊字型 "status:200"
#3.2 不⼀致 輸出紅⾊字型 "status:XXX"

if [ $init_url_status -eq $url_status ];then
echo -e "\033[32mstatus:$url_status\033[0m"
else
echo -e "\033[31mstatus:$url_status\033[0m"
fi

#4、刪除臨時⽂件
rm -f $temp_file
[root@localhost shellfile]# sh check.sh www.baidu.com
status:200

12.6 應用程式的啟動指令碼

go程式啟動指令碼

#!/bin/bash
#程式名稱
PROG=goweb
PID=${PROG:+$PROG.pid}

#⽬錄名改變,指令碼被連結,仍然可以準確進⼊指令碼所在⽬錄
cd $(dirname $(readlink -f $0))

sucess() {
echo -e "$*\033[59G[\033[1;32m OK \033[0m]"
}
failed() {
echo -e "$*\033[59G[\033[1;31m FAILED \033[0m]"
}

#函式成功: PID不存在 || 程序不存在
#函式失敗: PID⽂件不可寫 || 程序已經存在
startup() { #
#函式成功:程序存在
#函式失敗:程序不存在
start_process() {
nohup ./${PROG} & echo $! >${PID}
if sleep 2 ; kill -0 `<${PID}` 2>/dev/null ;then
sucess "Starting ${PROG}:"
else
failed "Starting ${PROG}:" ; exit 1
fi
}
if [[ -f ${PID} ]] ;then
if [[ -w ${PID} ]] ;then
if [[ -L /proc/`<${PID}`/exe ]] 2>/dev/null ;then
echo "${PROG} Already Running"
else
start_process
fi
else
echo 'PID File: Permission denied' ; exit 1
fi
else
start_process
fi
}

#函式成功: PID⽂件可寫 && 程序存在 && 程序屬於⾃⼰
#函式失敗: PID⽂件不存在 || 程序不存在 || 程序不屬於⾃⼰
shutdown() {
#函式成功:程序不存在
#函式失敗:程序依然存在
stop_process() {
kill -15 `cat ${PID} 2>/dev/null`
if sleep 2 ; kill -0 `cat ${PID} 2>/dev/null` 2>/dev/null ;then
failed "Stopping ${PROG}:" ; exit 1
else
sucess "Stopping ${PROG}:"
rm -f ${PID}
fi
}
if [[ -f ${PID} ]] ;then
if [[ -w ${PID} ]] ;then
if [[ -L /proc/`<${PID}`/exe ]] 2>/dev/null ;then
if [[ -w /proc/`<${PID}`/mem ]] ;then
stop_process
else
echo "Process: Permission denied"
fi
else
echo 'Process: No such Process' ; rm -f ${PID}
fi
else
echo 'PID File: Permission denied' ; exit 1
fi
else
echo 'PID File: No such PID file'
fi
}

status() {
ps aux | awk '$11~'/${PROG}/
#netstat -tnlp 2>/dev/null | grep ${PROG} | column -t
if [[ -f ${PID} ]] ;then
if [[ -w ${PID} ]] ;then
if [[ -L /proc/`<${PID}`/exe ]] 2>/dev/null ;then
if [[ -w /proc/`<${PID}`/mem ]] ;then
#stop_process
netstat -nlp 2>/dev/null | grep `cat ${PID} 2>/dev/null` | column -t
else
echo "Process: Permission denied"
fi
else
echo 'Process: No such Process' ; rm -f ${PID}
fi
else
echo 'PID File: Permission denied' ; exit 1
fi
else
echo 'PID File: No such PID file'
fi
}

case "$1" in
start)
startup ;;
stop)
shutdown ;;
restart)
shutdown && sleep 5 && startup ;;
status)
status ;;
*)
echo "Usage: $0 {start|stop|restart|status}" ;;
esac

java啟動指令碼

#!/bin/bash
#這⾥可替換為你⾃⼰的執⾏程式,其他程式碼⽆需更改
APP_NAME=hello-0.0.1-SNAPSHOT.jar

#APP_DIR=./
APP_DIR=`pwd`
echo "當前路徑"
echo $APP_DIR

#使⽤說明,⽤來提示輸⼊引數
usage() {
echo "Usage: sh console.sh [start|stop|restart|status]"
exit 1
} 
#檢查程式是否在運⾏
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `

echo "ps number is: ${pid}"
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
} 

#啟動⽅法
start(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
nohup java -jar $APP_DIR/$APP_NAME > $APP_DIR/log.out 2>&1 &
#nohup java -jar $APP_DIR/$APP_NAME
echo "${APP_NAME} start success"
fi
}

#
停⽌⽅法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}

#輸出運⾏狀態
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}

#重啟
restart(){
stop
start
}

#根據輸⼊引數,選擇執⾏對應⽅法,不輸⼊則執⾏使⽤說明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac

#指令碼編寫完成後,可以使⽤命令sh xxx.sh執⾏,也可以使⽤./xxx.sh執⾏。
#sh xxx.sh與./xxx.sh區別
#sh xxx.sh 是不需要有執⾏許可權
#./xxx.sh 是需要有執⾏許可權的,可以透過 chmod +x xxx.sh 賦予許可權。
# sh start.sh start
# 當前路徑
/root/shell/java-hello
ps number is:
hello-0.0.1-SNAPSHOT.jar start success

12.7 mysql備份指令碼

每天全量備份,邏輯備份,熱備份。已用場景:資料量小於20G

!/bin/bash

# 定義備份⽂件名
DATE=`date +%Y%m%d%H%M%S`
FILENAME="backup_$DATE.sql"

# 定義MySQL連線引數
MYSQL_HOST="localhost"
MYSQL_PORT="3306"
MYSQL_USER="root"
MYSQL_PASSWORD="password"
MYSQL_DATABASE="mydatabase"

# 執⾏備份命令
mysqldump -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE > $FILENAME

# 列印備份結果
if [ $? -eq 0 ]; then
echo "Database backup successful! Backup file: $FILENAME"
else
echo "Database backup failed!"
fi

相關文章