bash shell編程快速入門教程

教程 7039瀏覽 2評論

bash shellShell?俗稱殼(用來區別于核),是指“提供使用者使用界面”的命令解析器(軟件)。它類似于DOS下的command和后來的cmd.exe。它接收用戶命令,然后調用相應的應用程序。

同時,Shell又是一種程序設計語言。作為命令語言,它交互式解釋和執行用戶輸入的命令,或者自動地解釋和執行預先設定好的一連串的命令。Shell不像C/C++等語言,它不需要編譯就能執行。作為程序設計語言,Shell 定義了各種變量和參數,并提供了許多在高級語言中才具有的控制結構,包括循環和分支。

UNIX系統上有很多版本的Shell,bash是各種Linux發行版標準配置的Shell,在Linux系統上/bin/sh往往是指向/bin/bash的符號鏈接。

1 簡單的Hello World!程序

幾乎所有的講解編程的書給讀者的第一個例子都是 Hello World 程序,那么我們今天也就從這個例子出發,來逐步了解 bash。用 vi 編輯器編輯一個 hello 文件如下:

#!/bin/bash 
# This is a very simple example
echo Hello World

這樣最簡單的一個 BASH 程序就編寫完了,以下逐個說明每行的作用。

  • 第一行:在 BASH?中,第一行的 "#!" 及后面的 "/bin/bash" 就表明該文件是一個 BASH 程序,需要由 /bin 目錄下的 BASH 程序來解釋執行。
  • 第二行: "# This is a ..." 就是 BASH 程序的注釋,在 BASH 程序中從“#”號(注意:后面緊接著是“!”號的除外)開始到行尾的多有部分均被看作是程序的注釋。
  • 的三行:echo 語句的功能是把 echo 后面的字符串,輸出到標準輸出中去。由于 echo 后跟的是 "Hello World" 這個字符串,因此 "Hello World"這個字串就被顯示在控制臺終端的屏幕上了。

注意:BASH 中的絕大多數語句結尾處都沒有分號。

執行改程序有兩種方法,一種是顯式制定 BASH 去執行,如下兩條命令:

$ bash hello
$ sh hello                 # 這里 sh 是指向 bash 的一個鏈接

另外一種方法,可以先將 hello 文件改為可以執行文件,然后直接運行它。此時由于 hello 文件第一行的 "#!/bin/bash" 的作用,系統會自動用/bin/bash 程序去解釋執行 hello 文件的:

$ chmod u+x hello          # 修改為可執行文件
$ ./hello                  # 執行

注意:BASH 程序被執行后,實際上 Linux 系統是另外開設了一個進程來運行的。

2?輸入、輸出和錯誤輸出

在字符終端環境中,標準輸入/標準輸出的概念很好理解:

  • 輸入即指對一個應用程序 或命令的輸入,無論是從鍵盤輸入還是從別的文件輸入;
  • 輸出即指應用程序或命令產生的一些信息;
  • 錯誤輸出在Linux 系統下還有一個標準錯誤輸出的概念,與 Windows 系統不同,這個概念主要是為程序調試和系統維護目的而設置的,錯誤輸出于標準輸出分開,可以讓一些高級的錯誤信息不干擾正常的輸出信息,方便一般用戶的使用。

在 Linux 系統中:標準輸入(stdin)默認為鍵盤輸入標準輸出(stdout)默認為屏幕輸出標準錯誤輸出(stderr)默認也是輸出到屏幕(上面的 std 表示 standard)。在 BASH 中,使用這些概念時一般將標準輸出表示為 1,標準錯誤輸出表示為 2。下面我們舉例來說明如何使用他們。

輸入、輸出及標準錯誤輸出主要用于 I/O 的重定向,就是說需要改變他們的默認設置。先看這個例子:

$ ls > ls_result
$ ls -l >> ls_result

第一條命令將 ls 命令的結果輸出重定向到 ls_result 文件中,第二條命令則將 ls -l 命令的結果追加到 ls_result 文件中,兩條命令都不是輸出到屏幕上。

  • >:就是輸出重定向的代表符號(包括標準輸出和標準錯誤輸出)。
  • >>:則表示不清除原來內容而追加輸出。

