系列文章首發平臺爲果凍想個人博客。果凍想,是一個原創技術文章分享網站。在這裏果凍會分享他的技術心得,技術得失,技術人生。我在果凍想等待你,也希望你能和我分享你的技術得與失,期待。
神奇的Linux
Linux有多神奇,我就不說了。作爲在一個IT界混的碼農,或多或少的都會接觸Linux,不管你是怎麼接觸Linux,對於其中的一些命令肯定也會或多或少的瞭解一些。Linux雖然不是你的必須裝備,但是裝備上了Linux,對於你的職業生涯肯定是有百利而無一害的。基於此,我接觸了Linux,然後就學習了一下Shell腳本,以此文章,作爲Shell腳本學習中的一個筆記,以備後期翻閱。
Shell能幹什麼
學習一項知識,憑的是興趣,這是一個理由;另一個理由則是爲了工作。好吧,我高尚一回,我覺對不是爲了生計而去學習Shell腳本,純粹是爲了興趣,彌補我大學時期沒有學習Shell腳本而留下來的遺憾。而更多的人學習Shell並不是和我一樣,更多的是爲了工作,解決工作中的實際問題而決定要看看Shell腳本的,那麼Shell腳本在工作中到底能幹什麼呢?先看看以下問題:
- 你是否經常需要處理一些重複性的工作?
- 你是否經常發現使用高級語言,如C++去處理一些事情發現大材小用?
- 你工作中是否經常需要進行一些批量處理的任務?
- ……
如果對於上面的問題,如果你回答“YES”。那你就非常有必要去學習一下Linux Shell腳本。好了,想必你已經決定了去看看Shell了。來吧。
一段簡單的Shell
首先來一段簡單的Shell腳本,整體上對Shell有一個簡單的認識。
#!/bin/bash
cd ~ # 切換到用戶的主目錄,也就是/home/xxx
mkdir shell_test # 創建一個名爲shell_test的文件夾
for ((i=0; i<10; i++)) # Shell的for循環
do
touch test_$i.txt #創建空文件,文件名爲test_0.txt, test_1.txt ...
done
上面是一段簡單的Shell程序,實現在功能爲:
- 在用戶主目錄下創建一個shell_test文件夾;
- 在用戶主目錄下創建10個規定命名格式的空文件。
由於是第一個Shell程序,所以有必要逐行解釋一下,留下一個好的印象。
- 第一行:指定腳本解釋器,這裏是用/bin/bash做解釋器;
- 第二行:切換到當前用戶的home目錄;
- 第三行:創建一個名爲shell_test的空目錄;
- 第四、五行:for循環,一共循環10次;
- 第六行:創建10個名稱爲test_0.txt, test_1.txt格式的空文件。
cd
、mkdir
和touch
都是系統自帶的程序,一般在/bin
目錄下;而for
、do
和done
都是Shell的關鍵字。
Shell中使用#
開頭的行就是註釋。
一個簡單的程序之後,我們就開始進入Shell的語法學習吧。
變量
從變量開始學習這段旅程。
基礎知識
1. 在Shell中,使用變量之前不需要事先聲明,只是通過使用它們來創建它們;
2. 在默認情況下,所有變量都被看做是字符串,並以字符串來存儲;
3. Shell變量是區分大小寫的;
4. 在Shell中,通過在變量名前加一個$
符號來訪問它的內容,無論何時想要獲取變量內容,都必須在它前面加一個$
符號;
下面通過一段Shell腳本來詳細的說明上面的內容:
#!/bin/bash
var1=10 #定義一個變量var1
#定義一個變量var2,對於字符串中有空格的情況,需要使用引號將字符串括起來,
#此外等號兩邊不能有空格
var2="Jelly Think"
echo $var1 #獲取變量var1的值
echo $var2 #獲取變量var2的值
使用read
在Shell中,我們可以使用read
命令將用戶的輸入賦值給一個變量。這個命令需要一個參數,即準備讀入用戶輸入數據的變量名,然後它會等待用戶輸入數據。通常情況下,在用戶按下回車鍵時,read命令結束。例如以下代碼:
#!/bin/bash
read var1 #讀入用戶輸入的var1變量的值
echo $var1 #輸出var1的值
exit 0
引號的使用技巧
在上面的代碼中也說了,如果字符串中包含了空格,就需要使用引號將字符串括起來,而這只是引號的一個簡單的使用。
像$var1這種的變量在引號中的行爲取決於你所使用的引號類型。這句話有以下兩層意思:
- 如果把一個$變量表達式放在雙引號中,程序執行到這一行時就會把變量替換爲它的值;
- 如果把一個$變量表達式放在單引號中,就不會發生替換現象;
但是,我們可以通過在$字符前面加上一個\
字符取消它的特殊含義,也就是經常說的轉義字符。下面通過一段簡單的代碼來理解上面內容的意思:
#!/bin/bash
var1="JellyThink"
echo "This is $var1" #輸出:This is JellyThink
echo 'This is $var1' #輸出:This is $var1
echo "This is \$var1" #輸出:This is $var1
exit 0
環境變量
當一個Shell腳本程序開始執行時,一些變量根據環境設置中的值進行初始化,一般比較常用的有以下幾個:
- $HOME:當前用戶的主目錄,例如:
/home/jelly
; - $PATH:以冒號分隔的用來搜索命令的目錄列表;
- $0:Shell腳本的名字;
- $#:傳遞給腳本的參數個數;
- $$:Shell腳本的進程號,腳本程序通常會用它來生成一個唯一的臨時文件。
參數變量
如果腳本程序在調用時帶有參數,一些額外的變量就會被創建。即使沒有傳遞任何參數,環境變量$#也依然存在,只不過它的值是0罷了。例如以下實力代碼:
#!/bin/bash
#參數的命名是$1、$2......
#$0表示腳本的名字
#輸出參數個數
echo "參數個數爲:$#"
echo "腳本名字:$0"
echo "第一個參數:$1"
echo "第二個參數:$2"
exit 0
輸出如下:
參數個數爲:2
腳本名字:./argsDemo.sh
第一個參數:Jelly
第二個參數:Think
條件判斷
我們知道,所有程序設計語言的基礎是對條件進行測試判斷,並根據測試結果採取不同行動的能力。在總結之前,先看看Shell腳本程序裏可以使用的條件結構,然後再來看看使用這些條件的控制結構。
一個Shell腳本能夠對任何可以從命令行上調用的命令的退出碼進行判斷,這其中也包括我們寫的Shell腳本程序。這就是爲什麼要在所有自己編寫的腳本程序的結尾包括一條返回值的exit
命令的重要原因。
test或[命令
在實際工作中,大多數腳本程序都會廣泛使用Shell的布爾判斷命令[
或test
。在一些系統上,這兩個命令其實是一樣的,只是爲了增強可讀性,當使用[
命令時,我們還使用符號]
來結尾。寫了這麼多程序了,還是頭一次見,將一個[
作爲一個命令。算我孤陋寡聞了。下面就通過一個簡單的例子來看看test
和[
是如何使用的。
#!/bin/bash
if test -f testDemo.sh
then
echo "testDemo.sh exists."
fi
if [ -f test.c ]
then
echo "test.c exists."
else
echo "I cannot find exists."
fi
exit 0
我們通過使用-f
來判斷指定文件是否存在。test
命令可以使用的條件類型可以歸爲3類:
- 字符串比較;
- 算術比較;
- 文件相關的條件測試。
下面就分別看看。
字符串比較 | 結果 |
---|---|
string1 = string2 | 如果兩個字符串相同,結果就爲真 |
string1 != string2 | 如果兩個字符串不同,結果就爲真 |
-n string | 如果字符串不爲空,則結果爲真 |
-z string | 如果字符串爲一個空串(null),則結果爲真 |
算術比較 | 結果 |
---|---|
expression1 -eq expression2 | 如果兩個表達式相等,則結果爲真 |
expression1 -ne expression2 | 如果兩個表達式不等,則結果爲真 |
expression1 -gt expression2 | 如果expression1大於expression2,則爲真 |
expression1 -ge expression2 | 如果expression1大於等於expression2,則爲真 |
expression1 -lt expression2 | 如果expression1小於expression2,則爲真 |
expression1 -le expression2 | 如果expression1小於等於expression2,則爲真 |
!expression | 表達式爲假,則結果就爲真;反之亦然 |
文件條件測試 | 結果 |
---|---|
-d file | 如果文件是一個目錄,則爲真 |
-f file | 如果文件是一個普通文件,則爲真;也可以用來測試文件是否存在 |
-r file | 如果文件可讀,則結果爲真 |
-s file | 如果文件大小不爲0,則結果爲真 |
-w file | 如果文件可寫,則結果爲真 |
-x file | 如果文件可執行,則結果爲真 |
控制結構
Shell也有一組控制結構,它們與其它程序語言中的控制接口很相似,下面就分開來總結它們。
if語句
結構如下:
if condition
then
statements
else
statements
fi
代碼示例:
#!/bin/bash
var1=Jelly #這個是賦值,不能有空格分隔
var2=Think
if [ $var1 = $var2 ] #‘=’號兩邊都需要空格分隔
then
echo "$var1 = $var2"
else
echo "$var1 != $var2"
fi
exit 0
elif語句
結構如下
if condition
then
statements
elif condition
statements
else
statements
fi
一個很容易出錯的問題。例如以下代碼:
#!/bin/bash
echo "Is it morning? please answer yes or no."
read timeofday
if [ $timeofday = "yes" ]
then
echo "Good morning."
elif [ $timeofday = "no" ]
then
echo "Good afternoon."
else
echo "Sorry, $timeofday not recognized. Enter yes or no."
exit 1
fi
exit 0
上面這段代碼很簡單,運行以下,如果你不輸入yes
或no
,就會運行出錯,得到以下提示信息:
./elifDemo1.sh: line 6: [: =: unary operator expected
./elifDemo1.sh: line 9: [: =: unary operator expected
這是爲何?代碼中有if [ $timeofday = "yes" ]
,當我不輸入任何內容時,這個if
語句就會變成這樣if [ = "yes" ]
,很明顯,這不是一個合法的條件。爲了避免出現這種情況,我們必須給變量加上引號,改成這樣if [ "$timeofday" = "yes" ]
。這樣就沒有問題了。
for語句
結構如下:
for variable in values
do
statements
done
代碼示例:
#!/bin/bash
for foo in Jelly Think Website
do
echo $foo
done
exit 0
在文章的開頭,那段簡單的代碼中,也包含了for
的簡單使用。
while語句
結構如下:
while condition
do
statements
done
代碼示例:
#!/bin/bash
echo "Enter password: "
read pwd
while [ "$pwd" != "root" ]
do
echo "Sorry, try again."
read pwd
done
exit 0
until語句
結構如下:
until condition
do
statements
done
它與while
循環很相似,只是把條件測試反過來了;也就是說,循環將反覆執行直到條件爲真。
case語句
結構如下:
case variable in
pattern [ | pattern] ...) statements;;
pattern [ | pattern] ...) statements;;
...
esac
case
的代碼結構相對來說是比較複雜的。case
結構具備匹配多個模式,然後執行多條相關語句的能力,這使得它非常適合於處理用戶的輸入。下面通過一個實例來看看case
的具體使用。
#!/bin/bash
echo "Is it morning? Please answer yes or no."
read input
case "$input" in
yes ) echo "Good Morning.";;
no ) echo "Good Afternoon.";;
y ) echo "Good Morning.";;
n ) echo "Good Afternoon.";;
* ) echo "Sorry, answer not recognized";;
esac
exit 0
當case
語句被執行時,它會把變量input
的內容與各字符串依次進行比較。一旦某個字符串與輸入匹配成功,case
命令就會執行緊隨右括號)
後面的代碼,然後就結束。
在代碼中,最後面的*
表示匹配任何字符串,我們在寫代碼時,總是在其它匹配之後再添加一個*
以確保如果沒有字符串得到匹配,case
語句也會執行某個默認動作。
由於case
比較複雜,所以不得不再來一段代碼,究其用法,如下:
#!/bin/bash
echo "Is it morning? Please answer yes or no."
read input
case "$input" in
yes | y | Yes | YES ) echo "Good Morning.";;
n* | N* ) echo "Good Afternoon.";;
* ) echo "Sorry, answer not recognized.";;
esac
exit 0
上面這段代碼,使用了|
符號,也就是說,也就是合併了多個匹配模式;同時還使用了*
通配符;沒有問題,上面的代碼運行的很好。
&&和||操作符
Shell中也支持&&
和||
符號,和C++語言中的是一樣;比如:
statement1 && statement2 && statement3 && ...
從左開始順序執行每條命令,如果一條命令返回的是true
,它右邊的下一條命令才能執行。如果此持續直到有一條命令返回false
,或者列表中的所有命令都執行完畢;遵循“短路”規則。
statement1 || statement2 || statement3 || ...
從左開始順序執行每條命令,如果一條命令返回的是false
,它右邊的下一條命令才能夠被執行。如此持續直到有一條命令返回true
,或者列表中的所有命令都執行完畢。
語句塊
在Shell中也有語句塊,使用{}
來定義一個語句塊。我們可以把多條語句放到一個語句塊中執行。
函數
函數,這麼NB的東西,Shell怎麼能少呢。定義函數的結構如下:
function_name()
{
statements
}
代碼示例:
#!/bin/bash
foo #運行到這裏,還沒有定義foo函數,會報錯
foo() {
echo "Function foo is executing."
}
echo "script starting"
foo #輸出"Function foo is executing."
echo "script ended"
exit 0
腳本程序從自己的頂部開始執行,當它遇到了foo() {
結構時,它知道腳本正在定義一個名爲foo
的函數。當執行到單獨的foo
時,Shell就知道應該去執行剛纔定義的函數了。函數執行完畢以後,腳本接着foo
後的代碼繼續執行。
當一個函數被調用時,腳本程序的位置參數($*
、$@
、$#
、$1
、$2
等)會被替換爲函數的參數,這也是我們讀取傳遞給函數的參數的方法。當函數執行完畢後,這些參數會恢復爲它們先前的值。
我們可以使用local
關鍵字在Shell函數中聲明局部變量,局部變量將僅在函數的作用範圍內有效。此外,函數可以訪問全局作用範圍內的其它Shell變量。如果一個局部變量和一個全局變量的名字相同,前者就會覆蓋後者,但僅限於函數的作用範圍之內,例如以下代碼:
#!/bin/bash
sample_text="global variable"
foo()
{
local sample_text="local variable"
echo "Function foo is executing..."
echo $sample_text
}
echo "script starting..."
echo $sample_text
foo
echo "script ended..."
echo $sample_text
exit 0
輸出如下:
script starting...
global variable
Function foo is executing...
local variable
script ended...
global variable
總結
終於總結完第一部分了,還有第二部分,在下一篇博文中將主要總結Shell中經常使用的一些命令。這篇對Shell中的一些基礎進行總結的博文就到此了,下一篇再見。
果凍想-一個原創技術文章分享網站。
2014年10月28日 於深圳。
如果你覺的文章還不錯,可以關注果凍想微信公衆號,定期推送技術文章: