導讀 | 世界上對 shell 腳本最好的概念性介紹來自一個老的 AT&T 培訓視頻 。在視頻中,Brian W. Kernighan(awk 中的「k」),Lorinda L. Cherry(bc 做者之一)論證了 UNIX 的基礎原則之一是讓用戶利用現有的實用程序來定製和建立複雜的工具。 |
世界上對 shell 腳本最好的概念性介紹來自一個老的 AT&T 培訓視頻 。在視頻中,Brian W. Kernighan(awk 中的「k」),Lorinda L. Cherry(bc 做者之一)論證了 UNIX 的基礎原則之一是讓用戶利用現有的實用程序來定製和建立複雜的工具。html
用 Kernighan 的話來講:「UNIX 系統程序基本上是 …… 你能夠用來創造東西的構件。…… 管道的概念是 [UNIX] 系統的基礎;你能夠拿一堆程序 …… 並將它們端到端鏈接到一塊兒,使數據從左邊的一個流到右邊的一個,由系統自己管着全部的鏈接。程序自己不知道任何關於鏈接的事情;對它們而言,它們只是在與終端對話。」linux
他說的是給普通用戶以編程的能力。git
POSIX 操做系統自己就像是一個 API。若是你能弄清楚如何在 POSIX 的 shell 中完成一個任務,那麼你能夠自動化這個任務。這就是編程,這種平常 POSIX 編程方法的主要方式就是 shell 腳本。程序員
像它的名字那樣,shell 腳本就是一行一行你想讓你的計算機執行的語句,就像你手動的同樣。github
由於 shell 腳本包含常見的平常命令,因此熟悉 UNIX 或 Linux(一般稱爲 POSIX 系統)對 shell 是有幫助的。你使用 shell 的經驗越多,就越容易編寫新的腳本。這就像學習外語:你內心的詞彙越多,組織複雜的句子就越容易。shell
當您打開終端窗口時,就是打開了 shell 。shell 有好幾種,本教程適用於 bash、tcsh、ksh、zsh 和其它幾個。在下面幾個部分,我提供一些 bash 特定的例子,但最終的腳本不會用那些,因此你能夠切換到 bash 中學習設置變量的課程,或作一些簡單的語法調整。編程
若是你是新手,只需使用 bash 。它是一個很好的 shell,有許多友好的功能,它是 Linux、Cygwin、WSL、Mac 默認的 shell,而且在 BSD 上也支持。bash
Hello worldide
您能夠從終端窗口生成您本身的 hello world 腳本 。注意你的引號;單和雙都會有不一樣的效果(LCTT 譯註:想必你不會在這裏使用中文引號吧)。工具
$ echo "#/!/bin/sh" > hello.sh $ echo "echo 'hello world' " >> hello.sh
正如你所看到的,編寫 shell 腳本就是這樣,除了第一行以外,就是把命令「回顯」或粘貼到文本文件中而已。
像應用程序同樣運行腳本:
$ chmod +x hello.sh $ ./hello.sh hello world
無論多少,這就是一個 shell 腳本了。
如今讓咱們處理一些有用的東西。
去除空格
若是有一件事情會干擾計算機和人類的交互,那就是文件名中的空格。您在互聯網上看到過:http://example.com/omg%2ccutest%20cat%20photophoto%21%211.jpg 等網址。或者,當你無論不顧地運行一個簡單的命令時,文件名中的空格會讓你掉到坑裏:
$ cp llama pic.jpg ~/photos cp: cannot stat 'llama': No such file or directory cp: cannot stat 'pic.jpg': No such file or directory
解決方案是用反斜槓來「轉義」空格,或使用引號:
$ touch foo/ bar.txt $ ls "foo bar.txt" foo bar.txt
這些都是要知道的重要的技巧,可是它並不方便,爲何不寫一個腳本從文件名中刪除這些煩人的空格?
建立一個文件來保存腳本,以釋伴shebang(#!) 開頭,讓系統知道文件應該在 shell 中運行:
$ echo '#!/bin/sh' > despace
好的代碼要從文檔開始。定義好目的讓咱們知道要作什麼。這裏有一個很好的 README:
despace is a shell script for removing spaces from file names. Usage: $ despace "foo bar.txt"
如今讓咱們弄明白如何手動作,而且如何去構建腳本。
假設你有個只有一個 foo bar.txt 文件的目錄,好比:
$ ls hello.sh foo bar.txt
計算機無非就是輸入和輸出而已。在這種狀況下,輸入是 ls特定目錄的請求。輸出是您所指望的結果:該目錄文件的名稱。
在 UNIX 中,能夠經過「管道」將輸出做爲另外一個命令的輸入,不管在管道的另外一側是什麼過濾器。 tr程序剛好設計爲專門修改傳輸給它的字符串;對於這個例子,可使用 --delete選項刪除引號中定義的字符。
$ ls "foo bar.txt" | tr --delete ' ' foobar.txt
如今你獲得了所需的輸出了。
在 Bash shell 中,您能夠將輸出存儲爲變量 。變量能夠視爲將信息存儲到其中的空位:
$ NAME=foo
當您須要返回信息時,能夠經過在變量名稱前面綴上美圓符號($)來引用該位置。
$ echo $NAME foo
要得到您的這個去除空格後的輸出並將其放在一邊供之後使用,請使用一個變量。將命令的結果放入變量,使用反引號(`)來完成:
$ NAME=`ls "foo bar.txt" | tr -d ' '` $ echo $NAME foobar.txt
咱們完成了一半的目標,如今能夠從源文件名肯定目標文件名了。
到目前爲止,腳本看起來像這樣:
#!/bin/sh NAME=`ls "foo bar.txt" | tr -d ' '` echo $NAME
第二部分必須執行重命名操做。如今你可能已經知道這個命令:
$ mv "foo bar.txt" foobar.txt
可是,請記住在腳本中,您正在使用一個變量來保存目標名稱。你已經知道如何引用變量:
#!/bin/sh NAME=`ls "foo bar.txt" | tr -d ' '` echo $NAME mv "foo bar.txt" $NAME
您能夠將其標記爲可執行文件並在測試目錄中運行它。確保您有一個名爲 foo bar.txt(或您在腳本中使用的其它名字)的測試文件。
$ touch "foo bar.txt" $ chmod +x despace $ ./despace foobar.txt $ ls foobar.txt
去除空格 v2.0
腳本能夠正常工做,但不徹底如您的文檔所述。它目前很是具體,只適用於一個名爲 foo/ bar.txt 的文件,其它都不適用。
POSIX 命令會將其命令自身稱爲$0,並將其後鍵入的任何內容依次命名爲 $1,$2,$3等。您的 shell 腳本做爲 POSIX 命令也能夠這樣計數,所以請嘗試用 $1 來替換 foo/ bar.txt 。
#!/bin/sh NAME=`ls $1 | tr -d ' '` echo $NAME mv $1 $NAME
建立幾個新的測試文件,在名稱中包含空格:
$ touch "one two.txt" $ touch "cat dog.txt"
而後測試你的新腳本:
$ ./despace "one two.txt" ls: cannot access 'one': No such file or directory ls: cannot access 'two.txt': No such file or directory
看起來您發現了一個 bug!
這實際上不是一個 bug,一切都按設計工做,但不是你想要的。你的腳本將 $1 變量真真切切地 「擴展」 成了:「one two.txt」,搗亂的就是你試圖消除的那個麻煩的空格。
解決辦法是將變量用以引號封裝文件名的方式封裝變量:
#!/bin/sh NAME=`ls "$1" | tr -d ' '` echo $NAME mv "$1" $NAME
再作個測試:
$ ./despace "one two.txt" onetwo.txt $ ./despace c*g.txt catdog.txt
此腳本的行爲與任何其它 POSIX 命令相同。您能夠將其與其餘命令結合使用,就像您但願的使用的任何 POSIX 程序同樣。您能夠將其與命令結合使用:
$ find ~/test0 -type f -exec /path/to/despace {} /;
或者你可使用它做爲循環的一部分:
$ for FILE in ~/test1/* ; do /path/to/despace $FILE ; done
等等。
去除空格 v2.5
這個去除腳本已經能夠發揮功用了,但在技術上它能夠優化,它能夠作一些可用性改進。
首先,變量實際上並不須要。 shell 能夠一次計算所需的信息。
POSIX shell 有一個操做順序。在數學中使用一樣的方式來首先處理括號中的語句,shell 在執行命令以前會先解析反引號 ` 或 Bash 中的 $() 。所以,下列語句:
$ mv foo/ bar.txt `ls foo/ bar.txt | tr -d ' '`
會變換成:
$ mv foo/ bar.txt foobar.txt
而後實際的 mv 命令執行,就獲得了 foobar.txt 文件。
知道這一點,你能夠將該 shell 腳本壓縮成:
#!/bin/sh mv "$1" `ls "$1" | tr -d ' '`
這看起來簡單的使人失望。你可能認爲它使腳本減小爲一個單行並無必要,但沒有幾行的 shell 腳本是有意義的。即便一個用簡單的命令寫的緊縮的腳本仍然能夠防止你發生致命的打字錯誤,這在涉及移動文件時尤爲重要。
此外,你的腳本仍然能夠改進。更多的測試發現了一些弱點。例如,運行沒有參數的 despace 會產生一個沒有意義的錯誤:
$ ./despace ls: cannot access '': No such file or directory mv: missing destination file operand after '' Try 'mv --help' for more information.
這些錯誤是讓人迷惑的,由於它們是針對 ls 和 mv 發出的,但就用戶所知,它運行的不是 ls 或 mv,而是despace。
若是你想想,若是它沒有獲得一個文件做爲命令的一部分,這個小腳本甚至不該該嘗試去重命名文件,請嘗試使用你知道的變量以及 test 功能來解決。
if 和 test
if 語句將把你的小 despace 實用程序從腳本蛻變成程序。這裏面涉及到代碼領域,但不要擔憂,它也很容易理解和使用。
if 語句是一種開關;若是某件事情是真的,那麼你會作一件事,若是它是假的,你會作不一樣的事情。這個 if-then 指令的二分決策正好是計算機是擅長的;你須要作的就是爲計算機定義什麼是真或假以及並最終執行什麼。
測試真或假的最簡單的方法是 test 實用程序。你不用直接調用它,使用它的語法便可。在終端試試:
$ if [ 1 == 1 ]; then echo "yes, true, affirmative"; fi yes, true, affirmative $ if [ 1 == 123 ]; then echo "yes, true, affirmative"; fi $
這就是test 的工做方式。你有各類方式的簡寫可供選擇,這裏使用的是 -z 選項,它檢測字符串的長度是否爲零(0)。將這個想法翻譯到你的 despace 腳本中就是:
#!/bin/sh if [ -z "$1" ]; then echo "Provide a /"file name/", using quotes to nullify the space." exit 1 fi mv "$1" `ls "$1" | tr -d ' '`
爲了提升可讀性,if 語句被放到單獨的行,可是其概念仍然是:若是 $1 變量中的數據爲空(零個字符存在),則打印一個錯誤語句。
嘗試一下:
$ ./despace Provide a "file name", using quotes to nullify the space. $
成功!
好吧,其實這是一個失敗,但它是一個漂亮的失敗,更重要的是,一個有意義的失敗。
注意語句 exit 1 。這是 POSIX 應用程序遇到錯誤時向系統發送警報的一種方法。這個功能對於須要在腳本中使用 despace ,並依賴於它成功執行才能順利運行的你或其它人來講很重要。
最後的改進是添加一些東西,以保護用戶不會意外覆蓋文件。理想狀況下,您能夠將此選項傳遞給腳本,因此它是可選的;但爲了簡單起見,這裏對其進行了硬編碼。 -i 選項告訴 mv 在覆蓋已存在的文件以前請求許可:
#!/bin/sh if [ -z "$1" ]; then echo "Provide a /"file name/", using quotes to nullify the space." exit 1 fi mv -i "$1" `ls "$1" | tr -d ' '`
如今你的 shell 腳本是有意義的、有用的、友好的 - 你是一個程序員了,因此不要停。學習新命令,在終端中使用它們,記下您的操做,而後編寫腳本。最終,你會把本身從工做中解脫出來,當你的機器僕人運行 shell 腳本,接下來的生活將會輕鬆。
做者簡介:
Seth Kenlon 是一位獨立的多媒體藝術家,自由文化倡導者和 UNIX 極客。他是基於 Slackware 的多媒體制做項目(http://slackermedia.ml)的維護者之一
via: https://opensource.com/article/17/1/getting-started-shell-scripting
做者:Seth Kenlon 譯者:hkurj 校對:wxy