1. 程式人生 > >Shell與腳本

Shell與腳本

開始 整型 註釋符 語句 文本 name 運算 指定 shel

shell是Linux操作系統的用戶接口,我們經常需要編寫腳本讓操作系統自動執行一系列指令的需求,本文將簡單介紹開發shell腳本的所需的語言特性。

shell腳本是指令序列,其指令可以直接在終端中執行。同樣地,終端中的指令也可以寫到腳本中。

腳本文件通常以.sh作為後綴名,第一行以#!開頭指定執行腳本的程序:

#!/usr/bin/bash

#!開頭的第一行被稱為Hashbang或Shebang。 而#是shell腳本中的行註釋符。

通常有三種執行腳本的方式:

  • sh start.sh: 在終端中創建一個sh子進程執行腳本, 執行者需要擁有腳本的讀權限。
    該方式實際上是將腳本路徑作為參數傳遞給了sh
    命令。
  • source start.sh: 在終端中執行腳本,相當於將腳本中的指令逐條復制到終端執行。
    腳本中局部變量將保留在終端環境變量中, 腳本的pid和工作目錄等環境也與終端一致。
  • ./start.sh: 根據HashBang指定的程序,在子進程中執行腳本。

sh命令有一些有用的選項幫助我們開發和調試腳本:

  • sh -n start.sh:對腳本進行語法檢查, 不實際執行腳本
  • sh -x start.sh: 把將要執行的命令輸出到stderr便於進行調試

變量

shell中變量是弱類型的, 變量名只能包含字母、數字或下劃線"_",首字符只能為字母。

shell主要面向文本處理而非數據計算,因此變量默認類型為字符串型。

A=abc
echo $A

變量在使用前無需聲明,在為變量賦值時=左右不能添加空格。

A = abc會被shell解釋為執行指令A,參數為=abc

$為變量標誌符, echo $A指令將顯示變量A的內容abc, 為了明確指定變量名也可以寫作${A}

A=a
AB=ab
echo ${A}B

$(cmd)可以把命令的輸出作為返回值, 如:

PWD=$(pwd)

變量$PWD存儲了當前的工作目錄路徑。

在shell中可以直接書寫字符串,但仍建議用單引號或雙引號標識字符串。

在單引號標識的字符串中$不被作為變量標識符, 而雙引號則會將$替換為變量內容。

A="abc"
echo ‘$A‘ # $A
echo "$A" # abc

字符串拼接不需要任何運算符,只需要將它們寫在一起即可:

A="abc"
B="123"
echo "$A+$B"  # abc+123
echo "$A$B"  # abc123
echo "$Adef"  # abcdef

全局變量

變量按照作用域可以分為局部變量和全局變量,上文示例中定義的變量都是局部變量, 作用域僅限執行腳本的進程。

子進程可以繼承父進程的全局變量,export指令用於定義全局變量:

export A=abc

整型變量

shell僅支持整型計算, declare命令可以聲明整型變量,let指令用於算術運算:

declare -i a=1
let a=a+1
echo $a  # 2
let a+=1
echo $a  # 3

let指令支持算術運算符包括:

  • +:加法
  • -: 減法
  • *: 乘法
  • /: 除法
  • **: 乘方
  • %: 取余

let指令也支持算術運算符對應的算術賦值運算符,如+=

數組

bash中可以使用圓括號定義數組,元素之間用空格分割,數組下標從1開始:

arr=(1 ‘a‘ "abc")
echo ${arr[1]}

也可以直接使用下標定義數組:

arr2[1]=1
arr2[2]=2

該方法同樣可以用於修改已存在的數組。

特殊變量

shell中預定義了一些特殊變量,通過這些變量可以獲得環境信息:

  • $$: 執行腳本的進程ID(pid)
  • $?: 上一條命令的返回值
  • $!: 上一條後臺指令的執行進程的ID

上述變量在交互式終端中同樣有效。

還有一些變量可以獲得執行腳本時傳入的參數:

  • $0: 腳本的文件名
  • $1~$n: 傳給腳本的第n個參數
  • $#: 傳入參數的個數
  • $@: 參數列表
  • $*: 單個字符串形式的參數列表

流程控制

if

declare -i a=90
if [ $a -gt 80 ]; then
    echo "A"
elif [ $a -lt 60 ]; then
    echo "C"
else
    echo "D"
fi

結束標誌fi即是if反寫, 我們還將在其它地方遇到bash的這種命名風格。

註意,[]旁邊的空格不可省略。

