1. 程式人生 > 實用技巧 >linux-bash程式設計

linux-bash程式設計

目錄

1. 基本語法

1.1 註釋

註釋以"#"開始, "#"後面的內容被忽略.

說明:

  1. "#" 前可以沒有語句, 表示整行被註釋.
  2. "#" 前可以有語句, 只註釋"#"後面的內容.

1.2 變數

VAR_NAME='value'      # 變數賦值, 注意等號兩邊不能有空格
echo "I am $VAR_NAME, ${VAR_NAME}" # 通過$符使用變數

LIST=$(ls)               # 將shell命令結果賦值給變數,
SERVER_NAME=$(hastname)  # 參考子命令擴充套件

全域性變數:在指令碼中任何位置都可以使用該變數,shell變數預設都是全域性變數。
本地變數:可以在函式內部宣告本地變數,使用local關鍵字。

1.3 陣列

陣列直接賦值, 注意等號兩邊不能有空格.

$ myarr=()                        # 定義一個空陣列  
$ myarr=(1 2 3 4 5)               # 陣列元素都為數字  
$ myarr=(one two three four five) # 陣列元素都為字串  
$ myarr=(1 two 3 four 5)          # 陣列元素由數字和字串組成  
$ myarr=(1 two 3 "a line")        # 陣列元素包含空格  

通過陣列下標賦值

$ myarr[0]=1         # 通過下標為陣列元素賦值  
$ myarr[2]=test  
$ myarr[9]="a line"  # 下標超出原陣列長度也可以直接賦值  

獲取陣列資訊

$ myarr=(one two three four five) # 陣列元素都為字串  
$ 
$ echo ${myarr[0]}          # 通過下標訪問元素  
$ echo ${myarr[@]}          # 列印整個陣列  
one two three four five  
  
