shell腳本學習總結及坑點記錄


shell腳本是一種爲 shell 編寫的腳本程序, 一般文件後綴爲 .sh
shell 的解釋器種類衆多:

  • sh: 即 Bourne Shell, sh 是 Unix 標準默認的 shell
  • bash: 即 Bourne Again Shell, bash 是 linux 標準默認的 shell

shell 腳本中開頭第一句的#!告訴系統其後路徑所指的程序即是解釋此腳本文件的
shell 解釋器

shell 變量

shell 變量命名:

  1. 只能使用英文字母, 數字和下劃線, 首個字符不能以數字開頭
  2. 中間不能有空格可以使用下劃線
  3. 不能使用標點符號
  4. 不能使用 bash 裏的關鍵字

局部變量和全部變量

shell 腳本中的變量默認都是全局的, 即其作用於從聲明定義處一直到程序結束,如:

#!/bin/bash

func() 
{
    i="hello world"  #變量定義是=左右不能有空格
}

func
echo ${i}

輸出爲:

hello world

想要聲明局部變量使用local關鍵字

預定義變量

  1. $0~9, $0 是腳本文件名, $1~9 是命令行參數
  2. $# 命令行參數個數
  3. $@ 所有命令行參數
  4. $$ 執行的進行id
  5. $? 上個命令的退出狀態, 或函數的返回值

變量默認值

value=${somevalues:othervalues}
#如果somevalues被定義了,則value=somevalues,否則values=othervalues

環境變量

  1. HOME 用戶主目錄
  2. PATH: 系統環境變量 PATH
  3. TERM: 當前終端
  4. PWD: 當前工作目錄, 絕對路徑

shell 常用關鍵字

  1. echo
  2. exec: 執行另一個 shell 腳本
  3. read: 讀標準輸入
  4. expr: 對整數型變量進行算數運算
  5. test: 用於測試變量是否相等, 是否爲空, 文件類型等
  6. exit: 退出
  7. who: 顯示當前登錄系統用戶名
  8. tee: 讀取標準輸入文件, 並將其內容輸出爲文件

let命令

linux let命令是 bash 中用於計算的工具, 用於執行一個或多個表達式, 變量計算中不需要加上 $ 來表示變量, 如果表達式中包含了空格或其他特殊字符, 則必須引起來
如:

i=0
let i++

getopts獲取參數+case語句解析

#!/bin/bash

func() 
{
    while getopts "a:b:" opt
    do 
        case ${opt} in
            a) echo ${OPTARG}
               ;;
            b) echo ${OPTARG}
                ;;
            ?) echo "wrong parameter"
        esac
    done
}

main() 
{
    func ${@}
}

main ${@}
  1. 當有不認識的選項時爲 ?)
  2. ;; 相當於 break, 必須加
  3. esac就是 case 反回來
  4. 每個參數後跟一個冒號表示其需要一個參數
  5. 可以在參數前加一個冒號,如:while getopts ":a:b:" opt,這個:表示getopts不返回錯誤
  6. OPTARG變量保存當前參數,OPTINDargv的當前索引值

解析命令行參數工具

getoptgetopts用於 shell 腳本中分析腳本參數

  1. geopts 是 shell 內置命令, getopt 是一個獨立外部工具
  2. getopts 使用語法簡單, getopt 使用語法較複雜
  3. getopts 不支持長參數(如: --option), getopt 支持
  4. getopts 不會重拍所有參數順序, getopt 會重拍參數順序
  5. getopts 出現的目的是爲了替代 getopt 較快捷的執行參數分析工作

getopt 所設置的全局變量:

  1. optarg 指向當前選項參數(如果有)的指針
  2. optind 再次調用 getopt() 時的下一個 argv 指針的索引
  3. optopt 最後一個位置選項

調用main函數

定義完main函數, 使用 main "$@"進行對main的調用

獲得執行腳本的當前絕對路徑

basepath=$(cd "$(dirname $0)"; pwd)

cut命令

cut 命令用來從文件或者標準輸入中讀內容並截取特定部分送到標準輸出

  1. -b : 輸入每行的第 n 個字符(有中文就亂碼)
  2. -c : 輸入每行的第 n 個字符(適用中文)
  3. -d: 自定義分隔符
  4. -f : 與 -d 一起使用, 指定顯示哪個區域
  5. -n : 取消分割多字節字符(例如中文), 僅與 -b 一起使用