下面再來看一個稍微復雜的例子:

$ find /home -name lost* 2> err_result

這個命令在 ">" 符號之前多了一個 "2","2>" 表示將標準錯誤輸出重定向。由于 /home 目錄下有些目錄由于權限限制不能訪問,因此會產生一些標準錯誤輸出被存放在 err_result 文件中。如果直接執行

find /home -name lost* > all_result

其結果是只有標準輸出被存入 all_result 文件中,要想讓標準錯誤輸出和標準輸入一樣都被存入到文件中,那該怎么辦呢?看下面這個例子:

$ find /home -name lost* > all_result 2>& 1     # 或
$ find /home -name lost* >& all_result          # 簡潔寫法

上面這個例子中,首先將標準錯誤輸出重定向到標準輸出中,再將標準輸出重定向到 all_result 這個文件中。這樣我們就可以將所有的輸出都存儲到 all_result?文件中了。

如果那些出錯信息并不重要,下面這個命令可以讓你避開眾多無用出錯信息的干擾:

$ find /home -name lost* 2> /dev/null

同學們回去后還可以再試驗一下如下幾種重定向方式,看看會出什么結果,為什么?

$ find /home -name lost* > all_result 1>& 2
$ find /home -name lost* 2> all_result 1>& 2
$ find /home -name lost* 2>& 1 > all_result

另外一個非常有用的重定向操作符是 "-",請看下面這個例子:

$ (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xvfp -)

該命令表示把 /source/directory 目錄下的所有文件通過壓縮和解壓,快速的全部移動到 /dest/directory 目錄下去。這個命令在 /source/directory 和 /dest/directory 不處在同一個文件系統下時將顯示出特別的優勢。

下面還幾種不常見的用法:

  • n<&-:表示將 n 號輸入關閉
  • <&-:表示關閉標準輸入(鍵盤)
  • n>&-:表示將 n 號輸出關閉
  • >&-:表示將標準輸出關閉

3 變量

對于熟悉 C 語言的程序員,我們將解釋 BASH 中的定義和用法與 C 語言中有何不同。BASH 中的變量都是不能含有保留字,不能含有 "-" 等保留字符,也不能含有空格。

3.1 簡單說明

在 BASH 中變量定義是不需要的,沒有 "int i" 這樣的定義過程。如果想用一個變量,只要他沒有在前面被定義過,就直接可以用,當然你使用該變量的第一條語句應該是對他賦初值了,如果你不賦初值也沒關 系,只不過該變量是空( 注意:是 NULL,不是 0 )。不給變量賦初值雖然語法上不反對,但不是一個好的編程習慣。我們看看下面的例子:

首先用 vi 編輯下面這個文件 hello2:

#!/bin/bash
# give the initialize value to STR
STR="Hello World"
echo $STR

在上面這個程序中我們需要注意下面幾點:

  1. 變量賦值時,'='左右兩邊都不能有空格;
  2. BASH 中的語句結尾不需要分號(";");
  3. 除了在變量賦值和在?for?循環語句頭中,BASH 中的變量使用必須在變量前加"$"符號。
  4. 由于 BASH 程序是在一個新的進程中運行的,所以該程序中的變量定義和賦值不會改變其他進程,或原始 Shell 中同名變量的值,也不會影響他們的運行。

更細致的文檔甚至提到,以單引號括起來的變量將不被 BASH 解釋為變量,如 '$STR' ,而被看成為純粹的字符串。而且更為標準的變量引用方式是 ${STR} 這樣的,$STR 自不過是對 ${STR} 的一種簡化。在復雜情況下(即有可能產生歧義的地方)最好用帶 {} 的表示方式。

3.2 整數和字符串變量

BASH 中的變量既然不需要定義,也就沒有類型一說,一個變量即可以被定義為一個字符串,也可以被再定義為整數。如果對該變量進行整數運算,他就被解釋為整數;如果對他進行字符串操作,他就被看作為一個字符串。請看下面的例子:

