一、專題背景
最近使用了個自動化平臺(詳見自動化運維平臺Spug測試)進行每週的變更,效果很不錯,平臺將大量重複繁瑣的操作通過腳本分發方式標準化自動化了,平臺核心是下發到各個服務器的shell腳本,感覺有必要對shell腳本做個總結,所以有了寫本專題的想法。本專題將結合運維實際介紹shell腳本的各項用法,預計10篇左右,將包括系統巡檢、監控、ftp上傳下載、數據庫查詢、日誌清理、時鐘同步、定時任務等,裏面會涉及shell常用語法、注意事項、調試排錯等。
二、本文前言
本文是該專題的第一篇。
做運維的都寫過腳本,腳本的第一行#!/bin/bash
大家都很熟悉,今天就具體講講這個第一行:
- 爲什麼用使用#!/bin/bash
- 首行不寫或者寫得有誤有什麼影響
在具體測試前先介紹下shell和bash
三、什麼是shell
shell是一種特殊的交互式工具。它爲用戶提供了啓動程序、管理文件系統中的文件以及運行在Linux系統上的進程的途徑。shell的核心是命令行提示符。命令行提示符是shell負責交互的部分。它允許你輸入文本命令,然後解釋命令,並在內核中執行。
當一個用戶登錄Linux系統之後,系統初始化程序init就爲每一個用戶運行一個稱爲shell(外殼)的程序。shell就是一個命令行解釋器,它爲用戶提供了一個向Linux內核發送請求以便運行程序的界面系統級程序,用戶可以用shell來啓動、掛起、停止甚至是編寫一些程序。
四、什麼是bash
在Linux系統上,通常有好幾種Linux shell可用。不同的shell有不同的特性,有些更利於創建腳本,有些則更利於管理進程。所有Linux發行版默認的交互shell都是bash shell。bash shell由GNU項目開發,被當作標準Unix shell——Bourne shell(以創建者的名字命名)的替代品。bash shell的名稱就是針對Bourne shell的拼寫所玩的一個文字遊戲,稱爲Bourne again shell。
除了bash shell,還有dash shell、zsh shell、tcsh、ash等。
bash和dash的區別(後面的測試基於二者的區別):dash shell只是Bourne shell功能的一個子集, bash shell腳本中的有些功能沒法在dash shell中使用,如在腳本中dash無法使用雙等號(==)來測試兩個字符串是否相等,可以利用這個特性區分bash和dash。
五、不寫第一行可以不
爲了說清楚這個問題,先看兩個例子
示例一:
root@ubuntu1604:~# more first.sh
echo "Hello World"
root@ubuntu1604:~# ll|grep first.sh
-rw-r--r-- 1 root root 19 Jan 8 14:19 first.sh
root@ubuntu1604:~# sh first.sh
Hello World
root@ubuntu1604:~# ./first.sh
-bash: ./first.sh: Permission denied
root@ubuntu1604:~# chmod u+x first.sh
root@ubuntu1604:~# ./first.sh
Hello World
腳本名 | 首行 | 執行方式 | 執行結果 |
---|---|---|---|
first.sh | 空 | ./ | 成功 |
腳本first.sh只有一行:echo "Hello World",腳本執行成功,那是不是意味着首行聲明可以不需要寫呢,或者說首行的聲明會給腳本執行造成什麼影響?我們再看個示例。
示例二:
root@ubuntu1604:~# more dash1.sh
test1=abcdef
test2=abcdef
if [ $test1 == $test2 ]
then
echo "They're the same!"
else
echo "They're different"
fi
root@ubuntu1604:~# ./dash1.sh
They're the same!
root@ubuntu1604:~# more dash2.sh
#!/bin/bash
test1=abcdef
test2=abcdef
if [ $test1 == $test2 ]
then
echo "They're the same!"
else
echo "They're different"
fi
root@ubuntu1604:~# ./dash2.sh
They're the same!
root@ubuntu1604:~# more dash3.sh
#!/bin/dash
test1=abcdef
test2=abcdef
if [ $test1 == $test2 ]
then
echo "They're the same!"
else
echo "They're different"
fi
root@ubuntu1604:~# ./dash3.sh
./dash3.sh: 4: [: abcdef: unexpected operator
They're different
root@ubuntu1604:~# more dash4.sh
#!/bin/sh
test1=abcdef
test2=abcdef
if [ $test1 == $test2 ]
then
echo "They're the same!"
else
echo "They're different"
fi
root@ubuntu1604:~# ./dash4.sh
./dash4.sh: 4: [: abcdef: unexpected operator
They're different
腳本dash1.sh、dash2.sh、dash3.sh和dash4.sh的執行方式相同,內容也相同(除了首行聲明外),結果執行結果卻大不相同,dash1.sh、dash2.sh執行正常,dash3.sh和dash4.sh執行報錯。這個至少證明首行的聲明還是起作用的。
到這裏大家肯定雲裏霧裏了,即使之前對shell腳本很清楚的童鞋估計現在也被我繞暈了,這就對了,因爲我就是這麼過來了……
說回正題,我們先梳理下首行空、/bin/bash、/bin/dash和/bin/sh這4種情況的執行情況,大家先看一張表:
腳本名 | 首行 | 執行方式 | 執行結果 |
---|---|---|---|
dash1.sh | 空 | ./ | 成功 |
dash2.sh | #!/bin/bash | ./ | 成功 |
dash3.sh | #!/bin/dash | ./ | 失敗 |
dash4.sh | #!/bin/sh | ./ | 失敗 |
爲了解釋這個原因,先介紹下默認的交互shell和默認的系統shell
1.默認的交互shell
默認的交互shell會在用戶登錄某個虛擬控制檯終端或在GUI中運行終端仿真器時啓動,簡單講就是用戶使用登陸交互終端如crt、putty等登陸系統的默認shell。
root@ubuntu1604:~# echo $SHELL
/bin/bash
root@ubuntu1604:~# cat /etc/passwd|grep root
root:x:0:0:root:/root:/bin/bash
默認的交互shell爲bash
默認的交互shell由配置文件/etc/default/useradd的SHELL參數決定,感興趣的童鞋可以修改測試下。
2.默認的系統shell
默認的系統shell,用於那些需要在啓動時使用的系統shell腳本,“sh+腳本名”這種執行方式就是使用的系統shell,後面會有介紹。
root@ubuntu1604:~# which sh
/bin/sh
root@ubuntu1604:~# cd /bin/
root@ubuntu1604:/bin# ll|grep sh
-rwxr-xr-x 1 root root 1037528 May 16 2017 bash*
-rwxr-xr-x 1 root root 253816 Jun 16 2017 btrfs-show-super*
-rwxr-xr-x 1 root root 154072 Feb 18 2016 dash*
lrwxrwxrwx 1 root root 4 Feb 20 2019 rbash -> bash*
lrwxrwxrwx 1 root root 4 Feb 20 2019 sh -> dash*
lrwxrwxrwx 1 root root 4 Feb 20 2019 sh.distrib -> dash*
lrwxrwxrwx 1 root root 7 Aug 19 2015 static-sh -> busybox*
默認的系統shell /bin/sh指向/bin/dash,即默認系統shell爲dash。
3.腳本第一行測試總結
介紹完了默認交互shell和默認系統shell,在對以上兩個示例做個總結:
腳本名 | 首行 | 執行方式 | 默認交互shell | 默認系統shell | 等效於 | 執行結果 |
---|---|---|---|---|---|---|
first.sh | 空 | ./ | bash | dash | bash first.sh | 成功 |
dash1.sh | 空 | ./ | bash | dash | bash dash1.sh | 成功 |
dash2.sh | #!/bin/bash | ./ | bash | dash | bash dash1.sh | 成功 |
dash3.sh | #!/bin/dash | ./ | bash | dash | dash dash1.sh | 失敗 |
dash4.sh | #!/bin/sh | ./ | bash | dash | dash dash1.sh | 失敗 |
大家現在可能有些頭緒了,通過實驗可以得出結論一:
- 1.當腳本沒有在首行聲明shell時,系統會使用默認的交互shell(即文中的bash)執行腳本;
- 2.當腳本首行有聲明shell時,系統會讀取對應的shell;
回到之前的問題:不寫第一行可以不?
答案是不寫首行聲明某些時候不影響腳本執行結果,但是爲了規範,建議大家最好養成首行就聲明shell的習慣,因爲首行 #後面的驚歎號會告訴shell用哪個shell來運行腳本,並且聲明只能在首行。
在通常的shell腳本中,井號( # )用作註釋行。shell並不會處理shell腳本中的註釋行。然而,shell腳本文件的第一行是個例外, # 後面的驚歎號會告訴shell用哪個shell來運行腳本。
六、執行腳本的兩種方式
shell腳本執行有兩種方式,“sh+腳本名”和“./腳本名”,這兩種方式有啥卻別呢?首行如果聲明有誤或者在第二行聲明shell有什麼差別沒?看看下面的例子:
示例三:
root@ubuntu1604:~# more sh1.sh
echo "Hello,I am loong576!"
root@ubuntu1604:~# sh sh1.sh
Hello,I am loong576!
root@ubuntu1604:~# ./sh1.sh
Hello,I am loong576!
root@ubuntu1604:~# more sh2.sh
#!/bin/bash
echo "Hello,I am loong576!"
root@ubuntu1604:~# sh sh2.sh
Hello,I am loong576!
root@ubuntu1604:~# ./sh2.sh
Hello,I am loong576!
root@ubuntu1604:~# more sh3.sh
#!/bin/dash
echo "Hello,I am loong576!"
root@ubuntu1604:~# sh sh3.sh
Hello,I am loong576!
root@ubuntu1604:~# ./sh3.sh
Hello,I am loong576!
root@ubuntu1604:~# more sh4.sh
#!/bin/errorsh
echo "Hello,I am loong576!"
root@ubuntu1604:~# sh sh4.sh
Hello,I am loong576!
root@ubuntu1604:~# ./sh4.sh
-bash: ./sh4.sh: /bin/errorsh: bad interpreter: No such file or directory
root@ubuntu1604:~# more sh5.sh
#!/bin/errorsh
echo "Hello,I am loong576!"
root@ubuntu1604:~# sh sh5.sh
Hello,I am loong576!
root@ubuntu1604:~# ./sh5.sh
Hello,I am loong576!
示例三執行彙總:
腳本名 | 首行 | 執行方式 | 默認交互shell | 默認系統shell | 等效於 | 執行結果 |
---|---|---|---|---|---|---|
sh1.sh | 空 | sh | bash | dash | dash sh1.sh | 成功 |
sh1.sh | 空 | ./ | bash | dash | bash sh1.sh | 成功 |
sh2.sh | #!/bin/bash | sh | bash | dash | dash sh2.sh | 成功 |
sh2.sh | #!/bin/bash | ./ | bash | dash | bash sh2.sh | 成功 |
sh3.sh | #!/bin/dash | sh | bash | dash | dash sh3.sh | 成功 |
sh3.sh | #!/bin/dash | ./ | bash | dash | dash sh3.sh | 成功 |
sh4.sh | shell定義有誤 | sh | bash | dash | dash sh4.sh | 成功 |
sh4.sh | shell定義有誤 | ./ | bash | dash | errorsh sh4.sh | 失敗 |
sh5.sh | 空,第二行定義且有誤 | sh | bash | dash | dash sh5.sh | 成功 |
sh5.sh | 空,第二行定義且有誤 | ./ | bash | dash | bash sh5.sh | 成功 |
以上實驗驗證了之前的結論一,也可得出shell聲明須在首行的結論;同時從實驗也可對比sh和./兩種方式的區別得出結論二:
- 1.使用sh方式時,即使無論腳本首行指定的shell是什麼或者爲空都不影響腳本執行時使用的shell,具體講在本文“sh+腳本”方式等價於“dash+腳本”;
- 2.使用./執行腳本時會讀取腳本開頭指定的shell,若首行未指定shell則使用默認的交互shell,即本文的bash;
當然,二者還有個小區別是sh可以直接運行,./方式需要腳本有執行權限。
七、本文總結
本文圍繞腳本第一行展開測試,得出首行聲明有必要且必須在首行的結論,同時也擴展對腳本的兩種執行方式進行了對比,除了測試,也對shell和bash進行了理論介紹。
好了,腳本第一行寫好了,下面可以編寫各個功能的shell腳本了,敬請期待下篇,大家快上車,關注不迷路,哈哈……
更多請關注:shell專題