例如:

$ echo long ago test befor | cut -f 2,3 -d ' '
ago test

test 命令

用於檢查某個條件是否成立, 可以進行數值, 字符串和文件三個方面的比較

[] 是test命令的一種形式

數值測試

  1. eq (equal的縮寫),表示等於爲真

  2. ne (not equal的縮寫),表示不等於爲真

  3. gt (greater than的縮寫),表示大於爲真

  4. ge (greater&equal的縮寫),表示大於等於爲真

  5. lt (lower than的縮寫),表示小於爲真

  6. le (lower&equal的縮寫),表示小於等於爲真

num1=100
num2=100
if test $[num1] -eq $[num2]
then
    echo '兩個數相等!'
else
    echo '兩個數不相等!'
fi

# 可以寫爲 if [ ${num1} -eq ${num2} ]

字符串測試

  1. = 等於則爲真
  2. != 不相等則爲真
  3. -z 字符串 字符串的長度爲零則爲真
  4. -n 字符串 字符串的長度不爲零則爲真

文件測試

  1. -e 文件名 如果文件存在則爲真
  2. -r 文件名 如果文件存在且可讀則爲真
  3. -w 文件名 如果文件存在且可寫則爲真
  4. -x 文件名 如果文件存在且可執行則爲真
  5. -s 文件名 如果文件存在且至少有一個字符則爲真
  6. -d 文件名 如果文件存在且爲目錄則爲真
  7. -f 文件名 如果文件存在且爲普通文件則爲真
  8. -c 文件名 如果文件存在且爲字符型特殊文件則爲真
  9. -b 文件名 如果文件存在且爲塊特殊文件則爲真

例:

cd /bin
if test -e ./bash
then
    echo '文件已存在!'
else
    echo '文件不存在!'
fi

(),(()),[],[[]],{}

  • ()

展開一個新的shell子程序, 所以括號中的變量爲局部變量

a="123"
(a="456";echo "a=$a")
echo "a=$a

打印

456
123
  • (())
    只支持整數運算,浮點數shell當做字符串處理
  1. 進行運算擴展
    例:
a=$((4+5)) 
echo "a=$a"

$[]作用相同

  1. 做數值運算
a=5
((a++))
echo "a=$a"
  1. 做算數比較
if ((1+1>1));then
  echo "1+1>1"
fi
  • []
    test 命令的另一種形式, 其中使用的命令見上一節

  • [[]]

是一個關鍵字, 比 []更加通用,其中可以使用正則
例如:

if [[  1.1 > 1.1 ]] || [[ 1.1 == 1.1 ]]; then
  echo "ok"
fi

if [[ "123" == 12* ]]; then #右邊是正則不需要引號
  echo "ok"
fi

if [[  2.1 > 1.1 ]]; then  #支持浮點型
  echo "ok"
fi
  • {}
    用於通配擴展

循環

循環中 continue命令與break作用和其他語言中類似

for 循環

語法爲:

for a in "item1" "item2" "item3"
do
    echo $a
done

例:

#輸出當前目錄下所有.sh結尾的文件
for a in `ls ./`
do
    if [[ $a == *.sh ]]
  then
        echo $a
  fi
done

while 循環

語法:

while condition
do
    command
done

例如:

#輸出1~10000
int=1;
while(($int<=10000))do
    echo $int
    ((int++))
done

函數

shell也可以用戶定義函數,然後在shell腳本中可以隨便調用
所有函數在使用前必須定義。這意味着必須將函數放在腳本開始部分,直至shell解釋器首次發現它時,纔可以使用。調用函數僅使用其函數名即可。 語法格式如下:

[function] funname()
{

    cmd....
    [return int]
}

函數傳參

調用函數時可以向其傳遞參數。 在函數體內部,通過 $n 的形式來獲取參數的值,例如,$1表示第一個參數,$2表示第二個參數… 調用的時候 ,函數名,參數直接用空格分割開

output()
{
    echo "$1"
    echo "$2"
}
output 1 2 #調用output

輸出:
1
2

字典

使用示例如下:

