尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
bash(以及通常意義上的shell腳本編程)出現的日子可是不短了,每天都有新手通過bash見識到shell腳本編程和系統自動化的威力。隨著微軟公司在Windows 10中發布了交互式的bash shell以及Unix子系統,現在已是更適合了解shell腳本所能做到的簡潔和高效的時候了。
從計算機出現的早期開始,shell腳本就一直在幫助系統管理員和工程師完成費時費力的枯燥工作。那麼,什麼是shell腳本?為什麼你要關心它?
什麼是shell腳本
shell腳本就是包含一組可運行的特定shell命令(在本文中是bash shell)的文本文件,命令的執行順序與其出現在腳本中的順序一致。shell以命令行的形式提供了可用的操作系統命令庫的接口。
shell腳本其實就是為使用shell環境中的命令所編寫的小型程序,可用於自動化那些通常沒人願意手動完成的任務,例如Web爬取、磁盤用量跟蹤、天氣數據下載、文件更名,等等。你甚至能夠用shell腳本製作一些初級的遊戲!腳本中可以加入簡單的邏輯,例如在其他語言中出現的if語句。
OS X、BSD以及Linux操作系統中可用的命令行shell有很多種,包括tcsh、zsh和廣受歡迎的bash。本文關注的是Unix環境中的主流:bash。
每種shell都有自己的特性和功能,但是多數人在Unix中最先熟悉的就是bash。在OS X中,Terminal(終端)應用會打開一個bash shell窗口(如圖0-1)。在Linux中,有各種各樣的shell控制台程序,其中最常見的是gnome-terminal(GNOME)或konsole(KDE)。這些應用可以修改自身的配置來使用其他類型的shell,不過默認選用的都是bash。實際上,不管你用的是哪種類Unix操作系統,打開Terminal應用,得到的都是bash。
圖0-1 OS X中的Terminal應用,其中顯示了bash的版本號
注意 2016年8月,微軟發布了針對Windows 10周年版(Windows 10 Anniversary)的bash,所以如果你用的是Windows,照樣可以使用bash shell。bash的美妙之處就在於它的可移植性,所以多數腳本基本上應該沒問題。
使用終端與系統交互可能看起來是件艱巨的任務。但隨著時間的推移,打開終端,快速對系統做出更改,會變得比在一個又一個的菜單中移動滑鼠,找到要更改的選項更加自然。
執行命令
bash的核心功能是執行系統命令。來看一個簡單的「Hello World」的例子。在bash shell中,echo命令可以在螢幕上顯示文本,例如:
$ echo "Hello World"
在命令行中輸入上面的內容,你就會看到Hello World出現在螢幕上。這行代碼執行了bash標準命令echo。bash用來搜尋標準命令的目錄被保存在環境變量PATH中。你可以使用echo查看PATH變量的內容,如代碼清單0-1所示。
代碼清單0-1 輸出當前環境變量PATH
$ echo $PATH
/Users/bperry/.rvm/gems/ruby-2.1.5/bin:/Users/bperry/.rvm/gems/ruby-2.1.5@
global/bin:/Users/bperry/.rvm/rubies/ruby-2.1.5/bin:/usr/local/bin:/usr/bin:/
bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/MacGPG2/bin:/Users/bperry/.rvm/bin
注意 如果代碼清單中既顯示了輸入命令,也顯示了輸出內容,輸入命令會以粗體顯示並以$作為起始,以此同輸出內容區分開來。
輸出中的各個目錄之間以冒號分隔。當需要運行程序或命令時,bash會檢查所有這些目錄。如果命令沒有在其中,bash就無法執行。另外要注意的是,bash是以這些目錄在PATH中出現的順序依次檢查的。順序在這里很重要,如果有兩個同名的命令分別位於PATH中兩個不同的目錄中,可能會由於目錄出現的先後順序產生不同的結果。如果在查找特定命令時碰到了麻煩,可以用which命令查看待查找命令在PATH中的位置,如代碼清單0-2所示。
代碼清單0-2 使用which在PATH中查找命令
$ which ruby
/Users/bperry/.rvm/rubies/ruby-2.1.5/bin/ruby
$ which echo
/bin/echo
知道了這些信息,你可以把需要測試的文件移動或復制到echo $PATH所列出的某個目錄中(如代碼清單0-1所示),然後就能執行命令了。本文中使用了which來確定命令的完整路徑。在調試有問題的PATH環境變量時,which是一個有用的工具。
配置登錄腳本
我們會編寫一些隨後用於其他腳本中的腳本,因此能夠輕鬆調用新腳本就顯得很重要了。你可以配置PATH環境變量,使得在啟動新的命令shell時,可以像其他命令那樣自動調用定制腳本。新命令shell啟動後的第一件事是讀取用戶主目錄(在OS X和Linux中分別是/Users/<username>和/home/<username>)中的登錄腳本並執行其中的定制命令。根據你所用的系統,登錄腳本可以是.login、.profile、.bashrc或.bash_profile。要想知道具體是哪個,像下面這樣在這些文件中各加入一行:
echo this is .profile
將最後一個單詞調整為對應的文件名,然後登錄。這行內容應該會出現在終端窗口頂端,指明登錄時運行的是哪個腳本。如果你打開終端,看到的是this is .profile,那麼說明你的shell環境載入的是.profile文件;如果看到的是this is .bashrc,則說明是.bashrc文件,以此類推。依賴於你所用的shell,這種行為也會有變化。
你可以修改登錄腳本,讓它幫助在PATH變量中加入其他目錄。另外也可以在登錄腳本中完成其他bash設置,例如修改bash提示符外觀、定制PATH等。讓我們用cat命令來看一個定制好的.bashrc登錄腳本。cat命令接受文件名作為參數,然後將文件內容輸出到控制台螢幕,如代碼清單0-3所示。
代碼清單0-3 定制過的.bashrc文件,其中將RVM加入了PATH
$ cat ~/.bashrc
export PATH="$PATH:$HOME/.rvm/bin" # 將RVM添加到PATH中。
該代碼顯示出了.bashrc文件的內容,可以看到PATH獲得了一個新的值,允許本地RVM (Ruby version manager,Ruby版本管理器)管理已安裝的各種Ruby版本。因為.bashrc在每次啟動新的命令shell時都會設置PATH,所以RVM在系統中總是默認可用。
你可以用類似的方法讓自己的shell腳本也默認可用。首先,在主目錄中創建一個開發目錄,將編寫的所有腳本都保存到這個目錄。然後在登錄腳本中將該目錄添加到PATH變量中,這樣就可以更方便地引用新寫的腳本了。
命令echo $HOME可以在終端中列印出主目錄的路徑。進入主目錄,然後創建開發目錄(建議將其命名為scripts)。接著用文本編輯器打開登錄腳本,在腳本頂端添加以下代碼(把/path/to/scripts/替換成開發目錄的具體路徑),將開發目錄加入PATH。
export PATH="/path/to/scripts/:$PATH"
在此之後,你保存到開發目錄中的任何腳本都可以像其他shell命令那樣調用了。
運行shell腳本
我們到目前為止已經用到了幾個命令,例如echo、which和cat,但是只是單獨使用,並沒有把它們放到shell腳本中。讓我們來寫一個可以連續執行這些命令的shell腳本,如代碼清單0-4所示。這個腳本先列印出Hello World,然後輸出shell腳本neqn的路徑,neqn應該位於PATH默認的目錄中。接著用該路徑將neqn的內容列印到螢幕上。(neqn的內容是什麼目前並不重要,這里只是作為一個例子而已。)這是利用shell腳本按順序執行命令序列的一個很好的例子,在這里我們查看了文件的完整系統路徑並快速檢查了文件內容。
代碼清單0-4 我們的第一個shell腳本
echo "Hello World"
echo $(which neqn)
cat $(which neqn)
打開你自己慣用的文本編輯器(Linux上的Vim或gedit、OS X上的TextEdit都是很流行的編輯器),輸入代碼清單0-4中的代碼。然後將shell腳本保存到開發目錄中並命名為intro。shell腳本對文件擴展名沒有特別要求,用不用都行(如果喜歡的話,可以用.sh作為擴展名,但不是必須的)。第一行代碼使用echo命令列印出文本Hello World。第二行代碼稍微複雜點,使用which命令找出neqn的位置,然後將其用echo命令在螢幕上列印出來。為了像這樣將一個命令作為另外一個命令的參數來運行兩個命令,bash使用子shell(subshell)來執行第二個命令並將其輸出作為第一個命令的參數。在上面的例子中,子shell執行的是which命令,該命令返回neqn腳本的完整路徑。這個路徑再被用作echo的參數,結果就是在螢幕上顯示出neqn的路徑。最後,還是用子shell將neqn的路徑傳給cat命令,在螢幕上列印出neqn腳本的內容。
保存好文件之後,就可以在終端運行腳本了。代碼清單0-5顯示了執行結果。
代碼清單0-5 運行我們的第一個shell腳本
$ sh intro
❶ Hello World
❷ /usr/bin/neqn
❸ #!/bin/sh
# Provision of this shell script should not be taken to imply that use of
# GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported.
GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:"
PATH="$GROFF_RUNTIME$PATH"
export PATH
exec eqn -Tascii ${1+"$@"}
# eof
$
運行這個腳本的時候,使用sh命令並將intro腳本作為參數。sh命令會依次執行腳本中的每行代碼,就好像這些命令是在終端上敲入的一樣。你可以看到先是列印出了Hello World❶,然後是neqn的路徑❷,最後輸出neqn的文件內容❸,也就是shell腳本neqn的源代碼(至少在OS X上是這樣的,Linux版本也許略有不同)。
讓shell腳本用起來更自然
不使用sh命令也可以執行腳本。如果在shell腳本intro中多加一行,然後修改腳本的權限,就可以像其他bash命令那樣直接調用腳本了。在文本編輯器中更新intro腳本:
❶ #!/bin/bash
echo "Hello World"
echo $(which neqn)
cat $(which neqn)
我們在腳本頂端加上了一行/bin/bash❶。這行叫作shebang。shebang允許你指定用哪個程序來解釋腳本。這里選擇將文件作為bash腳本。你可能還碰到過其他shebang,例如針對Perl(#!/usr/bin/perl)或Ruby(#!/usr/bin/env ruby)的。
shebang:這個詞其實是兩個字符名稱sharp-bang 的簡寫。在Unix 的行話里,用sharp 或hash(有時候是mesh)來稱呼字符「#」,用bang 來稱呼驚嘆號「!」,因而shebang 合起來就代表了這兩個字符。詳情請參考:http://en.wikipedia.org/wiki/ Shebang_(Unix)。
有了這行,還得設置文件權限才能像其他程序那樣直接運行shell腳本。在終端中的操作方法如代碼清單0-6所示。
代碼清單0-6 將腳本intro的權限修改為可執行
❶ $ chmod +x intro
❷ $ ./intro
Hello World
/usr/bin/neqn
#!/bin/sh
# Provision of this shell script should not be taken to imply that use of
# GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported.
GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:"
PATH="$GROFF_RUNTIME$PATH"
export PATH
exec eqn -Tascii ${1+"$@"}
# eof
$
我們用到了權限修改命令chmod❶並將+x作為命令參數,該參數可以將隨後指定的文件設置為可執行權限。權限設置好之後,不用調用bash就可以直接運行shell腳本❷。這是一種很好的shell腳本編程實踐,在你以後精進技藝的過程中就會發現它的作用了。
這只是一個簡單的例子,告訴你如何運行shell腳本,如何使用shell腳本運行其他的shell腳本。在今後編寫shell腳本的時候,你也會看到更多的shebang。
為什麼要用shell腳本
你也許疑惑為什麼偏要選擇bash shell腳本,而不去用那些漂亮的新語言,比如Ruby或Go。盡管這些語言都試圖在多種系統上做到可移植性,但它們通常並沒有被默認安裝。原因很簡單:所有Unix機器上都已經有了一個基本的shell,而且絕大多數用的都是bash shell。
文章開頭也提到過,微軟最近在Windows 10中也加入了多數Linux發行版和OS X中採用的bash shell。這意味著你的shell腳本幾乎不需要做什麼額外的工作,就擁有了比以往更好的可移植性。相較於其他語言,shell腳本能夠更準確、更輕鬆地完成系統維護及其他任務。
代碼清單0-7中展示了一個方便的微型shell腳本(沒錯,只有一行),完全可移植。該腳本可以統計出OpenOffice文檔目錄中的文檔共有多少頁,這對於作者特別有用。
代碼清單0-7 統計OpenOffice文檔目錄中文檔頁面數量的bash腳本
#!/bin/bash
echo "$(exiftool *.odt | grep Page-count | cut -d ":" -f2 | tr '\n' '+')""0" | bc
我們不會深究這個腳本的工作細節,畢竟才剛上路嘛!不過概括地講,腳本從各個文檔中提取出頁數信息,使用加號將頁數拼接在一起,然後通過管道將算式傳給命令行計算器,計算出最終的頁面總數。所有這一切全在這一行代碼中完成。還有更多像這樣的酷炫腳本,做過一些練習之後,這個腳本的含義就一目了然了!
開始動手吧
如果你之前沒有接觸過shell腳本編程,那麼現在對此應該有了基本的了解。創建小型腳本完成特定的任務是Unix哲學的核心。搞清楚如何編寫腳本並根據個人需要擴展Unix系統,這樣才能成長為一名高級用戶。本文只是道開胃菜,真正酷炫的shell腳本在這本書里面↓
shell腳本實戰(第2版)
- 101個shell經典實例,拿來即用
- 一冊搞定腳本編程技術
bash仍舊是類Unix服務器或工作站用戶的主要工具,這些用戶包括Web開發人員(很多人都是在OS X上做開發,然後部署到Linux服務器)、數據分析師、移動應用程序開發人員以及軟件工程師,等等。除此之外,大量的業餘愛好者也在自己的開源微型計算機(例如樹莓派)上運行著Linux,做到智能家庭的自動化。shell腳本是這類用途的不二之選。
無論是對於在bash技藝上追求精益求精的老手,還是那些偶爾用一下終端或shell腳本的用戶,書中所展現的腳本都大有裨益。後一陣營中的用戶可能希望溫習一些技巧或是學點bash的高級概念,給自己再充充電。
本書並非教程!其目標是通過(基本上)簡短緊湊的腳本教會你bash腳本編程的實用技術以及常見工具的用法,但不會去逐行解釋腳本。本書只講解每個腳本的核心內容,有經驗的shell腳本用戶通過閱讀代碼就能明白其餘的部分。你大可放開手腳,把腳本拆解開來,根據自己的需要修改,借此達到融會貫通的目的。書中的腳本旨在解決那些三天兩頭就會碰上的麻煩事,比如Web管理或是文件同步,不管用的是什麼工具,每個技術專家都得應付這類問題。
本書重點關注編寫可移植的自動化腳本(例如構建軟件或提供業務流程)時經常面對的那些難題,為此我們的做法是先做到一些常見任務的自動化。但如果想從本書中獲得最大收益,就要將已形成的解決方案推廣應用到其他類似的問題上。
盡管很多系統管理員都能受益於這個腳本,但其中真正的價值在於,創建包裝器腳本這種一般性的解決方案能夠確保跨平台行為的一致性。書中隨後的章節將深入研究bash腳本編程中一些酷炫的特性以及Unix系統常見的實用工具,為你傳授各種絕招。
目錄
第 1 章 遺失的代碼庫
第 2 章 改進用戶命令
第 3 章 創建實用工具
第 4 章 Unix調校
第 5 章 系統管理:用戶管理
第 6 章 系統管理:系統維護
第 7 章 Web與Internet用戶
第 8 章 網站管理員絕招
第 9 章 Web與Internet管理
第 10 章 Internet服務器管理
第 11 章 OS X腳本
第 12 章 shell腳本趣用與遊戲
第 13 章 與雲共舞
第 14 章 ImageMagick及圖像處理
第 15 章 天數與日期
附錄 A 在Windows 10中安裝bash
附錄 B 免費福利
‘,