#!/bin/bash
x=1999
let "x = $x + 1"
echo $x
x="olympic'"$x
echo $x

關于整數變量計算,有如下幾種:" + - * / % ",他們的意思和字面意思相同。整數運算一般通過 let 和 expr 這兩個指令來實現,如對變量 x 加 1 可以寫作:let "x = $x + 1" 或者 x=`expr $x + 1`

在比較操作上,整數變量和字符串變量各不相同,詳見下表:

對應的操作
整數操作
字符串操作
相同
-eq
=
不同
-ne
!=
大于
-gt
>
小于
-lt
<
大于或等于
-ge
小于或等于
-le
為空
-z
不為空
-n

比如:

  • 比較字符串 a 和 b 是否相等就寫作:if [ $a = $b ]
  • 判斷字符串 a 是否為空就寫作: if [ -z $a ]
  • 判斷整數變量 a 是否大于 b 就寫作:if [ $a -gt $b ]

注意:在字符串比較時盡量不要使用 -n ,而用 ! -z 來代替。(其中符號 "!" 表示求反操作)

3.3 文件變量

BASH 中的變量除了用于對 整數 和 字符串 進行操作以外,另一個作用是作為文件變量。BASH 是 Linux 操作系統的 Shell,因此系統的文件必然是 BASH 需要操作的重要對象,如 if [ -x /root ] 可以用于判斷 /root 目錄是否可以被當前用戶進入。下表列出了 BASH 中用于判斷文件屬性的操作符:

運算符
含義( 滿足下面要求時返回 TRUE )
-e file
文件 file 已經存在
-f file
文件 file 是普通文件
-s file
文件 file 大小不為零
-d file
文件 file 是一個目錄
-r file
文件 file 對當前用戶可以讀取
-w file
文件 file 對當前用戶可以寫入
-x file
文件 file 對當前用戶可以執行
-g file
文件 file 的 GID 標志被設置
-u file
文件 file 的 UID 標志被設置
-O file
文件 file 是屬于當前用戶的
-G file
文件 file 的組 ID 和當前用戶相同
file1 -nt file2
文件 file1 比 file2 更新
file1 -ot file2
文件 file1 比 file2 更老

注意:上表中的 file 及 file1、file2 都是指某個文件或目錄的路徑。

3.4?局部變量

在 BASH 程序中如果一個變量被使用了,那么直到該程序的結尾,該變量都一直有效。為了使得某個變量存在于一個局部程序塊中,就引入了局部變量的概念。BASH 中,在變量首次被賦初值時加上 local 關鍵字就可以聲明一個局部變量,如下面這個例子:

#!/bin/bash
HELLO=Hello
function hello {
    local HELLO=World
    echo $HELLO
}
echo $HELLO
hello
echo $HELLO

該程序的執行結果是:

Hello
World
Hello

這個執行結果表明,全局變量 $HELLO 的值在執行函數 hello 時并沒有被改變。也就是說局部變量 $HELLO 的影響只存在于函數那個程序塊中。

3.5 保留變量

BASH 中有一些保留變量,下面列出了一些:

  • $IFS   ? ?這個變量中保存了用于分割輸入參數的分割字符,默認識空格。
  • $HOME  這個變量中存儲了當前用戶的根目錄路徑。
  • $PATH   這個變量中存儲了當前 Shell 的默認路徑字符串。
  • $PS1   表示第一個系統提示符。
  • $PS2  表示的二個系統提示符。
  • $PWD 表示當前工作路徑。
  • $EDITOR ? ? ? ?表示系統的默認編輯器名稱。
  • $BASH   ? ? ?表示當前 Shell 的路徑字符串。
  • $0, $1, $2, ... ?表示系統傳給腳本程序或腳本程序傳給函數的第0個、第一個、第二個等參數。
  • $#   表示腳本程序的命令參數個數或函數的參數個數。
  • $$   表示該腳本程序的進程號,常用于生成文件名唯一的臨時文件。
  • $?   表示腳本程序或函數的返回狀態值,正常為 0,否則為非零的錯誤號。
  • $*   表示所有的腳本參數或函數參數。
  • [email protected]   和 $* 涵義相似,但是比 $* 更安全。
  • $!   表示最近一個在后臺運行的進程的進程號。