#!/bin/bash

#聲明
declare -A dic 
#賦值
dic=( 
    [node1]="value1" 
    [noid2]="value2"
)

# 打印元素個數
echo ${#dic[@]}
#遍歷value
echo ${dic[*]}
#遍歷key
echo ${!dic[*]}
#使用for循環遍歷key
for key in  ${!dic[*]}
do
        echo "$key : ${dic[$key]}"
done

#字典添加一個新元素
dic+=([key4]="value4")

輸出爲

2
value1 value2
node1 noid2
node1 : value1
noid2 : value2

解釋器要用bsah, sh不支持declare關鍵字及其語法

數組

語法如下:

#數組
list=("value1" "value2" "value3")
#打印指定元素
echo ${list[2]}
#打印所有下標
echo ${!list[*]}
#打印所有數組元素
echo ${list[*]}
#數組增加一個元素
1. list+=("value4")
2. 下標添加 
#for循環
for key in ${list[@]}
do
        echo ${key}
done

輸出如下:

value3
0 1 2
value1 value2 value3
value1 value2 value3 value4
value1
value2
value3
value4

截取字符串

注意,字符串長度從第0位開始計數

#!/bstrn/bash

str="123456789"

echo ${#str}            #字符串長度
echo ${str:5}           #從左邊第五個字符開始到結束
echo ${str:2:4}         #左邊第二個字符開始四個字符
echo ${str:(-3):1}      #倒數第三位
echo ${str:(-3)}        #倒數三位
echo ${str:(-4):2}      #倒數第四位開始兩位

輸出

9
6789
34
3456
7
789
67

以上方法應該基本能囊括所有的基本需求了

去除行首空格

sed 's/^[ \t]*//g'

去除行尾空格

sed 's/[ \t]*$//g'

刪除所有空格

sed s/[[:space:]]//g

時間

echo $(date +“%Y%m%d%H%M%S")	#年月日時分秒
echo $(date +"%F %T)			#年月日時分秒

輸出格式不同

20190718135605
2019-07-18 13:56:05

獲取昨天/明天…的日期

使用格式爲
獲取今天之後的n天: date +"%Y%m%d" -d "+ n days"
獲取今天之前的n天: date +"%Y%m%d" -d "- n days"

#!/bin/bash

today=$(date +"%Y%m%d")
yday=$(date +"%Y%m%d" -d "yesterday")
day=$(date +"%Y%m%d" -d "-2 days")

echo ${today}
echo ${yday}
echo ${day}

輸出爲

20190816
20190815
20190814

執行sql查詢保存結果

假如有一張student表, 有id name score等字段
1.
例:

HOST="127.0.0.1"
USER="root"
PASSWORD="..."
DBNAME="black_db"
TABNAME="student"

QUERY_SQL="SELECT id, name FROM ${TABNAME}"

while read -a row
do 
    echo ${row[0]} ${row[1]}
done< <(echo ${QUERY_SQL} | mysql -h${HOST} -u${USER} -p${PASSWORD} ${DBNAME})

輸出

id name
1 lzj
2 balcklv
3 whitelv
4 lv

字段保存在對應的數組中打印
當然也可以賦值給變量保存供後續使用


BLACKDB="mysql -uroot -p123456lzj -h127.0.0.1"

SQL="select id, if (name = '', -1, name), money from blackDB.bankDB"

result=$(${BLACKDB} -N -e "${SQL}") #-N去掉字段名 -e接sql命令

echo ${result}

輸出

1 lzj 2 blacklv 3 whitelv 4 lv

推薦第二種方法,第一種效率太低,速度太慢

shell編程風格

可以參考Google開源項目風格指南
我的風格和其中的推薦也不是完全一樣,不過有些還是應該聽取其建議的,比如對於變量和函數的命名風格,對於變量的擴展格式等,說的就很對,應該吸取其建議

浮點數的計算和比較

  1. 使用 bc 命令, scale指明精度
res=$(echo "scale = 2; ${x} / ${y}" | bc) #保留兩位小數,計算x / y的結果 

 if [ $(echo "${res} < 0.6" | bc) -eq 1 ]; then   #比較res的值和0.6的大小
        echo "hello world"
fi 

但是bc命令計算在計算0.xxx的時候,不會顯示0, 就加入算出來是0.1, 值會是.1, 雖然不妨礙計算和比較:

echo $(echo "${res} + 0.5" | bc) #不會妨礙結果,值爲1.00
  1. 使用awk
#!/bin/bash

a=7
b=9

c=$(echo $a $b | awk '{print $1 / $2}')		
echo $c

d=$(echo $a $b | awk '{printf("%.2f", $1/$2)}') 	#使用printf可以控制精度
echo $d

輸出

0.777778
0.78

if [] 中的邏輯的判斷、比較,參數的使用

1 字符串判斷

str1 = str2      當兩個串有相同內容、長度時爲真 
str1 != str2      當串str1和str2不等時爲真 
-n str1        當串的長度大於0時爲真(串非空) 
-z str1        當串的長度爲0時爲真(空串) 
str1           當串str1爲非空時爲真

2 數字的判斷

int1 -eq int2    兩數相等爲真 
int1 -ne int2    兩數不等爲真 
int1 -gt int2    int1大於int2爲真 
int1 -ge int2    int1大於等於int2爲真 
int1 -lt int2    int1小於int2爲真 
int1 -le int2    int1小於等於int2爲真

3 文件的判斷

-r file     用戶可讀爲真 
-w file     用戶可寫爲真 
-x file     用戶可執行爲真 
-f file     文件爲正規文件爲真 
-d file     文件爲目錄爲真 
-c file     文件爲字符特殊文件爲真 
-b file     文件爲塊特殊文件爲真 
-s file     文件大小非0時爲真 
-t file     當文件描述符(默認爲1)指定的設備爲終端時爲真

3 複雜邏輯判斷

-a         與 
-o        或 
!        非

該條收集於博客


坑點

shell 腳本對格式的要求比較嚴格,有時候很容易踩坑

win和linux格式

第一次win下寫完同步到linux後運行報了一堆莫名其妙的錯誤,意識到可能是文件存儲格式不對,轉一下就好了

date+%F的使用

date+之間要有空格, 而+%F之間不能有空格

echo $(date +%F)
echo $(date +"%F %T") 	#%F %T相當於後面跟了個字符串記得加引號

if [] 的使用

我們現在進行如下的判斷

i=0
if [ ${i} -ne 0 ]; then 

需要注意的是[]中收尾要加空格

if [${i} -ne 0]; then  #這樣不加空格就會報錯

declare 在函數內聲明賦值

字典的 declare 聲明式如果是在函數內部,那麼在函數內 / 外再爲其賦值,都不對,echo打印一下會發現其元素內容總是不對的,
但是如果·declare -A`的聲明式在函數外部,那麼一切正常

這個問題我還沒明白爲什麼,按理說應該都是全局變量
日後明白了再更
歡迎解惑···

賦值時空格的問題

要注意必須是:a=b
不能是:a = b
=左右不能有空格

效率問題

在 shell 中循環次數過多效率極低
一般將結果存入文件, 再使用 awk 讀出

將數組作爲參數傳遞

假設有數組array

#!/bin/bash

function ()
{
    arr=$1
    for i in ${arr[*]}; do	#arr保存的是array中所有元素,以空格區分,並不是直接arr就是一個數組
        echo $i
    done

}

showArr "${array[*]}"	#必須這樣傳遞,不然只會傳數組第一個元素

數據庫查詢結果替換分隔符

		data=$(${MYSQL} -s -B -N -e "${SQL}") 
        if [ "x${data}" != "x" ]; then
            echo "${data}" >> "file.tmp"
        fi
        awk 'BEGIN{FS=" "}{print $1,$2 ...$n "|" }' OFS="," file.tmp${i} >> file${i}

$1...$n代表 n 個字段,FS=“ ”代表將結果中的空格分隔換爲OFS=","的逗號分隔,並以 “|”結尾(當然也可以不要)
先將查詢結果存入tmp文件在使用awk處理,不存入文件直接處理會丟失數據,多條結果只會處理第一條 (理所當然了,你的參數只有第一條的)

將Query結果寫入文件不換行?

將 DB 的查詢結果直接 echo 重定向寫入文件卻沒有換行?
是因爲沒有加 ""
應該是 echo "${query_result}" > file

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章