Bash只支持單行註釋,使用#
開頭的都被看成註釋語句:shell
# 整行註釋 echo hello world # 行尾註釋
經過Bash的一些特性,能夠取巧實現多行註釋:編程
: ' 註釋1 註釋2 ' : <<'EOF' 註釋1 註釋2 EOF ____=' 註釋1 註釋2 '
可是,別閒的蛋疼去用取巧的多行註釋,安心用#
來註釋。安全
Bash中基本數據類型只有字符串類型,連數值類型都沒有(declare -i
可強制聲明數值類型)。bash
好比:app
# 都會看成字符串 echo haha echo 1234
Bash中字符串的串聯操做,直接將兩段數據鏈接在一塊兒便可,不須要任何操做符。less
例如:ssh
echo "junma""jinlong" echo 1234 5678
a=3 echo $a a="www.junmajinlong.com" echo $a a='hello world' echo $a
Shell中能夠引用未定義的變量:curl
echo $xyzdefabc
能夠定義空變量:異步
a= echo $a
變量替換是指在命令開始執行前,Shell會先將變量的值替換到引用變量的位置處。編程語言
例如:
a="hello" echo $a world
在echo命令開始執行前,Shell會取得變量a的值hello,並將它替換到命令行的$a
處。因而,在echo命令開始執行時,命令行已經變成:
echo hello world
除了變量替換,Shell還會作其它替換:
這些擴展和替換,都是Shell在調用命令以前就完成的,這和其它語言解析代碼的方式不同。
後面會詳細解釋Shell是如何作命令行解析的,若是不掌握命令行解析,當遇到命令行語法錯誤後極可能會花掉大量無謂的時間去調試命令。而掌握命令行解析後,就會對命令生命週期瞭如執掌,不敢說一次就能寫對全部命令行,但能節省大量調試時間,對寫命令行和寫腳本的能力也會上升一個層次。
使用反引號或$()
能夠執行命令替換。
`cmd` $(cmd)
命令替換是指先執行cmd,將cmd的輸出結果替換到$()
或反引號位置處。
例如:
echo `id root` echo $(id root)
在echo命令執行前,會先執行id命令,id命令的執行結果:
$ id root uid=0(root) gid=0(root) groups=0(root)
因此會將結果uid=0(root) gid=0(root) groups=0(root)
替換$(id root)
。因而,echo命令開始執行時,命令行已經變成了:
echo uid=0(root) gid=0(root) groups=0(root)
$[]
或$(())
或let命令能夠作算術運算。
let是單獨的命令,不能寫在其它命令行中。
a=3 let a=a+1 echo $a
$[]
和$(())
能夠寫在命令行內部,Shell在解析命令行的時候,會對它們作算術運算,而後將運算結果替換到命令行中。
a=33 echo $[a+3] echo $((a+3))
由於變量替換先於算術替換,因此,使用變量名或引用變量的方式均可以:
a=333 echo $[$a+3] echo $(($a+3))
每一個命令執行後都會有對應的進程退出狀態碼,用來表示該進程是不是正常退出。
因此,在命令行中,在Shell腳本中,常常會使用特殊變量$?
判斷最近一個前臺命令是否正常退出。
一般狀況下,若是$?
的值:
另外,在Shell腳本中,全部條件判斷(好比if語句、while語句)都以0退出狀態碼錶示True,以非0退出狀態碼爲False。
exit命令可用於退出當前Shell進程,好比退出當前Shell終端、退出Shell腳本,等等。
exit [N]
exit可指定退出狀態碼N,若是省略N,則默認退出狀態碼爲0,即表示正確退出。
在命令的結尾使用&
符號,能夠將這個命令放入後臺執行。
命令放入後臺後,會當即回到Shell進程,Shell進程會當即執行下一條命令(若是有)或退出。
使用$!
能夠獲取最近一個後臺進程的PID。
sleep 20 & echo $!
使用wait
命令能夠等待後臺進程(當前Shell進程的子進程)完成:
wait [n1 n2 n3 ...]
不給定任何參數時,會等待全部子進程(即全部後臺進程)完成。
sleep 5 & wait echo haha
Shell中有多種組合多個命令的方式。
1.cmd1退出後,執行cmd2
cmd1;cmd2
2.cmd1正確退出(退出狀態碼爲0)後,執行cmd2
cmd1 && cmd2
3.cmd1不正確退出後,執行cmd2
cmd1 || cmd2
4.邏輯結合:&&
和||
能夠隨意結合
# cmd1正確退出後執行cmd2,cmd2正確退出後執行cmd3 cmd1 && cmd2 && cmd3... # cmd1正確退出則執行cmd2,cmd1不正確退出會執行cmd3 # cmd1正確退出,但cmd2不正確退出,也會執行cmd3 cmd1 && cmd2 || cmd3 # cmd1正確退出會執行cmd3 # cmd1不正確退出會執行cmd2,cmd2正確退出會執行cmd3 cmd1 || cmd2 && cmd3
5.將多個命令分組:小括號或大括號能夠組合多個命令
# 小括號組合的多個命令是在子Shell中執行 # 即會先建立一個新的Shell進程,在執行裏面的命令 (cmd1;cmd2;cmd3) # 大括號組合的多個命令是在當前Shell中執行 # 大括號語法特殊,要求: # 1.開閉括號旁邊都有空白,不然語法解析錯誤(解析成大括號擴展) # 2.寫在同一行時,每一個cmd後都要加分號結尾 # 3.多個命令可分行書寫,不要求分號結尾 { cmd1;cmd2;cmd3; } { cmd1 cmd2 cmd3 }
軟件設計認爲,程序應該有一個數據來源、數據出口和報告錯誤的地方。在Linux系統中,每一個程序默認都會打開三個文件描述符(file descriptor,fd):
文件描述符,說白了就是系統爲了跟蹤打開的文件而分配給它的一個數字,這個數字和文件有對應關係:從文件描述符讀取數據,即表示從對應的文件中讀取數據,向文件描述符寫數據,即表示向對應文件中寫入數據。
Linux中萬物皆文件,文件描述符也是文件。默認:
這些文件默認又是各個終端的軟連接文件:
$ ls -l /dev/std* lrwxrwxrwx 1 root root 15 Jan 8 20:26 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx 1 root root 15 Jan 8 20:26 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx 1 root root 15 Jan 8 20:26 /dev/stdout -> /proc/self/fd/1 $ ls -l /proc/self/fd/ lrwx------ 1 root root 64 Jan 16 10:40 0 -> /dev/pts/0 lrwx------ 1 root root 64 Jan 16 10:40 1 -> /dev/pts/0 lrwx------ 1 root root 64 Jan 16 10:40 2 -> /dev/pts/0 lr-x------ 1 root root 64 Jan 16 10:40 3 -> /proc/75220/fd
因此,默認狀況下讀寫數據都是終端,例如:
# 數據輸出到終端 $ echo haha $ cat /etc/fstab # 從終端讀取數據 $ cat hello # 在終端輸入 hello # 在終端輸出 world # 在終端輸入 world # 在終端輸出 ^C
改變文件描述符對應的目標,能夠改變數據的流向。好比標準輸入fd=1默認流向是終端設備,若將其改成/tmp/a.log,便能讓數據寫入/tmp/a.log文件中而再也不是終端設備中。
在Shell中,這種改變文件描述符目標的行爲稱爲重定向,即從新肯定數據的流向。
其實,文件描述符有不少類操做,包括fd的重定向、fd的分配(open,即打開文件)、fd複製(duplicate)、fd的移動(move)、fd的關閉(close)。如今只介紹基礎重定向操做。
Shell中,基礎重定向操做有如下幾種方式:
[n]>file
:覆蓋式輸出重定向,輸出到fd=n的數據改變流向輸出到file文件中,file不存在則建立,file存在則先清空再寫入數據
>file
,等價於1>file
,即標準輸出覆蓋重定向到file文件中[n]>>file
:追加式輸出重定向,輸出到fd=n的數據改變流向輸出到file文件的尾部,file不存在則建立,file存在則直接追加在文件尾部
>>file
,等價於1>>file
,即標準輸出追加劇定向到file文件中[n]<file
:輸入重定向,以讀取模式打開file文件並分配fd=n,file不存在則報錯
<file
,等價於0<file
,即直接從file中讀數據3<file <&3
,看不懂先跳過&>file
:這是特殊的重定向方式,表示將標準錯誤和標準輸出都重定向到file文件中,等價於>file 2>&1
&>>file
:這是特殊的重定向方式,表示將標準錯誤和標準輸出都追加到file文件中,等價於>>file 2>&1
另外,常常用於輸出的一個特殊目標文件是/dev/null,它是空設備,能夠直接丟掉全部寫入它的數據。
echo www.junmajinlong.com >/dev/null curl -I www.junmajinlong.com 2>/dev/null >/tmp/a.log cat </etc/fstab
一個常常用的技巧是清空文件的方式:
$ cat /dev/null >file $ >file
cat是一個命令,這個命令的源代碼中寫了一些代碼用來處理選項和參數。
cat -n /etc/fstab
cat命令開始執行後,會識別-n
選項,該選項會讓cat輸出時同時輸出行號,cat同時還會識別/etc/fstab
參數,cat會讀取參數指定的文件而後輸出。
若是沒有指定cat的文件參數,則cat默認會從標準輸入中讀取數據。默認的標準輸入是終端,因此在沒有改變標準輸入的流向時,會從終端讀取數據,也就是用戶輸入什麼字符,就讀取什麼字符,而後輸出什麼字符:
$ cat junmajinlong # 在終端輸入 junmajinlong # 在終端輸出 junma # 在終端輸入 junma # 在終端輸出 ^C
但用戶能夠改變標準輸入的來源。好比:
$ cat </etc/fstab
表示將標準輸入來源改成/etc/fstab文件,因而cat會從/etc/fstab中讀取數據。
另外,約定俗成的,會使用一個-
來表示標準輸入或標準輸出。
# 下面是等價的,都表示從標準輸入中讀取數據 cat - cat /dev/stdin cat
注:這並不是是一向正確的,只是約定俗成的大多數程序的代碼中都定義了-
相關的代碼處理。可參考相關命令的man手冊。如man cat中有一行:
With no FILE, or when FILE is -, read standard input.
輸入重定向是<
,除此以外還有<<、<<<
。
<<
符號表示here doc。也就是說,它後面跟的是一篇文檔,就像一個文件同樣,只不過這個文件的內容是臨時定義在<<
符號後的。here doc經常使用於指定多行數據輸入。
既然是文檔,就有文檔起始符號表示文檔今後開始和文檔終止符號表示文檔到此結束。起始符和終止符中間的內容所有是文檔內容。文檔內容會做爲標準輸入的數據被讀取。
文檔的起始符和終止符能夠隨意定義,但二者先後必須同樣。常見的符號是:
例如:
# here doc做爲標準輸入被讀取,而後被cat輸出 cat <<EOF hello world EOF # here doc的內容還會被cat覆蓋式輸出到指定文件 cat <<eof >/tmp/file hello world eof # here doc的內容還會被cat追加式輸出到指定文件 cat <<eof >>/tmp/file hello world eof # here doc和重定向符號的先後位置隨意 cat >>/tmp/file<<eof ... eof
另外,若是將起始符用引號包圍,則不會進行變量替換、命令替換、算術替換等。若是不用引號包圍起始符,則會進行替換。
a=333 cat <<eof $a eof cat <<"eof" $a eof
輸出結果:
333 $a
<<<
表示here string。也就是說該符號後面是一個字符串,這個字符串會做爲標準輸入的內容。
cat <<<"www.junmajinlong.com"
使用單引號包圍here string時,不會進行變量替換、命令替換等,使用雙引號包圍時會進行替換。
$ a=3333 $ cat <<<$a 3333 $ cat <<<"hello world$a" hello world3333 $ cat <<<'hello world$a' hello world$a
here string常能夠替代管道前的echo命令echo xxx|
。例如:
# 下面是等價的 echo hello world | grep "llo" grep "llo" <<<"hello world"
管道的用法:
cmd1 | cmd2 | cmd3...
每一個豎線表明一個管道。上面命令行表示cmd1的標準輸出會放進管道,cmd2會從管道中讀取進行處理,cmd2的標準輸出會放入另外一個管道,cmd3會從這個管道中讀取數據進行處理。後面還能夠接任意數量的管道。
Shell管道是Shell中最值得稱讚的功能之一,它以很是簡潔的形式實現了管道的進程間通訊方式,我我的認爲Shell處理文本數據的半壁江山都來自於豎線形式的管道。像其它編程語言,打開管道後還要區分哪一個進程寫管道、哪一個進程讀管道,爲了安全,每一個進程還要關閉不用的讀端或寫端,總之就是麻煩,而Shell的管道很是簡潔,豎線左邊的就是寫管道的,豎線右邊的就是讀管道的。
例如:
ps aux | grep 'sshd'
ps命令產生的數據(標準輸出)會寫進管道,只要管道內一有數據,grep命令就從中讀取數據進行處理。
那下面的命令中,grep從哪讀數據呢?
ps aux | grep '#' /etc/fstab
那想要讓grep既從/etc/fstab讀取數據,也從管道中讀取數據呢?
ps aux | grep '#' /etc/fstab /etc/stdin ps aux | grep '#' /etc/fstab -
tee命令可將一份標準輸入原樣拷貝到標準輸出和0或多個文件中。換句話說,tee的做用是數據多重定向。
NAME tee - read from standard input and write to standard output and files SYNOPSIS tee [OPTION]... [FILE]... DESCRIPTION Copy standard input to each FILE, and also to standard output. -a, --append ppend to the given FILEs, do not overwrite
如圖:
例如:
$ echo hello world | tee /tmp/file1 /tmp/file2 | cat $ echo hello world | tee -a /tmp/file3 >/dev/null
Bash還支持進程替換(注:有些Shell不支持進程替換)。
進程替換的語法:
<(cmd) >(cmd)
進程替換和命令替換相似,都是讓cmd命令先執行,由於它們都是在Shell解析命令行的階段執行的。
進程替換先讓cmd放入後臺異步執行,而且不會等待cmd執行完。
其實,每一個進程替換都是一個虛擬文件,只不過這個文件的內容是由cmd命令產生的(<(cmd)
)或被cmd命令讀取的(>(cmd)
)。
$ echo <(echo www.junmajinlong.com) /dev/fd/63
既然進程替換是文件,那麼它就能夠像文件同樣被操做。好比被讀取、被看成標準輸入重定向的數據源等等:
# cmd作數據產生者 $ cat <(echo www.junmajinlong.com) # 等價於cat /dev/fd/63 $ cat < <(echo www.junmajinlong.com) # 等價於cat </dev/fd/63 # cmd作數據接收者 $ echo hello world > >(grep 'llo') $ echo hello world | tee >(grep 'llo') >(grep 'rld') >/dev/null
test命令或功能等價的Bash內置命令[ ]
能夠作條件測試,若是測試的結果爲True,則退出狀態碼爲0。
此外,還可使用[[]]
來作條件測試,甚至let、$[]、$(())
也能夠作條件測試,但這裏暫不介紹。
這些條件測試經常使用在if、while語句中,也經常使用在cmd1 && cmd2 || cmd3
格式的命令行中。
用法示例:
sh_file=test.sh [ -x "$sh_file" ] && ./$sh_file || { echo "can't execute,exit...";exit 1; } test -x "$sh_file" && ./$sh_file || { echo "can't execute,exit...";exit 1; }
[]
中的條件測試須要和開閉中括號使用空格隔開,不然語法解析錯誤(解析成通配符號)。
[ ] test
沒有任何測試內容時,直接返回false。
true命令直接返回true,即退出狀態碼爲0。
false命令直接返回false,即退出狀態碼非0。
$ true $ echo $? # 0 $ false $ echo $? # 1
條件表達式 | 含義 |
---|---|
-e file | 文件是否存在(exist) |
-f file | 文件是否存在且爲普通文件(file) |
-d file | 文件是否存在且爲目錄(directory) |
-b file | 文件是否存在且爲塊設備block device |
-c file | 文件是否存在且爲字符設備character device |
-S file | 文件是否存在且爲套接字文件Socket |
-p file | 文件是否存在且爲命名管道文件FIFO(pipe) |
-L file | 文件是否存在且是一個連接文件(Link) |
條件表達式 | 含義 |
---|---|
-r file | 文件是否存在且當前用戶可讀 |
-w file | 文件是否存在且當前用戶可寫 |
-x file | 文件是否存在且當前用戶可執行 |
-s file | 文件是否存在且大小大於0字節,即檢測文件是否非空文件 |
-N file | 文件是否存在,且自上次read後是否被modify |
條件表達式 | 含義 |
---|---|
file1 -nt file2 | (newer than)判斷file1是否比file2新 |
file1 -ot file2 | (older than)判斷file1是否比file2舊 |
file1 -ef file2 | (equal file)判斷file1與file2是否爲同一文件 |
條件表達式 | 含義 |
---|---|
int1 -eq int2 | 兩數值相等(equal) |
int1 -ne int2 | 兩數值不等(not equal) |
int1 -gt int2 | n1大於n2(greater than) |
int1 -lt int2 | n1小於n2(less than) |
int1 -ge int2 | n1大於等於n2(greater than or equal) |
int1 -le int2 | n1小於等於n2(less than or equal) |
條件表達式 | 含義 |
---|---|
-z str | (zero)斷定字符串是否爲空?str爲空串,則true |
str -n str |
斷定字符串是否非空?str爲串,則false。注:-n可省略 |
str1 = str2 str1 == str2 |
str1和str2是否相同,相同則返回true。"=="和"="等價 |
str1 != str2 | str1是否不等於str2,若不等,則返回true |
str1 > str2 | str1字母順序是否大於str2,若大於則返回true |
str1 < str2 | str1字母順序是否小於str2,若小於則返回true |
條件表達式 | 含義 |
---|---|
-a或&& | (and)兩表達式同時爲true時才爲true。 "-a"只能在test或[]中使用,&&只能在[[]]中使用 |
-o或|| | (or)兩表達式任何一個true則爲true。 "-o"只能在test或[]中使用,||只能在[[]]中使用 |
! | 對錶達式取反 |
( ) | 改變表達式的優先級,爲了防止被shell解析,應加上反斜線轉義( ) |
if test-commands; then consequent-commands; [elif more-test-commands; then more-consequents;] [else alternate-consequents;] fi
test-commands既能夠是test測試或[]、[[]]
測試,也能夠是任何其它命令,test-commands用於條件測試,它只判斷命令的退出狀態碼是否爲0,爲0則爲true。
例如:
if [ "$a" ];then echo '$a' is not none;else echo '$a' undefined or empty;fi if [ ! -d ~/.ssh ];then mkdir ~/.ssh chown -R $USER.$USER ~/.ssh chmod 700 ~/.ssh fi if grep 'junmajinlong' /etc/passwd &>/dev/null;then echo 'User "junmajinlong" already exists...' elif grep 'malongshuai' /etc/passwd &>/dev/null;then echo 'User "malongshuai" already exists...' else echo 'you should create user,exit...' exit 1 fi
case經常使用於肯定的分支判斷。好比:
case "$1" in start) echo start;; stop) echo stop ;; restart) echo restart ;; reload | force-reload) echo reload;; status) echo status;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload}" exit 2 esac
case用法基本要求:
;;
結尾,不然出現分支穿透(因此;;
不是必須的)*)
有兩種for循環結構:
# 成員測試類語法 for i in word1 word2 ...;do cmd_list;done # C語言for語法 for (( expr1;expr2;expr3 ));do cmd_list;done
成員測試類的for循環中,in關鍵字後是使用空格分隔的一個或多個元素,for循環時,每次從in關鍵字後面取一個元素並賦值給i變量。
例如:
$ for i in 1 2 3 4;do echo $i;done 1 2 3 4 $ for i in 1 2 "3 4";do echo $i;done 1 2 3 4
C語言型的for語法中,expr1是初始化語句,expr2是循環終點條件判斷語句,expr3是每輪循環後執行的語句,通常用來更改條件判斷相關的變量。
for ((i=1;i<=3;++i));do echo $i;done 1 2 3
while test_cmd_list;do cmd_list;done
while循環,開始時會測試test_cmd_list
,若是測試的退出狀態碼爲0,則執行一次循環體語句cmd_list
,而後再測試test_cmd_list
,一直循環,直到測試退出狀態碼非0,循環退出。
例如:
let i=1,sum=0; while [ $i -le 10 ];do let sum=sum+i let ++i done
還有until循環語句,但在Shell中用的不多。
while循環常常會和read命令一塊兒使用,read是Bash的內置命令,可用來讀取文件,一般會按行讀取:每次讀一行。
例如:
cat /etc/fstab | while read line;do let num+=1 echo $num: $line done
上面的命令行中,首先cat進程和while結構開始運行,while結構中的read命令從標準輸入中讀取,也就是從管道中讀取數據,每次讀取一行,由於管道中最初沒有數據,因此read命令被阻塞處於數據等待狀態。當cat命令讀完文件全部數據後,將數據放入到管道中,因而read命令從管道中每次讀取一行並將所讀行賦值給變量line,而後執行循環體,而後繼續循環,直到read讀完全部數據,循環退出。
但注意,管道兩邊的命令默認是在子Shell中執行的,因此其設置的變量在命令執行完成後就消失。換句話說,在父Shell中沒法訪問這些變量。好比上面的num變量是在管道的while結構中設置的,除了在while中能訪問該變量,其它任何地方都沒法訪問它。
若是想要訪問while中賦值的變量,就不能使用管道。若是是直接從文件讀取,可以使用輸入重定向,若是是讀取命令產生的數據,可以使用進程替換。
while read line;do let num1+=1 echo $num1: $line done </etc/fstab echo $num1 while read line;do let num2+=1 echo $num2: $line done < <(grep 'UUID' /etc/fstab)