Linux中國

shell 腳本之始

世界上對 shell 腳本最好的概念性介紹來自一個老的 AT&T 培訓視頻 。在視頻中,Brian W. Kernighan(awk 中的「k」),Lorinda L. Cherry(bc 作者之一)論證了 UNIX 的基礎原則之一是讓用戶利用現有的實用程序來定製和創建複雜的工具。

Kernighan 的話來說:「UNIX 系統程序基本上是 …… 你可以用來創造東西的構件。…… 管道的概念是 [UNIX] 系統的基礎;你可以拿一堆程序 …… 並將它們端到端連接到一起,使數據從左邊的一個流到右邊的一個,由系統本身管著所有的連接。程序本身不知道任何關於連接的事情;對它們而言,它們只是在與終端對話。」

他說的是給普通用戶以編程的能力。

POSIX 操作系統本身就像是一個 API。如果你能弄清楚如何在 POSIX 的 shell 中完成一個任務,那麼你可以自動化這個任務。這就是編程,這種日常 POSIX 編程方法的主要方式就是 shell 腳本

像它的名字那樣,shell 腳本就是一行一行你想讓你的計算機執行的語句,就像你手動的一樣。

因為 shell 腳本包含常見的日常命令,所以熟悉 UNIX 或 Linux(通常稱為 POSIX 系統)對 shell 是有幫助的。你使用 shell 的經驗越多,就越容易編寫新的腳本。這就像學習外語:你心裡的辭彙越多,組織複雜的句子就越容易。

當您打開終端窗口時,就是打開了 shell 。shell 有好幾種,本教程適用於 bashtcshkshzsh 和其它幾個。在下面幾個部分,我提供一些 bash 特定的例子,但最終的腳本不會用那些,所以你可以切換到 bash 中學習設置變數的課程,或做一些簡單的語法調整

如果你是新手,只需使用 bash 。它是一個很好的 shell,有許多友好的功能,它是 Linux、Cygwin、WSL、Mac 默認的 shell,並且在 BSD 上也支持。

Hello world

您可以從終端窗口生成您自己的 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.

這些錯誤是讓人迷惑的,因為它們是針對 lsmv 發出的,但就用戶所知,它運行的不是 lsmv,而是 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 腳本,接下來的生活將會輕鬆。

Happy hacking!

作者簡介:

Seth Kenlon 是一位獨立的多媒體藝術家,自由文化倡導者和 UNIX 極客。他是基於 Slackware 的多媒體製作項目(http://slackermedia.ml)的維護者之一

via: https://opensource.com/article/17/1/getting-started-shell-scripting

作者:Seth Kenlon 譯者:hkurj 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國