-lt, -gt用於進行整型的大小比較:

  • -eq: 等於(equal)
  • -ne: 不等於(not equal)
  • -gt: 大於(greater)
  • -ge: 大於等於(greater-equal)
  • -lt: 小於(less)
  • -le: 小於等於(less-equal)

進行復合邏輯判斷也很簡單:

if [ $a -gt 60 -a ( ! $a -gt 90 -o $a eq 91 ) ]; then
    echo "make no sense"
fi
  • !: 非
  • -a: 且and
  • -o: 或-o

邏輯運算遵循短路計算原則。

<, >等運算符在[]中只能用於字符串的比較, 而在[[]]<, >可以用於整型和字符串的大小比較, 也可以使用&&||來書寫邏輯表達式。

if的條件判斷不一定使用[][[]]表達式,它可以是任何一個命令。命令的返回值為0則if判斷為真, 非0判斷為假。

[][[]]轉義表達式也可以像普通指令一樣執行,判斷為真則返回0,假則返回非0值。

[ 2 -gt 1 -a 3 -lt 4 ] && echo ‘ok‘

除此之外,if還可以進行更多種類的條件判斷:

判斷字符串相等

if [ ${NAME} = ‘tmp‘ ]; then
    echo "name is tmp"
fi

判斷文件是否存在

if [ -e tmp ]; then
    echo "tmp exists"
fi 

判斷tmp是否存在,tmp可以是目錄或文件。

判斷是否為普通文件

if [ -f tmp ]; then
    echo "file tmp exists"
fi

判斷tmp是否為文件,tmp不能是目錄。

判斷是否為目錄

if [ -d tmp ]; then
    echo "directory tmp exists"
fi

判斷是否具有執行權限

if [ -x tmp ]; then
    echo "tmp is executable"
fi

不判斷文件是否可執行,只判斷是否擁有x權限。 因此,tmp為有x權限的目錄時也會判斷為真。

類似的還有,-w判斷是否擁有寫入權限, -r判斷是否擁有讀取權限。

判斷是否為空文件

if [ -s tmp ]; then
    echo "file is not empty"
fi

case

case類似於其它語言中的switch語句:

case ${NAME} in
    ‘a‘)
        echo "name is a"
        ;;
    ‘b‘)
        echo "name is b"
        ;;
    *)
        echo "other names"
        ;;
esac

從第一個匹配的標簽開始執行, 兩個標簽之間必須有;;*)是其它標簽都不匹配時的默認標簽。

for

for循環可以遍歷一個序列:

for i in $(seq 1 10); do
    echo $i
done
# echo: 1 2 3 ... 10

一些命令的輸出也可以作為序列:

for i in $(ls); do
  echo $i
done

遍歷所有參數:

for arg in "$@"; do  
    echo $arg  
done 

另一種形式的for循環:

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

while

declare -i i=0
while [ $i -lt 10 ]; do
    echo $i
    i=$i+1
done

while(true)這樣的死循環也很容易:

declare -i i=0
while ; do
    echo $i
    [ ! $i -lt 10 ] && break
    i=$i+1
done

函數

shell提供了定義函數的功能, 函數就像是腳本中的子腳本:

range() {
    for (( i=0; i<${1}; i++)); do
        echo $i
    done
    return ${1}
}

range 100

函數同樣使用位置參數$1~$n來訪問參數,$0為函數的名稱, $@, $#等變量的含義不變。

進程間通信

管道

管道用於將上一條指令的輸出作為下一條指令的輸入:

ls | grep ".zip"

xargs

有一些指令不支持使用管道傳遞參數,因此需要xargs命令

find ~ | xargs ls 

xargs會以空格為分隔符將輸入分隔為參數,然後將參數傳給ls。

重定向

重定向用於將命令的輸入輸出從標準流重定向到文件。標準流包括:

  • stdin: 標準輸入流,文件描述符為0
  • stdout: 標準輸出流,文件描述符1
  • stderr: 標準錯誤流,文件描述符2

輸出到文件,覆蓋原有內容:

echo "hello" > 1.txt 

輸出到文件, 追加到文件尾:

echo "hello" >> 1.txt

從文件輸入:

wc -l < 1.txt

重定向標準錯誤輸出流:

cmd 2> 2.txt

將標準錯誤輸出追加到文件:

cmd 2>> 2.txt

將標準錯誤和標準輸出一同重定向到文件:

cmd > 1.log 2>&1

2>&1是將stderr重定向到stdout。

後臺執行

shell可以執行一行指令後立即返回, 返回後可以通過$?變量獲得執行進程的ID:

$ sleep 10 &
[1] 79403
$ echo $!
79403

Shell與腳本