3.6 特殊操作

BASH 中還有一些對變量的簡潔、快速的操作,大家還記得 "${var}" 和 "$var" 同樣是對變量的引用吧,對 ${var} 進行一些變化就可以產生一些新功能:

  • ${var-default}:表示如果變量 $var 還沒有設置,則保持 $var 沒有設置的狀態,并返回后面的默認值 default。
  • ${var=default}:表示如果變量 $var 還沒有設置,則取后面的默認值 default。
  • ${var+otherwise}:表示如果變量 $var 已經設置,則返回 otherwise 的值,否則返回空( null )。
  • ${var?err_msg}:表示如果變量 $var 已經設置,則返回該變量的值,否則將后面的 err_msg 輸出到標準錯誤輸出上。

請同學們自己嘗試下面的例子:

#!/bin/bash
echo ${var?There is an error}
exit 0

還有下面一些用法,這些用法主要用于從文件路徑字符串中提取有用信息:

  • ${var#pattern}, ${var##pattern}:用于從變量 $var 中剝去最短(最長)的和 pattern 相匹配的最左側的串。
  • ${var%pattern}, ${var%%pattern}:用于從變量 $var 中剝去最短(最長)的和 pattern 相匹配的最右側的串。

3.7 BASH變量與C變量的區別

這里我們為原來不熟悉 BASH 編程,但是非常熟悉 C 語言的程序員總結一下在 BASH 環境中使用變量需要注意的問題。

  1. BASH 中的變量在引用時都需要在變量前加上 "$" 符號( 第一次賦值及在For循環的頭部不用加 "$"符號 );
  2. BASH 中沒有浮點運算,因此也就沒有浮點類型的變量可用;
  3. BASH 中的整形變量的比較符號與 C 語言中完全不同,而且整形變量的算術運算也需要經過 let 或 expr 語句來處理。

4?流程控制語法

BASH 中幾乎含有 C 語言中常用的所有控制結構,如條件分支、循環等,下面逐一介紹。

4.1 if...then...else

if 語句用于判斷和分支,其語法規則和 C 語言的 if 非常相似。其幾種基本結構為:

if [ expression ]
then
    statments
fi

或者

if [ expression ]
then
    statments
else
    statments
fi

或者

if [ expression ]
then
    statments
else if [ expression ]
then
    statments
else
    statments
fi

或者

if [ expression ]
then
    statments
elif [ expression ]
then
    statments
else
    statments
fi

注意:如果將 if 和 then 簡潔的寫在一行里面,就必須在 then 前面加上分號,如:if [ expression ]; then ...

下面這個例子說明了如何使用 if 條件判斷語句:

#!/bin/bash
if [ $1 -gt 90 ]; then
    echo "Good, $1"
elif [ $1 -gt 70 ]
then
    echo "OK, $1"
else
    echo "Bad, $1"
fi
exit 0

上面例子中的 $1 是指命令行的第一個參數,這個會在后面的“BASH 中的特殊保留字”中講解。

4.2 for

for 循環結構與 C 語言中有所不同,在 BASH 中 for 循環的基本結構是:

for $var in
do
    statments
done

其中 $var 是循環控制變量,是 $var 需要遍歷的一個集合,do/done 對包含了循環體,相當于 C 語言中的一對大括號。

注意:如果do 和 for 被寫在同一行,必須在 do 前面加上 ";",如: for $var in; do 。

下面是一個運用 for 進行循環的例子:

#!/bin/bash
for day in Sun Mon Tue Wed Thu Fri Sat
do
    echo $day
done

# 如果列表被包含在一對雙引號中,則被認為是一個元素

for day in "Sun Mon Tue Wed Thu Fri Sat"
do
    echo $day
done
exit 0

注意:上面的例子中,在 for 所在那行的變量 day 是沒有加 "$" 符號的,而在循環體內,echo 所在行變量 $day 是必須加上 "$" 符號的。

另外如果寫成 for day 而沒有后面的 in 部分,則 day 將取遍命令行的所有參數。如這個程序:

#!/bin/bash
for param
do
    echo $param
done
exit 0

上面這個程序將列出所有命令行參數。for 循環結構的循環體被包含在 do/done 對中,這也是后面的 while、until 循環所具有的特點。

4.3 while/until

while 循環的基本結構是:

while [ condition ]
do
    statments
done

until 循環的基本結構是:

until [ condition is TRUE ]
do
   statments
done

這兩個結構也請大家自己編寫例子來驗證。

4.4 case

BASH 中的 case 結構與 C 語言中的 switch 語句的功能比較類似,可以用于進行多項分支控制。其基本結構是:

case "$var" in
    condition1 ) statments1;;
    condition2 ) statments2;;
    ...
    *) statments2;;
   default statments;;
