在前面的章節中,我們已經裝備了一個命令行工具的武器庫。雖然這些工具能夠解決許多種計算問題, 但是我們仍然局限于在命令行中手動地一個一個使用它們。難道不是很棒,如果我們能夠讓 shell 來完成更多的工作? 我們可以的。通過把我們的工具一起放置到我們自己設計的程序中,然后 shell 就會自己來執行這些復雜的任務序列。 通過編寫 shell 腳本,我們讓 shell 來做這些事情。
## 什么是 Shell 腳本?
最簡單的解釋,一個 shell 腳本就是一個包含一系列命令的文件。shell 讀取這個文件,然后執行 文件中的所有命令,就好像這些命令已經直接被輸入到了命令行中一樣。
Shell 有些獨特,因為它不僅是一個功能強大的命令行接口,也是一個腳本語言解釋器。我們將會看到, 大多數能夠在命令行中完成的任務也能夠用腳本來實現,同樣地,大多數能用腳本實現的操作也能夠 在命令行中完成。
雖然我們已經介紹了許多 shell 功能,但只是集中于那些經常直接在命令行中使用的功能。 Shell 也提供了一些通常(但不總是)在編寫程序時才使用的功能。
## 怎樣編寫一個 Shell 腳本
為了成功地創建和運行一個 shell 腳本,我們需要做三件事情:
1. 編寫一個腳本。?Shell 腳本就是普通的文本文件。所以我們需要一個文本編輯器來書寫它們。最好的文本 編輯器都會支持語法高亮,這樣我們就能夠看到一個腳本關鍵字的彩色編碼視圖。語法高亮會幫助我們查看某種常見 錯誤。為了編寫腳本文件,vim,gedit,kate,和許多其它編輯器都是不錯的候選者。
2. 使腳本文件可執行。?系統會相當挑剔不允許任何舊的文本文件被看作是一個程序,并且有充分的理由! 所以我們需要設置腳本文件的權限來允許其可執行。
3. 把腳本放置到 shell 能夠找到的地方?當沒有指定可執行文件明確的路徑名時,shell 會自動地搜索某些目錄, 來查找此可執行文件。為了最大程度的方便,我們會把腳本放到這些目錄當中。
## 腳本文件格式
為了保持編程傳統,我們將創建一個 “hello world” 程序來說明一個極端簡單的腳本。所以讓我們啟動 我們的文本編輯器,然后輸入以下腳本:
~~~
#!/bin/bash
# This is our first script.
echo 'Hello World!'
~~~
對于腳本中的最后一行,我們應該是相當的熟悉,僅僅是一個帶有一個字符串參數的 echo 命令。 對于第二行也很熟悉。它看起來像一個注釋,我們已經在許多我們檢查和編輯過的配置文件中 看到過。關于 shell 腳本中的注釋,它們也可以出現在文本行的末尾,像這樣:
~~~
echo 'Hello World!' # This is a comment too
~~~
文本行中,# 符號之后的所有字符都會被忽略。
類似于許多命令,這也在命令行中起作用:
~~~
[me@linuxbox ~]$ echo 'Hello World!' # This is a comment too
Hello World!
~~~
雖然很少在命令行中使用注釋,但它們也能起作用。
我們腳本中的第一行文本有點兒神秘。它看起來它應該是一條注釋,因為它起始于一個#符號,但是 它看起來太有意義,以至于不僅僅是注釋。事實上,這個#!字符序列是一種特殊的結構叫做 shebang。 這個 shebang 被用來告訴操作系統將執行此腳本所用的解釋器的名字。每個 shell 腳本都應該把這一文本行 作為它的第一行。
讓我們把此腳本文件保存為 hello_world。
## 可執行權限
下一步我們要做的事情是讓我們的腳本可執行。使用 chmod 命令,這很容易做到:
~~~
[me@linuxbox ~]$ ls -l hello_world
-rw-r--r-- 1 me me 63 2009-03-07 10:10 hello_world
[me@linuxbox ~]$ chmod 755 hello_world
[me@linuxbox ~]$ ls -l hello_world
-rwxr-xr-x 1 me me 63 2009-03-07 10:10 hello_world
~~~
對于腳本文件,有兩個常見的權限設置;權限為755的腳本,則每個人都能執行,和權限為700的 腳本,只有文件所有者能夠執行。注意為了能夠執行腳本,腳本必須是可讀的。
## 腳本文件位置
當設置了腳本權限之后,我們就能執行我們的腳本了:
~~~
[me@linuxbox ~]$ ./hello_world
Hello World!
~~~
為了能夠運行此腳本,我們必須指定腳本文件明確的路徑。如果我們沒有那樣做,我們會得到這樣的提示:
~~~
[me@linuxbox ~]$ hello_world
bash: hello_world: command not found
~~~
為什么會這樣呢?什么使我們的腳本不同于其它的程序?結果證明,什么也沒有。我們的 腳本沒有問題。是腳本存儲位置的問題。回到第12章,我們討論了 PATH 環境變量及其它在系統 查找可執行程序方面的作用。回顧一下,如果沒有給出可執行程序的明確路徑名,那么系統每次都會 搜索一系列的目錄,來查找此可執行程序。這個/bin 目錄就是其中一個系統會自動搜索的目錄。 這個目錄列表被存儲在一個名為 PATH 的環境變量中。這個 PATH 變量包含一個由冒號分隔開的目錄列表。 我們可以查看 PATH 的內容:
~~~
[me@linuxbox ~]$ echo $PATH
/home/me/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:
/bin:/usr/games
~~~
這里我們看到了我們的目錄列表。如果我們的腳本駐扎在此列表中任意目錄下,那么我們的問題將 會被解決。注意列表中的第一個目錄,/home/me/bin。大多數的 Linux 發行版會配置 PATH 變量,讓其包含 一個位于用戶家目錄下的 bin 目錄,從而允許用戶能夠執行他們自己的程序。所以如果我們創建了 一個 bin 目錄,并把我們的腳本放在這個目錄下,那么這個腳本就應該像其它程序一樣開始工作了:
~~~
[me@linuxbox ~]$ mkdir bin
[me@linuxbox ~]$ mv hello_world bin
[me@linuxbox ~]$ hello_world
Hello World!
~~~
它的確工作了。
如果這個 PATH 變量不包含這個目錄,我們能夠輕松地添加它,通過在我們的.bashrc 文件中包含下面 這一行文本:
~~~
export PATH=~/bin:"$PATH"
~~~
當做了這個修改之后,它會在每個新的終端會話中生效。為了把這個修改應用到當前的終端會話中, 我們必須讓 shell 重新讀取這個 .bashrc 文件。這可以通過 “sourcing”.bashrc 文件來完成:
~~~
[me@linuxbox ~]$ . .bashrc
~~~
這個點(.)命令是 source 命令的同義詞,一個 shell 內部命令,用來讀取一個指定的 shell 命令文件, 并把它看作是從鍵盤中輸入的一樣。
* * *
注意:在 Ubuntu 系統中,如果存在 ~/bin 目錄,當執行用戶的 .bashrc 文件時, Ubuntu 會自動地添加這個 ~/bin 目錄到 PATH 變量中。所以在 Ubuntu 系統中,如果我們創建 了這個 ~/bin 目錄,隨后退出,然后再登錄,一切會正常運行。
* * *
### 腳本文件的好去處
這個 ~/bin 目錄是存放為個人所用腳本的好地方。如果我們編寫了一個腳本,系統中的每個用戶都可以使用它, 那么這個腳本的傳統位置是 /usr/local/bin。系統管理員使用的腳本經常放到 /usr/local/sbin 目錄下。 大多數情況下,本地支持的軟件,不管是腳本還是編譯過的程序,都應該放到 /usr/local 目錄下, 而不是在 /bin 或 /usr/bin 目錄下。這些目錄都是由 Linux 文件系統層次結構標準指定,只包含由 Linux 發行商 所提供和維護的文件。
## 更多的格式技巧
嚴肅認真的腳本書寫,一個關鍵目標是為了維護方便;也就是說,一個腳本可以輕松地被作者或其它 用戶修改,使它適應變化的需求。使腳本容易閱讀和理解是一種方便維護的方法。
### 長選項名稱
我們學過的許多命令都以長短兩種選項名稱為特征。例如,這個 ls 命令有許多選項既可以用短形式也 可以用長形式來表示。例如:
~~~
[me@linuxbox ~]$ ls -ad
~~~
和:
~~~
[me@linuxbox ~]$ ls --all --directory
~~~
是等價的命令。為了減少輸入,當在命令行中輸入選項的時候,短選項更受歡迎,但是當書寫腳本的時候, 長選項能提供可讀性。
### 縮進和行繼續符
當雇傭長命令的時候,通過把命令在幾個文本行中展開,可以提高命令的可讀性。 在第十八章中,我們看到了一個特別長的 find 命令實例:
~~~
[me@linuxbox ~]$ find playground \( -type f -not -perm 0600 -exec
chmod 0600 ‘{}’ ‘;’ \) -or \( -type d -not -perm 0711 -exec chmod
0711 ‘{}’ ‘;’ \)
~~~
顯然,這個命令有點兒難理解,當第一眼看到它的時候。在腳本中,這個命令可能會比較容易 理解,如果這樣書寫它:
~~~
find playground \
\( \
-type f \
-not -perm 0600 \
-exec chmod 0600 ‘{}’ ‘;’ \
\) \
-or \
\( \
-type d \
-not -perm 0711 \
-exec chmod 0711 ‘{}’ ‘;’ \
\)
~~~
通過使用行繼續符(反斜杠-回車符序列)和縮進,這個復雜命令的邏輯性更清楚地描述給讀者。 這個技巧在命令行中同樣生效,雖然很少使用它,因為輸入和編輯這個命令非常麻煩。腳本和 命令行的一個區別是,腳本可能雇傭 tab 字符拉實現縮進,然而命令行卻不能,因為 tab 字符被用來 激活自動補全功能。
> 為書寫腳本配置 vim
>
> 這個 vim 文本編輯器有許多許多的配置設置。有幾個常見的選項能夠有助于腳本書寫:
>
> :syntax on
>
> 打開語法高亮。通過這個設置,當查看腳本的時候,不同的 shell 語法元素會以不同的顏色 顯示。這對于識別某些編程錯誤很有幫助。并且它看起來也很酷。注意為了這個功能起作用,你 必須安裝了一個完整的 vim 版本,并且你編輯的文件必須有一個 shebang,來說明這個文件是 一個 shell 腳本。如果對于上面的命令,你遇到了困難,試試?:set syntax=sh。
>
> :set hlsearch
>
> 打開這個選項是為了高亮查找結果。比如說我們查找單詞“echo”。通過設置這個選項,這個 單詞的每個實例會高亮顯示。
>
> :set tabstop=4
>
> > 設置一個 tab 字符所占據的列數。默認是8列。把這個值設置為4(一種常見做法), 從而讓長文本行更容易適應屏幕。
>
> :set autoindent
>
> 打開 “auto indent” 功能。這導致 vim 能對新的文本行縮進與剛輸入的文本行相同的列數。 對于許多編程結構來說,這就加速了輸入。停止縮進,輸入 Ctrl-d。
>
> 通過把這些命令(沒有開頭的冒號字符)添加到你的 ~/.vimrc 文件中,這些改動會永久生效。
## 總結歸納
在這腳本編寫的第一章中,我們已經看過怎樣編寫腳本,怎樣讓它們在我們的系統中輕松地執行。 我們也知道了怎樣使用各種格式技巧來提高腳本的可讀性(可維護性)。在以后的各章中,輕松維護 會作為編寫好腳本的中心法則一次又一次地出現。
## 拓展閱讀
* 查看各種各樣編程語言的“Hello World”程序和實例:
[http://en.wikipedia.org/wiki/Hello_world](http://en.wikipedia.org/wiki/Hello_world)
* 這篇 Wikipedia 文章討論了更多關于 shebang 機制的內容:
[http://en.wikipedia.org/wiki/Shebang_(Unix)](http://en.wikipedia.org/wiki/Shebang_(Unix))
- 第一章:引言
- 第二章:什么是shell
- 第三章:文件系統中跳轉
- 第四章:研究操作系統
- 第五章:操作文件和目錄
- 第六章:使用命令
- 第七章:重定向
- 第八章:從shell眼中看世界
- 第九章:鍵盤高級操作技巧
- 第十章:權限
- 第十一章:進程
- 第十二章:shell環境
- 第十三章:VI簡介
- 第十四章:自定制shell提示符
- 第十五章:軟件包管理
- 第十六章:存儲媒介
- 第十七章:網絡系統
- 第十八章:查找文件
- 第十九章:歸檔和備份
- 第二十章:正則表達式
- 第二十一章:文本處理
- 第二十二章:格式化輸出
- 第二十三章:打印
- 第二十四章:編譯程序
- 第二十五章:編寫第一個shell腳本
- 第二十六章:啟動一個項目
- 第二十七章:自頂向下設計
- 第二十八章:流程控制 if分支結構
- 第二十九章:讀取鍵盤輸入
- 第三十章:流程控制 while/until 循環
- 第三十一章:疑難排解
- 第三十二章:流程控制 case分支
- 第三十三章:位置參數
- 第三十四章:流程控制 for循環
- 第三十五章:字符串和數字
- 第三十六章:數組
- 第三十七章:奇珍異寶