$ echo ${#myarr[*]}         # 獲取陣列長度  
$ echo ${#myarr[@]}         # 同上  
  
$ echo ${#myarr[9]}         # 獲取元素的長度  
  
$ echo ${myarr[@]:3:2}      # 陣列切片, 從第3個元素開始, 元素個數為2  
four five  

$ myarr+=(six seven)        # 給陣列追加新陣列  
$ echo ${myarr[@]}  
one two three four five six seven  

$ unset ${myarr[9]}         # 刪除陣列某個元素  
$ unset myarr               # 刪除整個陣列  

通過變數做下標:

$ myarr=(one two three four five)  
$ i=0  
$ echo ${myarr[i]}    # 通過變數做下標  
one  
$ echo ${myarr[i+1]}  # 通過變數運算做下標  
two  

遍歷陣列: 通過in關鍵字, ${myarr[@]}加雙引號, 元素中有空格不會被拆分. 如果不加雙引號, 最後一個元素中有空格, 會被拆分.

$ myarr=(one two three four "a line")
$ for ele in "${myarr[@]}"; do echo $ele ; done
one
two
three
four
a line
$

遍歷陣列: 通過下標自增

$ myarr=(one two three four "a line")
$ for ((i=0;i<${#myarr[*]};i++)); do echo ${myarr[i]}; done
one
two
three
four
a line
$

遍歷陣列: 通過下標自增2

$ for ((i=0;i<${#myarr[*]};i+=2)); do echo "${myarr[i]}, ${myarr[i+1]}"; done  
one, two  
three, four  
a line,  
$  

1.4 命令列引數

$0 # 指令碼名稱
$1 # param1
$2 # param2
...
$@ # 整個引數陣列,$1 $2 ...,不包括$0。

1.5 獲取使用者輸入

read命令接收鍵盤的輸入

$ read -p "Please Enter Your Name: " NAME
Please Enter Your Name: ZhangSan
$ echo "Your Name Is: $NAME"
Your Name Is: ZhangSan

1.6 檔案測試

注意,方括號與中間內容必須有空格。

[ -d FILE_NAME ] # FILE_NAME是dir
[ -e FILE_NAME ] # FILE_NAME存在
[ -f FILE_NAME ] # FILE_NAME存在且是regular file
[ -r FILE_NAME ] # FILE_NAME是readable
[ -s FILE_NAME ] # FILE_NAME存在且不為空
[ -w FILE_NAME ] # FILE_NAME有write permission
[ -x FILE_NAME ] # FILE_NAME是excutable

1.7 字串測試

注意,方括號與中間內容必須有空格。

[ -z STRING ]         # STRING是空的
[ -n STRING ]         # STRING不是空的
[ STRING1 = STRING2 ] # 字串相等
[ STRING1 != STRING2 ]# 字串不相等

1.8 算術測試

注意,方括號與中間內容必須有空格。

[ var1 -eq var2 ] # 相等
[ var1 -ne var2 ] # 不相等
[ var1 -lt var2 ] # var1 less than var2
[ var1 -le var2 ] # var1 less than or equal to var2
[ var1 -gt var2 ] # var1 greater than var2
[ var1 -ge var2 ] # var1 greater than or equal to var2

1.9 if條件判斷

語法1: 只有一個if分支

if [ condition-is-true ]
then # 注意,then不能直接寫在if語句行, 除非在if語句行尾加分號後再使用then
    command 1
    …
    command N
fi

語法2: if帶elif分支

if [ condition-is-true ]; then
     command 1
elif [ condition-is-true ]; then
     command 2
elif [ condition-is-true ]; then
    command 3
else
    command 4
fi

1.10 case條件判斷

語法:

case "$VAR" in
  pattern_1)
    # commands when $VAR matches pattern 1
    ;;
  pattern_2)
    # commands when $VAR matches pattern 2
    ;;
  *)
    # This will run if $VAR doesnt match any of the given patterns
    ;;
esac

例子:

read -p “Enter the answer in Y/N: ” ANSWER
case “$ANSWER” in
    [yY] | [yY][eE][sS])
        echo “the answer is Yes.”
        ;;
    [nN] | [nNY][oO])
        echo “the answer is No.”
        ;;
    *)
        echo “invalid answer .”
        ;;
esac

1.11 for迴圈

語法1:

for VARIABLE_NAME in ITEM_1 ITEM_N
do
  command 1
  command 2
    ...
    ...
  command N
done

例:

$ for V in a b c; do echo $V; done
a
b
c
$ 
$ COLORS="red green blue"
$ for C in $COLORS; do echo $C ; done
red
green
blue
$
FIELS=$(ls *txt)
NEW="new"
for FILE in $FILES
do
    echo "Renaming $FILE to $NEW-$FILE"
    mv $FILE $NEW-$FILE
done

語法2:

for (( VAR=1;VAR<N;VAR++ )) # 注意, 有兩對小括號.
do
  command 1
  command 2
    ...
    ...
  command N
done

$ for ((I=0;I<5;I++)); do echo $I; done
0
1
2
3
4

1.12 while迴圈

語法:

while [ condition-is-true ]
do
    command 1
    command 2
    …
    command N
done

例: 按行讀取檔案/ect/passwd的內容

while read CURRENT_LINE; do       # CURRENT_LINE是內建變數, 當前行的內容.
    echo "${LINE}: $CURRENT_LINE"
    ((LINE++))                    # 變數自增, 見模式擴充套件
done < /etc/passwd

1.13 迴圈控制

continue # 結束本次迴圈節,進入下一個迴圈節
break    # 結束整個迴圈

1.14 邏輯運算 &&, ||

邏輯與: &&
邏輯或: ||

例子:建立目錄tempDir成功後,進入該目錄並建立子目錄subTempDir

mkdir tempDir && cd tempDir && mkdir subTempDir

1.15 函式

語法:

function func_name() {
    command 1
    command 2
    ...
    command N
}

例子:宣告一個函式並呼叫

#!/bin/bash
function myFunc() {
    echo "Shell Scripting Is Fun!"
}

myFunc # 函式呼叫

函式引數

$1 第一個引數
$2 第二個引數
$@ 引數陣列

引數之間使用空格分隔

#!/bin/bash
function add() {
    let "SUM=$1+$2"
    return $SUM
}

A=$(add 3 4)
echo "3+4=${A}"

2. 萬用字元 or 模式擴充套件

共有8種模式擴充套件,按優先順序順序分為:

序號 名稱 說明
1 brace expansion 大括號 {}
2 tilde expansion 波浪號 ~
3 parameter and variable expansion 引數? 變數?
4 arithmetic expansion 算術擴充套件
5 command substitution 子命令擴充套件
6 word splitting 詞分割
7 pathname/filename expansion 路徑名擴充套件
8 process substitution 過程替換

2.1 brace expansion {}

大括號擴充套件。

有兩種形式:

  1. pre + {str1,str2[,…,strN]} + post
  2. pre + {START..END[..INCR]} + post # 擴充套件成一個序列

使用注意事項:

  1. 這是單純對字串的擴充套件,即使不存在對應的檔名,也會擴充套件成功。
  2. 逗號兩邊都不允許有空格。
  3. 大括號與中間內容之間不允許在空格。
  4. 逗號前可以為空,表示待擴充套件的為空字元,見後面例子。
  5. 大括號可以巢狀使用,見後面例子。
  6. 大括號可以與其它擴充套件聯用,見後面例子。
  7. START..END用於擴充套件為一個序列,一般用於迴圈。
  8. START..END支援逆序。
  9. START..END支援新增前導字元。
  10. START..END支援指定步長: START..END..INCR。
  11. START..END可以級聯使用,類似於多重迴圈。

例子 {str1,str2[,…,strN]}:

$ echo g{o,oo,ra}d
god good grad

$ cp a.log{,.bak}     # 逗號前為空,擴充套件為
cp a.log a.log.bak

$ echo a{1, 2}b       # 逗號兩邊有空格,不作為擴充套件處理,當作以空格分隔的兩個str。
a{1, 2}b

$ echo {j{p,pe}, pn}g # 巢狀使用大括號
jpg jpeg png

$ echo {cat, d*}      # 大括號與其它擴充套件聯用
cat dog door          # 注意,d*是檔名擴充套件,只有存在以d開頭的檔名時才會擴充套件成功,
                      #      否則,會直接將d*當作變通str而不進行擴充套件。

例子 {START..END[..INCR]}:

$ echo d{a..c}g       # 普通範圍擴充套件
dag dbg dcg

$ echo Num{5..1}      # 逆序擴充套件
Num5 Num4 Num3 Num2 Num1

$ echo {08..12}       # 新增前導0,將所有結果擴充套件成等寬的字串
08 09 10 11 12

$ echo {08..17..2}    # 對數字序列指定步長
08 10 12 14 16

$ echo {a..g..2}      # 對字母序列也可以指定步長
a c e g

$ echo {x..z}{0..2}
x0 x1 x2 y0 y1 y2 z0 z1 z2

2.2 tilde expansion ~

波浪號擴充套件。
主要形式如下:

待擴充套件字元 擴充套件後字元 說明
~ /home/whoami 當前使用者的主目錄
~username /home/username 特定使用者的主目錄
~+ pwd 當前路徑, 相當於pwd
~root /root /root目錄

注意:

  1. ~到/之間的字元都稱為tilde-prefix。
  2. 波浪號用於匹配目錄,對於~username,如果username不存在該使用者,則擴充套件不成功。按原樣輸出字元。

2.3. parameter and variable expansion

引數和變數擴充套件。

兩個符號

符號 說明
$ 可用於引數和變數擴充套件,也可以用於命令替換和算術擴充套件。它是引數、變數、命令、算術擴充套件的“引導符”
{} 在引數(引數小於10時)和變數擴充套件中,{}不是必須的,但它可以起到保護變數的作用,當變數名後跟著其它字元時,用於區分變數名與字元。

注意:

  1. 使用!可以將變數的值又當作變數,多做一次擴充套件。
  2. 可以使用${VAR:=defaultvalue}的方式指定預設值

例子:普通變數擴充套件

$ NAME=LINUX
$ echo pre${NAME}post # 變數擴充套件,將${NAME}替換為LINUX。
preLINUXpost
$ 
$ echo pre$NAMEpost   # 沒使用{},認為$NAMEpost是一個變數,為空,所以擴充套件為pre。
pre

例子:使用!

$ NAME=LINUX
$ LINUX=Ubuntu
$ echo pre${NAME}post
preLINUXpost
$ echo pre${!NAME}post # 加一個!,將NAME的值LINUX又當作變數名,擴充套件為Ubuntu。
preUbuntupost

例子:自定義變數預設值

$ NAME=      # 清空NAME的值,由於bash未定義的變數值都是空,所以直接賦值為空。
$ echo pre{NAME:=WINDOWS}post # NAME未定義,NAME賦值為預設值WINDOWS。
preWINDOWSpost

$ echo $NAME # 在上一語句中,NAME已經被賦值為指定的預設值
WINDOWS

2.4 arithmetic expansion

算術擴充套件。
語法: $((EXPRESSION))

注意:

  1. (())可以進行算術運算。
  2. (())可以將非10進位制數轉為10進位制數。
  3. (())中可以有空格,也可以沒空格。
  4. (())中可以使用變數。

例子:

$ echo $(( 3 + 4 ))
7

$ A=3
$ echo $(($A+4))
7

$ A=$((011)); echo $A # 以0開頭的是8進位制數
9

$ A=$((0x11)); echo $A # 以0x開頭的是16進位制數
17

2.5. command substitution

命令替換。
語法:$(command) 或 command
作用:將command的輸出作為當前命令的一部分。

注意:

  1. \((command) 使用小括號,是命令替換,\){VAR}使用大括號是變數擴充套件。$((expr))是算術擴充套件。
  2. $(command) 將command的輸出作為當前命令,而不只是執行command
$ date
2020年 08月 12日 星期三 11:06:11 CST

$ $(date) # 本條命令相當於在命令列執行“2020年 08月 12日 星期三 11:06:11 CST”。
2020年: command not found

2.6 word splitting

詞分離。

如果引數擴充套件、命令替換、算術擴充套件沒有在雙引號中,shell將對其結果進行“詞分離”操作。

shell把\(IFS中的每一個字元看做是分隔符。 預設情況下\)IFS的值是“
可以修改$IFS的值。

$ IFS=’123’     # 1,2,3都作為詞分隔符
$ echo a1b2c3d4 # 在命令列中,不進行“word splitting”操作
a1b2c3d4

$ echo $(echo a1b2c3d4) # 將echo a1b2c3d4放到命令替換語句中
a b c d4                # 以1,2,3將a1b2c3d4分割為a b c d4共4個份
$ ls $(echo a1b2c3d4)   # 將echo a1b2c3d4放到命令替換語句中
ls: canot access ‘a’: No such file or directory # a b c d4放到ls命令後面
ls: canot access ‘b’: No such file or directory # 相當於執行 ls a b c d4
ls: canot access ‘c’: No such file or directory # 
ls: canot access ‘d4’: No such file or directory# 

2.7 filename expansion

檔名擴充套件.

全為3種

副檔名 說明
* 匹配0~N個任意字元, 不能匹配空格 ls .txt, ls .
? 匹配1個任意字元,不能匹配空格 ls ?.txt
[abc] 匹配a、b、c中任意一個字元 ls [ab].txt
[^abc]或[!abd] 匹配a、b、c以外的任意一個字元 ls [!ab].txt
[start-end] 匹配連續範圍內的任意一個字元 ls [a-c].txt
[^start-end] 匹配連續範圍外的任意一個字元 ls [^1-3].txt

注意:

  1. 只有對應檔名存在時才能擴充套件成功,否則原樣輸出。比如 ls *.txt如果路徑下沒有.txt檔案,則直接將 .txt作為ls的引數,會報告沒有“.txt”這個檔案。
  2. *和?不能匹配空格。

例子:

$ ls *          # 所有檔案,但不包含隱藏檔案
$ ls .*         # 所有隱藏檔案,但包含了’.’和’..’這兩個目錄
$ ls .[!.]*     # 所有隱藏檔案,不包括’.’和’..’這兩個目錄
$ ls [a-c].txt  # a.txt b.txt c.txt,只擴充套件為已經存在的檔案。
                #    比如如果b.txt不存在,則只擴充套件為a.txt c.txt

2.8. process substitution

過程替換。

過程替換與重定向比較容易搞混:重定向是一口入一口出,過程替換是多入(實際是並沒有“入”)。看如下試驗:

$ ls <(true) # 注意,”<”和”(“之間不能有空格,否則認為是重定向,語法錯誤。
/dev/fd/63   # 可以認為<(true)就是檔案/dev/fd/63

$ vim <(ls)  # 通過vim視窗底部顯示,確認檔名為/dev/fd/63.
1 a.txt
2 c.txt
3 ex.sh

“/dev/fd/63” [readonly] 3L, 18C

$ ls <(true) >(true) # 可以認為<(LIST) 和 >(LIST)分別都是一個檔名。
/dev/fd/62 /dev/fd/63

if the “>(LIST)” form is used, writing to the file will provide input for LIST.
if the “<(LIST)” form is used, the file passed as an argument should be read to obtain the output of LIST

舉例說明:

$ cat <(ls) # <(ls)是一個檔案,檔案內容是ls的結果,cat這個檔案。
$ ls > >(cat) # >(cat)是一個檔案,檔案的內容是ls重定向來的,cat這個檔案。

例子:將兩個檔案分別sort並排序,然後找出在兩個檔案中都出現的行。

$ cat a.txt
d3
d4
a0
a1
a2
$ cat b.txt
c0
c1
c2
d3
d4

$ comm <(sort -u a.txt) <(sort -u b.txt) # 給comm傳兩個檔案,檔案內容是sort -u結果。
a0         #---第一列,僅出現在第一個檔案中的行
a1
a2
    c0     #---第二列,僅出現在第二個檔案中的行
    c1
    c2
        d3 #---第三列,在兩個檔案中都出現的行
        d4