esac

下面這個程序是運用 case 結構進行分支執行的例子

#!/bin/bash
echo "Hit a key, then hit return."
read Keypress
case "$Keypress" in
    [a-z] ) echo "Lowercase letter";;
    [A-Z] ) echo "Uppercase letter";;
    [0-9] ) echo "Digit";;
    * ) echo "Punctuation, whitespace, or other";;
esac
exit 0

上面例子中的第四行 "read Keypress" 一句中的 read 語句表示從鍵盤上讀取輸入。

4.5 break/continue

熟悉 C 語言編程的都很熟悉 break 語句和 continue 語句。BASH 中同樣有這兩條語句,而且作用和用法也和 C 語言中相同,break 語句可以讓程序流程從當前循環體中完全跳出,而 continue 語句可以跳過當次循環的剩余部分并直接進入下一次循環。

5 函數

BASH 是一個相對簡單的腳本語言,不過為了方便結構化的設計,BASH 中也提供了函數定義的功能。BASH 中的函數定義很簡單,只要向下面這樣寫就可以了:

function my_funcname {
    code block
}

或者

my_funcname() {
    code block
}

上面的第二種寫法更接近于 C 語言中的寫法。

注意:BASH 中要求函數的定義必須在函數使用之前,這是和 C 語言用頭文件說明函數方法的不同。

更進一步的問題是,如何給函數傳遞參數以及獲得返回值。BASH 中函數參數的定義并不需要在函數定義處指定,只需在函數被調用時,用 BASH 的保留變量 $1 $2 ... 來引用就可以了。BASH 可以用 return 語句來返回一個特定的整數,如果沒有 return 語句顯式的返回一個返回值,則返回值就是該函數最后一條語句執行的結果(一般為 0,如果執行失敗返回錯誤碼)。函數返回值是在調用該函數的程序體中,通過 $? 保留字來獲得。下面我們就來看看,一個用函數來計算整數平方的例子:

#!/bin/bash

square() {
    let "res = $1 * $1"
    return $res
}

square $1
result=$?
echo $result
exit 0

說明:無論是在 Shell 中對 BASH 腳本返回值的處理,還是在腳本中對函數返回值的處理,都是通過 "$?" 系統變量來獲得。BASH 要求返回值必須為一個整數,不能用 return 語句返回字符串變量。

6?隨機數

隨機數是經常要用到的,BASH 中也提供了這個功能,請看下面這個程序:

#!/bin/bash
# Prints different random integer from 1 to 65536
a=$RANDOM
echo $a
exit 0

這個程序可以在每次執行的時候,隨機的打印出一個大小在 1 到 65536 之間的整數。

7?運算符

算術運算符

  • + - * / %:表示加減乘除和取余運算
  • += -= *= /=:同 C 語言中的含義

位操作符

  • << <<= >> >>=:表示位左右移一位操作
  • & &= | |=:表示按位與、位或操作
  • ~ !:表示非操作
  • ^ ^=:表示異或操作

