為了進一步提高效率,逐步用 linux 替代 windows,如果不會編寫 shell 腳本則無法發揮命令行的優勢。
1 Shell腳本
- Shell 有些獨特,因為它不僅是一個功能強大的命令行接口,也是一個腳本語言解釋器。
- 一個 shell 腳本就是一個包含一系列命令的文件。shell 讀取這個文件,然后執行 文件中的所有命令,就好像這些命令已經直接被輸入到了命令行中一樣。
2 基本步驟
為了成功地創建和運行一個 shell 腳本,我們需要做三件事情:
- 編寫一個腳本
hello_world
。Linux並不根據后綴名判斷文件類型,如果命名為hello_world.sh
,執行的時候要輸入完整的文件名。基本格式如下:
#!/bin/bash
# This is our first script.
echo 'Hello World!'
這個#!
字符序列是一種特殊的結構,被用來告訴操作系統將執行此腳本所用的解釋器的名字。每個 shell 腳本都應該把這一文本行 作為它的第一行。
-
使腳本文件可執行。 使用
chmod
命令,對於腳本文件,有兩個常見的權限設置;權限為755的腳本,則每個人都能執行,和權限為700的 腳本,只有文件所有者能夠執行。注意為了能夠執行腳本,腳本必須是可讀的。 -
把腳本放置到 shell 能夠找到的地方。為了能夠運行此腳本,我們必須指定腳本文件明確的路徑,如下:
[me@linuxbox ~]$ ./hello_world
Hello World!
如果沒有給出可執行程序的明確路徑名,那么系統每次都會搜索一系列的目錄,來查找此可執行程序。這個目錄列表被存儲在一個名為 PATH 的環境變量中。這個 PATH 變量包含一個由冒號分隔開的目錄列表。
- 如果我們的腳本位於此列表中任意目錄下,那么不明確指定腳本文件路徑也可以運行。
- 如果這個 PATH 變量不包含這個目錄,我們能夠手動添加。比如,在.bashrc 文件中包含下面這一行文本將
~/bin
加入到PATH變量:
export PATH=~/bin:"$PATH"
注意這個命令是向PATH中增加新的目錄項,等號左側是PATH變量,右側是新的值,其中$PATH
是對PATH的展開(后面會講)。
當做了這個修改之后,它會在每個新的終端會話中生效。為了把這個修改應用到當前的終端會話中, 我們必須讓 shell 重新讀取這個 .bashrc 文件。
[me@linuxbox ~]$ . .bashrc
這個點(.)命令是 source 命令的同義詞,一個 shell 內建命令,用來讀取一個指定的 shell 命令文件, 並把它看作是從鍵盤中輸入的一樣。
3 保存位置
~/bin
目錄是存放為個人所用腳本的好地方。
如果我們編寫了一個腳本,系統中的每個用戶都可以使用它,那么這個腳本的傳統位置是 /usr/local/bin
。
系統管理員使用的腳本經常放到 /usr/local/sbin
目錄下。
大多數情況下,本地支持的軟件,不管是腳本還是編譯過的程序,都應該放到 /usr/local
目錄下, 而不是在 /bin
或 /usr/bin
目錄下。
4 格式技巧
- 為了減少輸入,當在命令行中輸入選項的時候,短選項更受歡迎,但是當書寫腳本的時候, 長選項能提供可讀性。
- 當使用長命令的時候,通過把命令在幾個文本行中展開,可以提高命令的可讀性。
find playground \
\( \
-type f \
-not -perm 0600 \
-exec chmod 0600 ‘{}’ ‘;’ \
\) \
-or \
\( \
-type d \
-not -perm 0711 \
-exec chmod 0711 ‘{}’ ‘;’ \
\)
5 變量
- 當 shell 碰到一個變量的時候,它會自動地創建它。這不同於許多編程語言,它們中的變量在使用之前,必須顯式的聲明或是定義。
- shell 不會在乎變量值的類型;它把它們都看作是字符串。
6 展開
6.1 命令展開
把一個命令的輸出作為一個展開模式來使用:
[me@linuxbox ~]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
6.2 字符串展開
- 基本用法
所有的變量存儲的都是字符串,我們在使用這些字符串時有時候需要進行簡單的處理,比如只取一部分、去掉后綴名等等。下面的命令就是按照特定要求展開字符串:
var = /home/me/video/maopian.avi
命令格式 | 功能 |
---|---|
${var} |
完全展開字符串。 |
${#var} |
展開為字符串var的長度。 |
${var:offset} |
提取字符串var中第 offset 個字符到末尾的部分。 |
${var:offset:length} |
提取字符串var中第 offset 個字符起 length 個字符。 |
- 開頭/末尾字符清除
命令格式 | 功能 |
---|---|
${var#pattern} |
從 var 字符串中清除開頭一部分匹配 pattern的文本。清除最短的匹配結果 |
${var##pattern} |
清除最長的匹配結果 |
${var%pattern} |
清除的文本從 var 所包含字符串的末尾開始 |
${var%%pattern} |
清除的文本從 var所包含字符串的末尾開始 |
- 查找替換。對 var 的內容執行查找和替換操作。如果找到了匹配通配符 pattern 的文本, 則用 string 的內容替換它。/string 可能會省略掉,這樣會導致刪除匹配的文本。
命令格式 | 功能 |
---|---|
${var/pattern/string} |
只有第一個匹配項會被替換掉。 |
${var//pattern/string} |
所有的匹配項都會被替換掉。 |
${var/#pattern/string} |
要求匹配項出現在字符串的開頭 |
${var/%pattern/string} |
要求匹配項出現在字符串的末尾 |
引用
有時候我們的字符串中包含了可展開的形式,但我們本意不想讓這部分展開。通過引用,我們可以禁止一部分字符串的展開。
隨着引用程度加強,越來越多的展開被禁止。如果需要禁止所有的展開,我們要使用單引號。
以下例子是無引用,雙引號,和單引號的比較結果:
[me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 me
[me@linuxbox ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
7 函數
Shell 函數有兩種語法形式:
function name {
commands
return
}
和
name () {
commands
return
}
通過在變量名之前加上單詞 local,來定義局部變量。這就創建了一個只對其所在的 shell 函數起作用的變量。在這個 shell 函數之外,這個變量不再存在。
8 位置參數
shell 提供了一個稱為位置參數的變量集合,這個集合包含了命令行中所有獨立的單詞。
#!/bin/bash
# posit-param: script to view command line parameters
echo "
Number of arguments: $#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
"
輸出結果:
[me@linuxbox ~]$ posit-param a b c d
Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
即使不帶命令行參數,位置參數 $0
總會包含命令行中出現的第一個單詞,也就是已執行程序的路徑名。
實際上通過參數展開方式你可以訪問的參數個數多於9個。只要指定一個大於9的數字,用花括號把該數字括起來就可以。 例如 ${10}、 ${55}、 ${211}
等等。
shell 還提供了一個名為 $#
,可以得到命令行參數個數。
正如位置參數被用來給 shell 腳本傳遞參數一樣,它們也能夠被用來給 shell 函數傳遞參數。
shell 提供了兩種特殊的參數。他們二者都能擴展成完整的位置參數列表.
參數 | 描述 |
---|---|
$* | 展開成一個從1開始的位置參數列表。當它被用雙引號引起來的時候,展開成一個由雙引號引起來 的字符串,包含了所有的位置參數,每個位置參數由 shell 變量 IFS 的第一個字符(默認為一個空格)分隔開。 |
$@ | 展開成一個從1開始的位置參數列表。當它被用雙引號引起來的時候, 它把每一個位置參數展開成一個由雙引號引起來的分開的字符串。 |
“$@” 在大多數情況下是最有用的方法,因為它保留了每一個位置參數的完整性。
9 條件判斷
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
經常與 if 一塊使用的命令是 test。這個 test 命令執行各種各樣的檢查與比較。 它有兩種等價模式:
test expression
和
[ expression ]
目前的 bash 版本包括一個復合命令,作為加強的 test 命令替代物。它使用以下語法:
[[ expression ]]
這個[[ ]]命令非常 相似於 test 命令(它支持所有的表達式),但是增加了一個重要的新的字符串表達式:
string1 =~ regex
如果 string1匹配擴展的正則表達式 regex,其返回值為真。
10 case語句
#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case $REPLY in
[[:upper:]]) echo "'$REPLY' is upper case." ;;&
[[:lower:]]) echo "'$REPLY' is lower case." ;;&
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
[[:digit:]]) echo "'$REPLY' is a digit." ;;&
[[:graph:]]) echo "'$REPLY' is a visible character." ;;&
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
- case 語句使用的模式和路徑展開中使用的那些是一樣的。模式以一個 “)” 為終止符。
- 還可以使用豎線字符作為分隔符,把多個模式結合起來。這就創建了一個 “或” 條件模式。
- 添加的 “;;&” 的語法允許 case 語句繼續執行下一條測試,而不是簡單地終止運行。
11 循環
- while 語法舉例
#!/bin/bash
# while-count: display a series of numbers
count=1
while [ $count -le 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
- util 語法舉例
#!/bin/bash
# until-count: display a series of numbers
count=1
until [ $count -gt 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
- for 語法舉例
第一種形式:
#!/bin/bash
for i in A B C D; do
echo $i;
done
第二種形式:
#!/bin/bash
# simple_counter : demo of C style for command
for (( i=0; i<5; i=i+1 )); do
echo $i
done