本教程分爲入門篇,命令篇和實戰篇,結合平時工做中使用Shell的經驗編寫。以實例爲主,側重於應用,總結了一些實用的技巧。html
如下爲本教程的《入門篇》,適於初學者快速入門以及老手查缺補漏。python
[TOC]shell
echo "Hello World"
echo -n "Hello World" # 不帶換行
echo -e '\e[0;33;1mHello\e[0m World' # 帶顏色的玩法 echo -e '\e[0;33;4mHello\e[0m World' # 帶顏色+下劃線 echo -e '\e[0;33;5mHello\e[0m World' # 帶顏色+閃爍
格式爲 \e[背景色;前景色;高亮格式m
,請閱讀詳細文檔後使用正確的姿式進行裝逼。ubuntu
if true then echo "Hello World" else echo "Bug" fi if false then echo "Hello World" elif true then echo "Bug" else echo "Bee" fi
if
、elif
會執行它後面跟着的命令,而後看返回值是否爲0
,若是爲0
則執行then
下面的語句塊,不然執行else
下面的語句塊。數組
[casheywen@ubuntu:~]$ true [casheywen@ubuntu:~]$ echo $? 0 [casheywen@ubuntu:~]$ false [casheywen@ubuntu:~]$ echo $? 1
注:bash
true
、false
事實上也爲一個命令,true
的返回碼必爲0
,false
的返回碼必爲1
app
$?
爲shell
內置變量,用於存放上一個命令的返回碼工具
test
、[ ]
、[[ ]]
實際上都是shell
中的命令,執行以後會返回1
或0
,而這幾個命令與if
相結合能夠達到咱們所須要的許多判斷功能,例如測試字符串是否爲空的三種寫法:性能
s="" if [ -z ${s} ] then echo "empty" fi if [[ -z ${s} ]] then echo "empty" fi if test -z ${s} then echo "empty" fi
事實上,if
後的[ ]
、[[ ]]
、test
命令都是能夠單獨執行的,而根據if
的判斷原理,後續執行哪一個分支也是由[ ]
、[[ ]]
、test
的返回值來決定的,如下是單獨執行它們的效果:測試
[casheywen@ubuntu:~]$ s="" [casheywen@ubuntu:~]$ [ -z "${s}" ] [casheywen@ubuntu:~]$ echo $? 0 [casheywen@ubuntu:~]$ s="abc" [casheywen@ubuntu:~]$ test -z "${s}" [casheywen@ubuntu:~]$ echo $? 1 [casheywen@ubuntu:~]$ s="123" [casheywen@ubuntu:~]$ [[ 100 -lt ${s} ]] [casheywen@ubuntu:~]$ echo $? 0
在性能方面[ ]
和test
性能基本相同,[[ ]]
性能是最高的,爲前二者的5
倍左右(以-d
運算符測試),因此建議儘可能使用[[ ]]
提升腳本性能。
運算符 | 描述 | 示例 |
---|---|---|
-e filename | 若是 filename 存在,則爲真 | [ -e /var/log/syslog ] |
-d filename | 若是 filename 爲目錄,則爲真 | [ -d /tmp/mydir ] |
-f filename | 若是 filename 爲常規文件,則爲真 | [ -f /usr/bin/grep ] |
-L filename | 若是 filename 爲符號連接,則爲真 | [ -L /usr/bin/grep ] |
-r filename | 若是 filename 可讀,則爲真 | [ -r /var/log/syslog ] |
-w filename | 若是 filename 可寫,則爲真 | [ -w /var/mytmp.txt ] |
-x filename | 若是 filename 可執行,則爲真 | [ -x /usr/bin/grep ] |
filename1 -nt filename2 | 若是 filename1 比 filename2 新,則爲真 | [ /tmp/install/etc/services -nt /etc/services ] |
filename1 -ot filename2 | 若是 filename1 比 filename2 舊,則爲真 | [ /boot/bzImage -ot arch/i386/boot/bzImage ] |
運算符 | 描述 | 示例 |
---|---|---|
-z string | 若是 string 長度爲零,則爲真 | [ -z "${myvar}" ] |
-n string | 若是 string 長度非零,則爲真 | [ -n "${myvar}" ] |
string1 = string2 | 若是 string1 與 string2 相同,則爲真 | [ "${myvar}" = "abc" ] |
string1 != string2 | 若是 string1 與 string2 不一樣,則爲真 | [ "${myvar}" != "abc" ] |
string1 < string | 若是 string1 小於 string2,則爲真 | [ "${myvar}" \< "abc" ] <br/>[[ "${myvar}" < "abc" ]] |
string1 > string | 若是 string1 大於 string2,則爲真 | [ "${myvar}" \> "abc" ] <br/>[[ "${myvar}" > "abc" ]] |
注意:
在字符串兩邊加上""防止出錯
<
和>
是字符串比較,不要錯用成整數比較若是是在
[ ]
中使用<
和>
,須要將它們寫成\<
和\>
運算符 | 描述 | 示例 |
---|---|---|
num1 -eq num2 | 等於 | [ 3 -eq $mynum ] |
num1 -ne num2 | 不等於 | [ 3 -ne $mynum ] |
num1 -lt num2 | 小於 | [ 3 -lt $mynum ] |
num1 -le num2 | 小於或等於 | [ 3 -le $mynum ] |
num1 -ge num2 | 大於或等於 | [ 3 -ge $mynum ] |
&& 能夠用來對兩個判斷語句求與 |
---|
if [ -n "abc" ] && [ -n "aa" ] |
if [[ -n "abc" ]] && [[ -n "aa" ]] |
if test -n "abc" && test -n "aa" |
if [[ -n "abc" && -n "aa" ]] |
注:只有
[[ ]]
才容許把&&
寫在裏面
`\ | \ | `能夠用來對兩個判斷語句求或 |
---|---|---|
`if [ -n "abc" ] \ | \ | [ -n "aa" ]` |
`if [[ -n "abc" ]] \ | \ | [[ -n "aa" ]]` |
`if test -n "abc" \ | \ | test -n "aa"` |
`if [[ -n "abc" \ | \ | -n "aa" ]]` |
注:只有
[[ ]]
才容許把||
寫在裏面
小技巧
&&
、||
還能夠用來拼接命令,達到按前一個命令成功與否來決定是否執行後一個命令的效果
cd /data && ls # 當`cd /data`返回0(即成功)時才執行後面的`ls` cd /data || cd /root # 當`cd /data`返回非0(即失敗)時才執行後面的`cd /root`
for i in {1..100} do echo ${i} done
注:
for i in `seq 100` do echo ${i} done for i in `seq 1 2 100` do echo ${i} done
注:
seq
自己爲一個命令,用於輸出數字組成的序列,如seq 100
會生成並輸出1 2 3 ... 100
(1~100以換行符隔開)這樣的序列,而seq 1 2 100
則會生成並輸出1 3 5 ... 99
(以1開始,2爲公差的等差數列中小於100的項,以換行符隔開)。反引號(`)之間的命令會被執行,其輸出結果會轉換成一個變量,故上面的
for in
會依次取出seq
的執行結果賦值於i
進行循環。
for ((i = 0; i < 100; i++)) do echo ${i} done for ((i = 0; i < 100; i+= 2)) do echo ${i} done
注:
以上與C語言式的
for
循環語法基本相同,區別在於雙重括號:(( ))
i=0 while [[ ${i} -lt 100 ]] do echo ${i} ((i++)) done
i=0 until [[ ${i} -ge 100 ]] do echo ${i} ((i++)) done
注:
while
和until
的判斷原理與if
是相似的,它會執行並它後面跟着的命令,不一樣點在於:
while
是後面語句返回值爲0
,則執行循環中的語句塊,不然跳出循環;
until
則是後面語句返回值非0
,則執行循環中的語句塊,不然跳出循環。
方法較多,此處只列舉最淺顯易懂,而且效率最高的辦法——直接將符合C語言語法的表達式放到(( ))
中便可達到對整數的計算目的:
echo $(( 1+1 )) # 最簡單的1+1 echo $(( (1+2)*3/4 )) # 表達式中還能夠帶括號 echo $(( 1<<32 )) # 左移右移也支持,但僅限於-4294967296~4294967296之間的數值 echo $(( 1&3 )) # &、^、|、~ 這樣的位操做亦支持 (( i=1+2 )) # 將1+2計算出結果後賦值給i,後續若`echo ${i}`會獲得3 (( i++ )) # 變量i自增1 (( i+=3 )) # 變量i自增3 # ... # 還有不少,再也不詳列
注:
進行整數運算的方法還有:
expr
、$[]
、let
等shell
等內置命令,也可調用bc
、python
等外部工具進行更復雜的數學運算
操做 | 含義 |
---|---|
${string/old/new} | string中第一個old替換爲new |
${string//old/new} | string中全部old替換爲new |
[casheywen@ubuntu:~]# s="i hate hate you" [casheywen@ubuntu:~]# echo ${s/hate/love} i love hate you [casheywen@ubuntu:~]# echo ${s//hate/love} i love love you
操做 | 含義 |
---|---|
${string:n} | string從下標n到結尾的子串 |
${string:n:m} | string從下標n開始長度爲m的子串 |
${string::m} | string從下標0開始長度爲m的子串 |
[casheywen@ubuntu:~]# s="0123456789" [casheywen@ubuntu:~]# echo ${s:3} 3456789 [casheywen@ubuntu:~]# echo ${s::3} 012 [casheywen@ubuntu:~]# echo ${s:0:3} 012 [casheywen@ubuntu:~]# echo ${s:2:5} 23456
通配刪除,即按通配符,刪除掉字符串中符合條件的一部分
操做 | 含義 |
---|---|
${string#pattern} | string從左到右刪除pattern的最小通配 |
${string##pattern} | string從左到右刪除pattern的最大通配 |
${string%pattern} | string從右到左刪除pattern的最小通配 |
${string%%pattern} | string從右到左刪除pattern的最大通配 |
注:
此處通配規則參考通配符一覽表
最小通配和最大通配的區別:
最小通配:符合通配的最小子串最大通配:符合通配的最大子串
例如string值爲/00/01/02/dir
,對於通配/*/
,其最小通配爲/00/
,而最大通配/00/01/02/
[casheywen@ubuntu:~]# s="/00/01/02/dir" [casheywen@ubuntu:~]# echo ${s#/*/} 01/02/dir [casheywen@ubuntu:~]# echo ${s##/*/} dir [casheywen@ubuntu:~]# [casheywen@ubuntu:~]# s="abc/cde/efg" [casheywen@ubuntu:~]# echo ${s%/*} abc/cde [casheywen@ubuntu:~]# echo ${s%%/*} abc [casheywen@ubuntu:~]# [casheywen@ubuntu:~]# s="/00/01/02/dir" [casheywen@ubuntu:~]# echo ${s#/*/} 01/02/dir [casheywen@ubuntu:~]# echo ${s##/*/} dir [casheywen@ubuntu:~]# s="abc/cde/efg" [casheywen@ubuntu:~]# echo ${s%/*} abc/cde [casheywen@ubuntu:~]# echo ${s%%/*} abc
小技巧
獲取文件名:${path##*/}
(至關於basename
命令的功能)
獲取目錄名:${path%/*}
(至關於dirname
命令的功能)
獲取後綴名:${path##*.}
[casheywen@ubuntu:~]# s="/root/test/dir/subdir/abc.txt" [casheywen@ubuntu:~]# echo ${s##*/} abc.txt [casheywen@ubuntu:~]# echo ${s%/*} /root/test/dir/subdir [casheywen@ubuntu:~]# echo ${s##*.} txt
a=() # 空數組 a=(1 2 3) # 元素爲1,2,3的數組 echo ${#a[*]} # 數組長度 echo ${a[2]} # 下標爲2的元素值(下標從0開始) a[1]=0 # 給下標爲1的元素賦值 # 遍歷數組 for i in ${a[*]} do echo ${i} done unset a # 清空數組
關聯數組能夠用於存儲key-value
型的數據,其功能至關於C++
中的map
或python
中的dict
。
declare -A a # 聲明關聯數組(必須有此句) a=(["apple"]="a1" ["banana"]="b2" ["carrot"]="c3") # 初始化關聯數組 echo ${#a[*]} # 獲取元素個數 echo ${a["carrot"]} # 獲取元素值 a["durian"]="d4" # 插入或修改元素 echo ${!a[*]} # 獲取全部的key unset a["banana"] # 刪除元素 # 遍歷數組(僅value) for i in ${a[*]} do echo ${i} done # 遍歷數組(key和value) for key in ${!a[*]} do echo "${key} ==> ${a[${key}]}" done unset a # 清空數組
注:
關聯數組須要bash 4.0以上版本才支持,選用需慎重。查看
bash
版本用bash --version
關聯數組必須用
declare -A
顯示聲明類型,不然數值會出錯。
LINE_CNT=`wc -l test.txt`
LINE_CNT=$(wc -l test.txt)
以上命令都可把wc -l test.txt
的結果存入LINE_CNT
變量中
注:
` `
和$( )
都只將命令行標準輸出的內容存入變量,若是須要將標準錯誤內容存入變量,須要用到重定向。
若是命令執行結果有多行內容,存入變量並打印時換行符會丟失:
[casheywen@ubuntu:~]# cat test.txt a b c [casheywen@ubuntu:~]# CONTENT=`cat test.txt` [casheywen@ubuntu:~]# echo ${CONTENT} a b c
若須要保留換行符,則在打印時必須加上""
:
[casheywen@ubuntu:~]# CONTENT=`cat test.txt` [casheywen@ubuntu:~]# echo "${CONTENT}" a b c
名稱 | 英文縮寫 | 內容 | 默認綁定位置 | 文件路徑 | Shell中代號 |
---|---|---|---|---|---|
標準輸入流 | stdin |
程序讀取的用戶輸入 | 鍵盤輸入 | /dev/stdin |
0 |
標準輸出流 | stdout |
程序的打印的正常信息 | 終端(terminal ), 即顯示器 |
/dev/stdin |
1 |
標準錯誤流 | stderr |
程序的錯誤信息 | 終端(terminal ),, 即顯示器 |
/dev/stderr |
2 |
操做 | 含義 |
---|---|
cmd > file |
把 stdout 重定向到 file |
cmd >> file |
把 stdout 追加到 file |
cmd 2> file |
把 stderr 重定向到 file |
cmd 2>> file |
把 stderr 追加到 file |
cmd &> file |
把 stdout 和 stderr 重定向到 file |
cmd > file 2>&1 |
把 stdout 和 stderr 重定向到 file |
cmd >> file 2>&1 |
把 stdout 和 stderr 追加到 file |
cmd <file >file2 cmd |
cmd 以 file 做爲 stdin,以 file2 做爲 stdout |
cat <>file |
以讀寫的方式打開 file |
cmd < file cmd |
cmd 命令以 file 文件做爲 stdin |
cmd << delimiter Here document |
從 stdin 中讀入,直至遇到 delimiter 分界符 |
把程序打印的內容輸出到文件
# 如下兩種方式都會將`Hello World`寫入到hello.txt(若不存在則建立) echo "Hello World" > hello.txt # hello.txt原有的將被覆蓋 echo "Hello World" >> hello.txt # hello.txt原有內容後追加`Hello World`
把程序的錯誤信息輸出到文件
例如文件路徑中不存在+++
這個文件:
[casheywen@ubuntu:~]# ls +++ ls: cannot access +++: No such file or directory [casheywen@ubuntu:~]# ls +++ > out.txt ls: cannot access +++: No such file or directory
上面的ls +++
後輸出的內容爲標準錯誤流中的錯誤信息,因此即便用> out.txt
重定向標準輸入,錯誤信息仍然被打印到了屏幕。
# 如下兩種方式都會將`ls +++`輸出的錯誤信息輸出到err.txt(若不存在則建立) ls +++ 2> err.txt # err.txt原有內容將被覆蓋 ls +++ 2>> err.txt # err.txt原有內容後追加內容
1. 讓程序從文件讀取輸入
以默認從標準輸入讀取表達式,並進行數學計算的命令bc
爲例:
[casheywen@ubuntu:~]# bc -q 1+1 2
注:
1+1
爲鍵盤輸入的內容,2
爲bc
命令打印的計算結果
bc
後的-q
參數用於禁止輸出歡迎信息以上重定向方法格式爲
命令 < 文件路徑
若是我須要把已經存在文件exp.txt
中的一個表達式輸入到bc
中進行計算,能夠採用重定向標準輸入流的方法:
bc -q < exp.txt
注:
當
exp.txt
中內容爲1+1
時,以上語句輸出2
因爲
bc
命令自己支持從文件輸入,如不使用重定向,也可用bc exp.txt
達到相同效果
2. 將變量中內容做爲程序輸入
EXP="1+1" bc -q <<< "${EXP}"
注:
以上代碼等同於執行
bc
並輸入1+1
,獲得的輸出爲2
。以上重定向方法格式爲
命令 <<< 變量內容
3. 將當前shell腳本中的多行內容做爲程序的輸入
例如在shell中內嵌多行python代碼:
python << EOF print 'hello from python' print 'hello' + 'world' EOF # 內容中支持shell變量 MSG="shell variable" python << EOF print '${MSG}' EOF
注:
以上用法能夠方便地將某個程序須要的多行輸入內容直接包含在當前腳本中
支持變量,能夠動態地改變多行輸入的內容
以上重定向方法格式爲:
命令 << EOF (換行)...(換行) EOF
,其中的EOF
換成其它字符串也是有效的,如:命令 << ABC (換行)...(換行) ABC
的,但通用習慣都使用EOF
管道的寫法爲 cmd1 | cmd2
,功能是依次執行cmd1
和cmd2
,並將cmd1
的標準輸出做爲cmd2
的標準輸入,例如:
echo "1+1" | bc
這裏 echo "1+1"
會將1+1
輸出到標準輸出,而管道會將echo
輸出的1+1
做爲bc
命令的標準輸入,這樣bc
會讀取到1+1
,最終獲得計算結果2
打印到屏幕。
注:
管道能夠多級拼接:
cmd1 | cmd2 | cmd3 | ...
管道的返回值爲最後一級命令的返回值
LINE_NO=0 cat test.txt | while read LINE do (( LINE_NO++ )) echo "${LINE_NO} ${LINE}" done # echo "${LINE_NO}"
以上代碼能夠將test.txt
中每一行標上行標後輸出。
注:
read
命令用於從標準輸入讀取一行並賦值給一個或多個變量,如read LINE
會從標準輸入讀取一行並將整行內容賦值給LINE
變量,read A B
則會從標準輸入讀入一行並將這行的第一、2列分別賦值給A、B兩個變量(分割符默認爲空格或tab,可給IFS
賦值來更改分割符)末尾註釋掉的
echo "${LINE_NO}"
若執行會輸出0
,緣由是管道中的while
語句是執行在子進程中的,不會改變父進程中LINE_NO
變量的值
find . -type f -name *.log | xargs rm
以上代碼能夠將當前目錄及子目錄全部後綴名爲.log
的文件
find . -type f -name *.log | xargs -i mv {} /data/log
以上代碼能夠將當前目錄及子目錄中全部後綴名爲.log
的文件移動到/data/log
中
注:
xargs
能夠從標準輸入讀取內容,以之構建並執行另外一個命令行
xargs
直接接命令名稱,則將從標準輸入讀取的全部內容合併爲一行構建命令行並執行
xargs
加上-i
參數,則會每讀取一行就構建並執行一個命令行,構建命令行時會將{}
替換爲該行的內容
[casheywen@ubuntu:~]# cat test.txt a b c [casheywen@ubuntu:~]# cat test.txt | xargs echo rm rm a b c [casheywen@ubuntu:~]# cat test.txt | xargs -i echo rm {} rm a rm b rm c
上例展現了
xargs
構建命令的原理,若是去掉xargs
後的echo
,則會執行打印出來的命令
若是你的當前目錄中有1.txt 2.txt 3.txt
三個文件,那麼當你執行ls *.txt
這條命令,shell
究竟爲你作了什麼?
其實shell
會先讀取當前目錄,而後按*.txt
的通配條件過濾獲得1.txt 2.txt 3.txt
這個文件列表,而後將這個列表做爲參數傳給ls
,即ls *.txt
至關於ls 1.txt 2.txt 3.txt
,ls
命令自己並不會獲得*.txt
這樣的參數。
注:僅當目錄中沒有符合
*.txt
通配的文件,shell
纔會將*.txt
這個字符串看成參數直接傳給ls
命令
因此若是須要列出當前目錄中全部的txt
文件,咱們使用echo *.txt
也一樣能夠達到目的:
[casheywen@ubuntu:~]# ls *.txt 1.txt 2.txt 3.txt [casheywen@ubuntu:~]# echo *.txt 1.txt 2.txt 3.txt
注:對於
{ }
通配shell
不會讀取目錄並過濾得到文件列表。詳細請參考下文。
字符 | 含義 | 實例 |
---|---|---|
* | 匹配 0 或多個字符 | a*b a與b之間能夠有任意長度的任意字符, 也能夠一個也沒有, 如aabcb, axyzb, a012b, ab。 |
? | 匹配任意一個字符 | a?b a與b之間必須也只能有一個字符, 能夠是任意字符, 如aab, abb, acb, a0b。 |
[list] |
匹配 list 中的任意單一字符 | a[xyz]b a與b之間必須也只能有一個字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb。 |
[!list] <br/>[^list] |
匹配 除list 中的任意單一字符 | a[!0-9]b a與b之間必須也只能有一個字符, 但不能是阿拉伯數字, 如axb, aab, a-b。 |
[c1-c2] |
匹配 c1-c2 中的任意單一字符 如:[0-9] [a-z] | a[0-9]b 0與9之間必須也只能有一個字符 如a0b, a1b... a9b。 |
{string1,string2,...} |
枚舉sring1或string2(或更多)其一字符串 | a{abc,xyz,123}b 展開成aabcb axyzb a123b |
{c1..c2} <br/>{n1..n2} |
枚舉c1-c2中全部字符或n1-n2中全部數字 | {a..f} 展開成a b c d e f <br/>a{1..5} 展開成a1 a2 a3 a4 a5 |
注:
*
、?
、[ ]
的通配都會按讀取目錄並過濾的方式展開通配項目
{ }
則不會有讀取目錄的過程,它是經過枚舉全部符合條件的通配項直接展開的
[casheywen@ubuntu:~]# ls 1.txt 2.txt 3.txt [casheywen@ubuntu:~]# echo *.txt 1.txt 2.txt 3.txt [casheywen@ubuntu:~]# echo {1..5}.txt 1.txt 2.txt 3.txt 4.txt 5.txt [casheywen@ubuntu:~]# ls {1..5}.txt ls: cannot access 4.txt: No such file or directory ls: cannot access 5.txt: No such file or directory 1.txt 2.txt 3.txt
由上面的命令可見,
*
通配的結果與目錄中存在哪些文件有關係,而{ }
的通配結果與目錄中存在哪些文件無關。若用{ }
進行通配,則有可能將不存在的文件路徑做爲命令行參數傳給程序。
*
用於通配文件名或目錄名中某一部分爲任意內容:
rm *.log # 刪除當前目錄中全部後綴名爲.log的文件 rm */log/*.log # 刪除全部二級log目錄中後綴名爲.log的文件
[ ]
用於通配文件名或目錄名中某個字符爲限定範圍內或限定範圍外的值:
rm Program[1-9]*.log # 刪除當前目錄中以Program跟着一個1到9的數字開頭,並以.log爲後綴名的文件 du -sh /[^udp]* # 對根目錄中全部不以u、d、p開頭的文件夾求取總大小
?
用於通配文件名中某處一個任意值的字符:
rm L????.txt # 通配一個文件名以L開頭,後面跟着4個字符,並以.txt結尾的文件:如LAB01.txt
{ }
也爲通配符,用於通配在它枚舉範圍內的值,因爲它是直接展開的,咱們能夠將它用於批量建立目錄或文件,也能夠用於生成序列:
批量生成目錄
[casheywen@ubuntu:~]# ls [casheywen@ubuntu:~]# mkdir dir{0..2}{0..2} [casheywen@ubuntu:~]# ls dir00 dir01 dir02 dir10 dir11 dir12 dir20 dir21 dir22
生成序列
{ }
生成的序列經常使用於for循環
for ip in 192.168.234.{1..255} do ping ${ip} -w 1 &> /dev/null && echo ${ip} is Alive done
以上例子用於查找
192.168.234.1~192.168.234.255
整個網段能ping
通的全部ip