關系運算符

  • < > <= >= == !=:表示大于、小于、大于等于、小于等于、等于、不等于操作
  • && ||:邏輯與、邏輯或操作

8?設計簡單用戶界面

BASH 中提供了一個小的語句格式,可以讓程序快速的設計出一個字符界面,用以實現用戶交互選擇菜單,該功能由 select 語句來實現的,select 語句的語法為:

select var in
do
    statments use $var
done

執行后,BASH 會將所有項加上數字列,并在屏幕上等待用戶選擇。在用戶作出選擇后,變量 $var 中就包含了那個被選中的字符串,然后就可以對該變量進行需要的操作了。我們可以從下面的例子中更直觀的來理解這個功能:

#!/bin/bash
OPTIONS="Hello Quit"
select opt in $OPTIONS; do
    if [ "$opt" = "Quit" ]; then
        echo done
        exit
    elif [ "$opt" = "Hello" ]; then
        echo Hello World
    else
        clear
        echo bad option
    fi
done
exit 0

大家可以試著執行上面的程序,看看是什么執行結果。

9?讀取用戶輸入

BASH 中通過 read 函數讀取用戶輸入,如下面這段程序:

#!/bin/bash
echo Please enter your name
read NAME
echo "Hi! $NAME !"
exit 0

上面這個腳本讀取用戶的輸入,并回顯在屏幕上。

另外,BASH 中還提供一種稱為 here documents 的結構,可以將用戶需要鍵盤輸入的字符串,改為從程序體中直接讀入,如密碼。下面的小程序演示了這個功能:

#!/bin/bash
passwd="[email protected]"
ftp -n localhost <<FTPFTP
user anonymous $passwd
binary
bye
FTPFTP
exit 0

這個程序在用戶需要通過鍵盤敲入一些字符時,通過程序內部的動作來模擬鍵盤輸入。請注意 here documents 的基本結構為:

command <<SOMESPECIALSTRING
statments
...
SOMESPECIALSTRING

這里要求,在需要鍵盤輸入的命令后,直接加上 << 符號,然后跟上一個特別的字符串,然后按順序輸入本來應該由鍵盤輸入的所有字符,在結束后,重復一遍前面 <<符號后的“特別的字符串”即表示該輸入到此結束。

10?一些特殊的慣用法

10.1 括號

在 BASH 中,一對括號一般被用于求取括號中表達式的值,或命令的執行結果,如:(a=hello; echo $a) ,其作用相當于 `...` 。

10.2 冒號

: 有兩個含義,一是表示空語句,有點類似于 C 語言中的單個 ";" ,表示該行是一個空命令。如果被用在 while/until 的頭結構中,則表示值 0,會使循環一直進行下去,如下例:

while :
do
    operation-1
    operation-2
    ...
    operation-n
done

另外,: 還可以用于求取后面變量的值,比如:

#!/bin/bash
: ${HOSTNAME?} {USER?} {MAIL?}
echo $HOSTNAME
echo $USER
echo $MAIL
exit 0

在 BASH 中,export 命令用于將系統變量輸出到外層的 Shell 中了。

11?BASH 程序調試

bash -x bash-script 命令,可以查看 BASH 腳本到底錯在什么地方,幫助程序員找出腳本中的錯誤。

另外,用 trap 語句可以在 BASH 腳本出錯退出時,打印出一些變量的值,以供程序員檢查。trap 語句必須跟在 "#!/bin/bash" 后,并且為非注釋代碼,例如:

 trap 'message $checkvar1 $checkvar2' EXIT

以上就是 BASH編寫快速入門的所有內容。

發表我的評論
取消評論

表情

Hi,您需要填寫昵稱和郵箱!

  • 昵稱 (必填)
  • 郵箱 (必填)
  • 網址

網友最新評論 (2)

  1. 呵呵,不錯
    康慧紫 4年前 (2015-12-27) 回復 編輯
七星彩走势图2元网官网