Shell 詳解

[TOC]
——主要來自《Red Hat Linux指南:基礎與系統管理篇》《Linux命令行與shell腳本編程大全.第3版》

1、shell

要點:命令行、標準輸入和標準輸出、重定向、管道、後臺運行程序、kill:終止後臺作業、文件名生成/路徑名展開、內置命令
Shell是用戶的系統界面,提供了用戶與內核進行交互操作的一種接口。它接收用戶輸入的命令並把它送去內核執行。實際上Shell是一個命令解釋器,它解釋由用戶輸入的命令並且把它們送到內核。

1.1 命令行

當在命令提示符後鍵入命令回車,shell將執行相應的程序。比如,鍵入ls後回車,shell開始執行名爲ls的工具。也可讓shell以同樣的方式執行其他類型的程序,如shell腳本、應用程序或自己編寫的程序。包含命令和參數的行稱爲命令行。命令是指在命令行上鍵入的字符,同時還指對應動作所調用的程序。

1.1.1 語法

命令行語法說明了行中各個元素的排列順序和間隔方式。當用戶鍵入命令回車後,shell將掃描命令行進行語法檢查。命令行上基本的語法格式如下:
command [arg1] ... [argn] RETURN
命令行上採用一個或多個空格來隔開每個元素。其中,command爲命令的名字,arg1到argn爲命令的參數,回車是終止命令的按鍵。語法格式中的方括號表明被括起來的參數爲可選項。並不是所有的命令都需要參數,有些命令就沒有參數,有些命令需要可變數目的參數,有些命令則需要特定數目的參數。選項是一種特殊類型的參數,前面通常爲一個或兩個連字符(“-”或“--”)。

1.命令名

一些有用的Linux命令行僅由命令名組成而不帶任何參數。例如,不帶任何參數的ls將顯示工作目錄下的文件列表。多數命令都帶一個或多個參數,當使用需要帶參數的命令時,若沒有帶參數,或帶了不正確的參數,或參數數目使用錯誤,系統都會返回用戶簡短錯誤信息提示,這些信息稱爲命令的用法消息(usage message)。

2.參數

命令行上,每一串不含空格字符的字符序列稱爲記號或字。參數是一種記號,如文件名、文本串、數字或命令處理的其他對象。
下面展示一個cp的命令行:

[root@QUFENGBIN ~]# cp temp tempcopy
參數都有編號,其中命令本身作爲參數0,它是命令行參數的開始。在這個例子中,cp爲參數0,temp爲參數1,tempcopy爲參數2。cp至少需要兩個字段(還可帶多個參數,但不能少於兩個),參數1是已存在的文件名,參數2是cp要創建或重寫的文件。這兩個參數都不是可選的,而是命令運行所必需的。
PS:shell一些關於參數的特殊字符
特殊字符 說明
$$ Shell本身的PID(ProcessID)
$! Shell最後運行的後臺Process的PID
$? 最後運行的命令的結束代碼(返回值)
$- 使用Set命令設定的Flag一覽
$* 所有參數列表。如"$*"用「"」括起來的情況、以"$1 $2 … $n"的形式輸出所有參數。
$@ 所有參數列表。如"$@"用「"」括起來的情況、以"$1" "$2" … "$n" 的形式輸出所有參數。
$# 添加到Shell的參數個數
$0 Shell本身的文件名
$1~$n 添加到Shell的各參數值。$1是第1參數、$2是第2參數…。
[root@aminglinux_01 test]# cat test.sh
#!/bin/sh
echo "number:$#"
echo "scname:$0"
echo "first :$1"
echo "second:$2"
echo "argume:$@"
[root@aminglinux_01 test]# sh test.sh aa bb
number:2
scname:test.sh
first :aa
second:bb
argume:aa bb

3.選項

選項(option)是改變命令執行效果的參數。可通過指定多個選項使得命令按照不同的方式執行。選項與特定的程序相關,並由命令行上調用的程序解釋,而非由shell解釋。
按照約定,選項是跟在命令之後其他參數(如文件名)之前的單獨的參數。多數命令的選項前面需要加一個連字符,但這個要求是與工具相關的,而與shell無關。GNU程序的選項前通常帶兩個連字符。例如,--help會生成用法消息。

4.選項的合併

當需要多個選項時,可將多個單字符選項組合成一個參數,以一個連字符開始,在選項前不要加空格。但是,這樣合併後的選項之前不能使用兩個連字符。對於合併選項的具體規則與具體的運行程序有關。多數命令的選項不分前後順序。

5.選項的參數

有些工具的選項本身也要帶參數。如,gcc(GUN的c編譯器)的-o選項必須後跟gcc產生的可執行文件名,通常選項與其參數間用空格隔開,如下:

[root@QUFENGBIN ~]# gcc -o prog prog.c

6.以連字符開始的參數

按照約定,工具的參數(如文件名)是允許以連字符開始的。這樣當某個文件的名字爲-l時,命令的意義將不明確。如果創建了這類文件,那麼,一些命令約定使用--(兩個連續的連字符)參數來表示選項的結束(和參數的開始)。如下示例:

[root@QUFENGBIN test]# touch -l
touch: invalid option -- 'l'
Try 'touch --help' for more information.
[root@QUFENGBIN test]# touch -- -l
[root@QUFENGBIN test]# ls -l
total 4
-rw-r--r-- 1 root root  0 Apr 28 11:41 -l
-rw-r--r-- 1 root root 14 Apr 26 16:26 test01
[root@QUFENGBIN test]# ls -- -l
-l
[root@QUFENGBIN test]# ls -l -- -l
-rw-r--r-- 1 root root 0 Apr 28 11:41 -l

1.1.2 處理命令行

當向命令行鍵入命令時,Linux的tty設備驅動程序(Linux操作系統內核的一部分)將檢查每個字符,來確定是否要立即採取動作。當鍵入的字符不需要採取立即的動作時,設備驅動程序將把字符存儲在緩衝區中,等待字符輸入。當按下回車鍵後,設備驅動程序將把命令行傳遞過程shell處理。

1.分析命令行

當shell處理命令行時,它將把命令行作爲一個整體來對待,並將其分成幾個組成部分。接着,shell將查找命令的名稱。命令行中提示符後的第1項(即參數0)通常爲命令名,因此shell將把命令行中從第1個字符到第一個空白字符(TAB或空格)之間的字符串作爲命令名。命令名(第1個記號)可採用簡單文件名或路徑名的方式指定。例如:

[root@QUFENGBIN test]# ls
-l  test01
[root@QUFENGBIN test]# /bin/ls
-l  test01

2.絕對路徑名與相對路徑名

當在命令行上輸入絕對路徑名或非簡單文件名的相對路徑名時(即輸入至少包含一條斜槓的路徑名),shell將在指定目錄下查找具有執行權限的對應文件。例如,輸入命令/bin/ls,shell將查找/bin目錄下具有執行權限且名爲ls的文件。當輸入的是一個簡單文件名時,shell在一組目錄中查找與該文件名匹配且具有執行權限的對應文件。shell並不是在所有目錄下搜索,而只在PATH變量設定的路徑下搜索。

1.1.3 執行命令行

1.進程

如果shell找到了與命令行上的命令具有相同名字的可執行文件,那麼,shell將啓動一個新的進程(進程是指Linux命令的執行),並將命令行上的命令名、參數、選項傳遞給程序(可執行文件)。當命令執行時,shell將等待進程的結束,這時shell處於非活躍狀態,稱爲休眠狀態。當程序執行完畢,就將它的退出狀態傳遞給shell,這樣shell就返回到活躍狀態(被喚醒),顯示提示符,等待下一個命令的輸入。

2.shell不處理的參數

由於shell不處理命令行上的參數,只是將它們傳遞給調用的程序,所以shell不知道選項和參數是否對程序有效。所以關於選項和參數的錯誤消息和用法消息都來自程序自身。也有些命令會忽略無效的選項。

1.2 標準輸入輸出

標準輸出(standard output)是指程序輸出信息(如文本)的地方。程序從來都不“知道”它發送到標準輸出的信息究竟送往何處。這些信息可以輸出到打印機、普通文件或屏幕。默認情況下,shell把命令的結果標準輸出到屏幕。shell也可以將輸出重定向到其他文件。
標準輸入(standard input)是程序信息的來源。而對於標準輸出,程序從不“知曉”信息的來源。默認情況下,程序的輸入來自鍵盤輸入。
對一個運行的程序來說,除了具有標準輸入和標準輸出外,通常還有錯誤消息輸出,稱爲標準錯誤輸出(standard error)。
命令並不知道標準輸入來自哪,也不知道標準輸出和標準錯誤輸出到哪。

1.2.1 作爲文件的屏幕

除了普通文件、目錄文件、硬鏈接和軟鏈接之外,Linux還有一種文件類型:設備文件(device file)。設備文件駐留在Linux文件結構中(通常位於目錄/dev中),用來代表外圍設備,如終端模擬器、顯示屏、打印機和硬盤驅動器。
在who工具顯示的內容中,登錄名後的設備名即爲屏幕的文件名。如果打開了多個窗口,每個打開的窗口都有對應的設備名。在這些窗口中運行tty工具即可得到它們各自的名稱。可以把這個設備文件看作是一個文本文件進行讀寫。向該文件寫入會在屏幕上顯示寫入的內容,而從該文件讀取就是從鍵盤上讀取鍵入的內容。

[root@QUFENGBIN test]# tty
/dev/pts/0
[root@QUFENGBIN test]# who
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 11:34 (111.113.5.18)

chsh:改變登錄shell
系統管理員在建立用戶賬戶時,將確定用戶第一次登錄系統或打開GUI環境下終端模擬器窗口時使用的shell(見/etc/passwd文件)。

root:x:0:0:root:/root:/bin/bash
.... .....
mysql:x:1000:1000::/home/mysql:/bin/false
www:x:1001:1001::/home/www:/bin/false
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

但在登錄系統後,用戶可以自己決定運行哪個shell。鍵入要使用的shell名(如bash,tcsh,或另一個shell),然後回車,則出現的提示符即爲新設定shell給出的,輸入exit命令可退回到上一個shell。
使用工具chsh可以永久地修改登錄shell。首先輸入命令chsh,然後在提示符後輸入口令和要使用的shell的絕對路徑名(如/bin/bash或者/bin/tcsh,或者另一個shell的路徑名)。

1.2.2 作爲標準輸入的鍵盤和作爲標準輸出的屏幕

當第一次登錄時,shell將其標準輸出發送到代表屏幕的設備文件中,採用這種方式輸出可以把輸出內容在屏幕上顯示出來。shell還將代表鍵盤的設備文件作爲標準輸入的來源,這樣命令會把在鍵盤上鍵入的任何內容作爲輸入接收。

1.2.3 重定向

重定向(redirection)是指改變shell標準輸入來源和標準輸出去向的各種方式。例如,默認情況下,shell將cat的標準輸入關聯到鍵盤,標準輸出關聯到屏幕。但也可以讓shell重定向任何命令的標準輸入或標註輸出,方法就是將輸入或輸出與某命令或某文件關聯,而不再是與代表鍵盤或屏幕的設備文件進行關聯。

1.重定向標準輸出

通過重定向符號(>)可以將shell命令的輸出重定向到指定的文件而不再是屏幕。重定向的命令格式爲:

command [arguments] >filename 

其中,command爲可執行程序(如應用程序或者是工具),arguments是可選參數,filename是shell要重定向輸出到的普通文件名。
重定向可能覆蓋文件!在重定向命令執行前,如果該文件已經存在,那麼shell將重寫並覆蓋其原來的內容。

2.重定向標準輸入

與重定向標準輸出一樣,也可以重定向標準輸入。通過重定向標準輸入符號(<)可以使shell將命令的輸入重定向爲來自指定的文件而不再是鍵盤。重定向標準輸入的命令格式爲:

command [arguments] <filename

其中,command爲可執行程序(如應用程序或者是工具),arguments是可選參數,filename是shell要重定向輸入來自的普通文件名。
將文件或者標準輸入作爲輸入的工具:將命令cat的輸入重定向到文件的執行結果與命令cat後跟文件名作爲參數的執行結果相同。像cat這樣具有這種特性的工具屬於Linux中的一類工具,這類工具還包括lpr、sort和grep。這類工具首先檢測調用它們的命令行。如果命令行上存在文件名,那麼這類工具就把指定的文件作爲輸入;否則,如果命令行上沒有指定文件名,那麼這類工具就把標準輸入作爲輸入。這種功能特性是該類工具自身所具有的,而與shell或者是操作系統無關。

3.noclobber:避免文件的重寫

shell提供了一種稱爲noclobber的特性,該特性可防止重定向時不經意地重寫了已存在的文件。

[root@QUFENGBIN test]# set -o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
-bash: test01: cannot overwrite existing file
[root@QUFENGBIN test]# set +o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
[root@QUFENGBIN test]# cat test01
noclobber test

在重定向輸出符號後跟管道符號,即使用符號組合“>|”可以忽略noclobber的設置。

[root@QUFENGBIN test]# set -o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
-bash: test01: cannot overwrite existing file
[root@QUFENGBIN test]# echo "noclobber test" >| test01
[root@QUFENGBIN test]# cat test01
noclobber test
[root@QUFENGBIN test]# set +o noclobber

4.向文件追加標準輸出

使用追加輸出符號(>>)可以向某個文件末尾添加新的內容,並且不改變原來已有內容。

[root@QUFENGBIN test]# cat test01
noclobber test
[root@QUFENGBIN test]# echo "noclobber test" > test01 ; cat test01
noclobber test
[root@QUFENGBIN test]# echo "noclobber test" >> test01 ; cat test01
noclobber test
noclobber test

5./dev/null:使數據消失

設備/dev/null是一個數據接收器(data sink),通常被稱爲位桶(bit bucket)。可以將不想看到或者是不想保存的數據重定向到/dev/null,這樣數據將不留痕跡的消失。

[root@QUFENGBIN test]# echo "noclobber test" > /dev/null

當從/dev/null中讀取數據時,將得到一個空字符串。

[root@QUFENGBIN test]# ls -l test01
-rw-r--r-- 1 root root 30 Apr 28 14:59 test01
[root@QUFENGBIN test]# cat /dev/null > test01
[root@QUFENGBIN test]# ls -l test01
-rw-r--r-- 1 root root 0 Apr 28 15:05 test01

1.2.4 管道

shell使用管道將一個命令的輸出直接連接到另一個命令的輸入。管道(pipe,有時被稱爲pipeline)的功能實現類似於下面的過程:首先將一個命令的標準輸出重定向到一個文件,然後將該文件作爲另一個命令的標準輸入。管道不會單獨處理每條命令,並且不需要中間文件。管道的符號爲一條豎線(|),命令行語法格式爲:

command_a [arguments] | command_b [arguments]

上面的命令行得到的結果與下面的這一組命令得到的結果相同:

command_a [arguments] > temp
command_b [arguments] < temp
rm remp

任何Linux工具都可以使用管道從命令行上指定的文件中接受輸入,也可以從標準輸入接受輸入。
有些使用管道的命令僅從標準輸入接受輸入,如工具tr(transtate,轉換)就只能從標準輸入接受輸入(即tr只能通過標準輸入接受輸入,而無法通過命令行參數來接受輸入)。
使用tr的最簡單格式:tr string1 string2
tr工具從標準輸入接受輸入,查找與string1匹配的字符,找到一個匹配就將string1的字符替換爲string2中對應字符。tr工具將它的輸出發送到標準輸出。

[root@QUFENGBIN test]# tr abc ABC < test01
ABCdefgABCDEFG
[root@QUFENGBIN test]# cat test01 | tr abc ABC
ABCdefgABCDEFG
[root@QUFENGBIN test]# tr abc ABC test01
tr: extra operand ‘test01’
Try 'tr --help' for more information.

1.過濾器
過濾器(filter)是將輸入數據流處理後在輸出數據流的一類命令。包含過濾器的命令行用一個管道將某個命令的標準輸出連接到過濾器的標準輸入,用另一個管道將過濾器的標準輸出連接到另一個命令的標準輸入。並不是所有的工具都可以用作過濾器。
2.tee:向兩個方向輸入
tee工具將標準輸入複製到文件和標準輸出。該工具被命名爲tee是因爲:它只有一個輸入,但輸出到兩個方向。

[root@QUFENGBIN test]# who | tee who.out | grep root
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)
[root@QUFENGBIN test]# cat who.out
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)

1.3 在後臺運行程序

前臺:當在前臺運行命令時,shell將一直等到命令執行完畢。纔會給出提示符使得你可繼續輸入下一個命令。當命令在後臺運行時,就不必等待該命令完成,可直接輸入另一個命令。
作業:作業(job)是由一個或者(可通過管道連接的)多個命令組成的序列。前臺只能有一個作業位於窗口或屏幕中,但可以有多個作業在後臺運行。同一時間運行多個作業是Linux的重要特性,這常被稱爲多任務特性。
作業編號與PID號:如果在命令行的末尾輸入與符號(&)後回車,那麼shell將在後臺運行這個作業。同時,shell會給這個作業分配一個作業編號(是個小數字),並將其顯示在方括號內。在作業編號之後,shell將顯示進程標識(process identification,PID)號,該號是由操作系統分配的一個大數。每個大數後面都標識了後臺運行的一條命令。然後,shell將顯示另一個提示符,這是便可以鍵入另一個命令。當後臺作業運行結束時,shell將顯示一個消息,這個消息的內容爲:已結束作業的作業編號和運行該作業的命令行。

1.將作業從前臺移至後臺

CONTROL+Z:程序掛起鍵,shell把前臺的作業掛起(阻止其繼續運行),並終止作業中的進程,將進程的標準輸入與鍵盤隔開。用bg命令後跟作業編號可以把掛起的作業放到後臺執行。如果僅有一個作業被掛起。那麼可以不必指明作業編號。只有前臺作業可以從鍵盤獲得輸入。爲了將鍵盤和後臺某個正運行的作業連接起來,必須把該後臺作業移至前臺。用fg命令後跟作業編號可以把後臺的作業移至前臺。不帶任何參數的fg命令可以將後臺唯一的作業移至前臺。

2.kill:終止後臺作業

命令行上輸入kill後跟進程的PID號(或者後跟%和作業編號),可以將後臺正在運行的進程(或作業)終止,使用中斷鍵(通常CONTROL+C)是不能實現其功能的。

1.4 文件名生成/路徑名展開

通配符和通配:當輸入包含特殊字符(也稱爲元字符)的部分文件名時,shell可以生成與已有文件的名字匹配的文件名。這些特殊的字符也常常被稱爲通配符(wildcard)。當某個特殊字符作爲參數出現在命令行上時,shell將該參數擴展爲有序的文件名列表,並將列表傳遞給命令行上調用的程序。包含特殊字符的文件名稱爲模糊文件引用(ambiguous file reference),因爲它們不與任何一個特定文件相關聯。對這些文件名操作的過程稱爲路徑名展開(path expansion)或者通配(globbing)。

1.4.1 特殊字符?

問號(?)是shell生成文件名的特殊字符,它與已有文件名中的某個單獨字符匹配。

[root@QUFENGBIN test]# ls test0?
test01
[root@QUFENGBIN test]# echo wh?.out
who.out
[root@QUFENGBIN test]# cat wh?.out
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)

1.4.2 特殊字符*

星號(*)的功能與問號的類似,不同之處在於,星號可以跟文件名中的任意多個(包括0個)字符匹配。

1.4.3 特殊字符[]

用方括號將一個字符列表括起來使得shell與包含列表中每個單獨字符的文件名進行匹配。例如,test?可匹配test後跟任何一個字符的文件名,而方括號更嚴格些,test[01]僅與test0、test1匹配。這裏,方括號定義了一個字符類(character class),該類由括號內的所有字符組成。
每個定義字符類只能替換文件名中的一個字符。方括號和其中的內容合起來的功能就如同問號一樣,但是隻能用字符類中的一個成員替換。
ps:左方括號後直接跟歎號(!)或脫字符(^)也可以定義字符類,該類與任何不在方括號內的字符匹配。例如,[^ab]*與不以a或b開始的文件名匹配。

[root@QUFENGBIN test]# ls
aa  ab  ac  ad  ba  bb  bc  bd  cc  dd
[root@QUFENGBIN test]# ls *[^ab]
ac  ad  bc  bd  cc  dd
[root@QUFENGBIN test]# ls [b-d]*
ba  bb  bc  bd  cc  dd

下面的例子表示ls工具不能翻譯模糊文件引用。第一個ls命令帶有參數aa*,shell將其擴展爲匹配的文件名aa,並將該名字傳遞給ls。第二個命令將*轉義,shell不再將*看作特殊字符,並將其傳遞給ls,ls報錯。大多數工具和程序像ls一樣也不能解釋模糊文件引用,該解釋工作由shell完成。

[root@QUFENGBIN test]# ls aa*
aa
[root@QUFENGBIN test]# ls aa\*
ls: cannot access aa*: No such file or directory
[root@QUFENGBIN test]# ls aaa*
ls: cannot access aaa*: No such file or directory

注意:模糊文件引用由shell進行擴展,而不是shell調用的程序進行擴展。上面的例子都不能“看到”模糊文件引用。shell對模糊文件引用進行擴展,並將擴展得到的文件列表傳遞給工具。在下面的echo例子中驗證了這一點,因爲它顯示了參數而不是模糊文件引用。

[root@QUFENGBIN test]# ls
aa  ab  ac  ad  ba  bb  bc  bd  cc  dd
[root@QUFENGBIN test]# echo a?
aa ab ac ad
[root@QUFENGBIN test]# echo *
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo a*
aa ab ac ad
[root@QUFENGBIN test]# echo .*
. ..
[root@QUFENGBIN test]# echo [a-m]*
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo [x-z]*
[x-z]*
[root@QUFENGBIN test]# echo *[a-d]
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo *[x-z]
*[x-z]
[root@QUFENGBIN test]# echo [a-d]\*
[a-d]*
[root@QUFENGBIN test]# echo [x-z]\*
[x-z]*

1.5 內置命令(內建命令)

外部命令
外部命令,有時候也被稱爲文件系統命令,是存在於bash shell之外的程序。它們並不是shell程序的一部分。外部命令程序通常位於/bin、/usr/bin、/sbin或/usr/sbin中。ps是一個外部命令,你可以使用which和type命令找到它。
04cfde2995914294fb7e96b6d702a7d9.png
當外部命令執行時,會創建出一個子進程。這種操作被稱爲衍生(forking)。
內置命令(內建命令)
內建命令和外部命令的區別在於前者不需要使用子進程來執行。它們已經和shell編譯成了一體,作爲shell工具的組成部分存在。不需要藉助外部程序文件來運行。可以利用 type 命令來了解某個命令是否是內建的。每個shell都有自己的內置命令集合。
輸入命令“man bash”,再輸入命令“/^SHELL BUILTIN COMMANDS”,shell將在內置命令部分搜索始於SHELL的行,從而可以查看內置命令的man頁內容。

2、Bourne Again Shell

要點:初始化文件、重定向標準錯誤輸出、編寫簡單的shell腳本、作業控制、操作目錄棧、參數和變量、進程、命令歷史機制、重新執行和編輯命令、別名、函數、控制bash特性和選項、處理命令行
Bourne Again Shell是一種命令解釋器,同時也是一種高級編程語言。作爲命令解釋器,它們通過提示符響應並處理用戶在命令行界面上輸入的命令。而作爲一種編程語言,它們將處理存放在所謂shell腳本文件中的命令。

2.1 shell基礎

內容包括編寫和使用初始化文件、重定向標準錯誤輸出、編寫和執行簡單的shell腳本、命令分割和分組、實現作業控制和操作目錄棧。

2.1.1 初始化文件

當啓動shell時,它將運行初始化文件初始化自己。具體運行哪個文件取決於該shell是一個登錄shell還是一個非登錄shell的交互式shell(比如通過命令bash),又或者是一個非交互式shell(用來執行shell腳本)。要想運行初始化文件中的命令,用戶必須具備讀權限。

1.登錄shell

登錄shell本來就屬於交互式shell。
/etc/profile:shell首先執行/etc/profile中的命令。通過設置這個文件,超級用戶可以爲全系統內的所有bash用戶建立默認特徵。
.bash_profile、.bash_login和.profile:然後shell依次查找~/.bash_profile、~/.bash_login和~/.profile,並執行它找到的首個文件中的命令。可以將命令放置在這些文件中的某個裏以覆蓋掉/etc/profile文件中的默認設置。
.bash_logout:當用戶註銷時,bash執行文件~/.bash_logout中的命令。這個文件包含了退出會話時需要執行的清理任務(比如刪除臨時文件)常用到的命令。

2.交互式非登錄shell

在交互式非登錄shell中並不執行前面提到的初始化文件中的命令。然而,交互式非登錄shell從登錄shell繼承了由這些初始化文件設置的shell變量。
/etc/bashrc:儘管不是通過bash直接調用,許多~/.bashrc文件還是調用/etc/bashrc。這種安排使得超級用戶可以爲全系統內的非登錄bash shell建立默認特性。
.bashrc:交互式非登錄shell執行~/.bashrc文件中的命令,而登錄shell的初始化文件(比如.bash_profile)通常會運行這個文件。這樣,登錄shell和非登錄shell都可以使用.bashrc中的命令。

3.非交互式shell

非交互式shell(如那些運行shell腳本的shell)並不執行前面描述的初始化文件中的命令。然而,這些shell從登錄shell那裏繼承了由這些初始化文件設置的shell變量。
BASH_ENV:非交互式shell查找環境變量BASH_ENV(或者當shell作爲sh調用時爲ENV),並執行由該變量命名的文件中的命令。

4.建立初始化文件

儘管有很多種初始化文件和shell,但是用戶通常只需要主目錄下的.bash_profile和.bashrc文件。.bash_profile中通過以下命令將爲登錄shell執行.bashrc(如果該文件存在)中的命令。

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

5..(句點)或者source:在當前shell中運行初始化文件

在編輯諸如.bashrc這類的初始化文件之後,要使這些修改起作用,用戶沒有必要註銷然後再次登錄,可以使用內置命令“.”(句點)或者source(這是兩個相同的命令)。與其他命令一樣,在命令行上,“.”後面必須有一個空格。內置命令“.”或者source用起來類似於運行一個shell腳本,但是這些命令將該腳本作爲當前進程的一部分運行。因此,當使用“.”或者source運行腳本的時候,在腳本中改變的變量也將影響到運行該腳本的shell。可以使用“.”或者source命令來運行任何shell腳本,而不僅僅是初始化文件,但是可能會帶來副作用(比如可能修改用戶依賴的shell變量的值)。如果將初始化文件作爲常規shell腳本運行,並且不使用“.”或source內置命令,那麼啓動腳本中創建的變量將只在運行該腳本的子shell中起作用。

2.1.2 符號命令

Bourne Again Shell以多種方式使用符號(、)、[、]和$。爲了避免混淆,下表列出了每種符號最通用的用法。
符號 命令
() 子shell
$() 命令替換
(()) 算術表達式計算,let的同義詞(當被括起來的值中包含等號時使用)
$(()) 算術展開(不用於被括起來的值中包含等號的情形)
[] test命令
[[]] 條件表達式,類似於[],但是添加了字符串比較

2.1.3 重定向標準錯誤輸出

除了標準輸出之外,命令還可以將輸出發送到標準錯誤輸出(standard error)。命令將錯誤消息發送到標準錯誤輸出,這樣就可以避免與發送到標準輸出的信息混淆在一起。
與處理標準輸出一樣,默認情況下,shell 將命令的標準錯誤輸出發送到屏幕上。除非將標準輸出和標準錯誤輸出中的某一個重定向,否則不能區分命令的輸出到底是標準輸出還是標準錯誤輸出
文件描述符:文件描述符(file descriptor)是程序發送輸出和獲取輸入的地方。當執行一個程序時,運行該程序的進程打開了 3 個文件描述符,分別是:0(標準輸入)、1(標準輸出)和 2(標準錯誤輸出)重定向標準輸出符號(>)是 1> 的簡寫,它通知 shell 將標準輸出重定向。類似地,< 是 0< 的簡寫,表示將標準輸入重定向。符號 2> 將標準錯誤輸出重定向
下面是個例子:
當運行 cat 時,如果所帶參數中的某個文件不存在,而另一個文件存在,那麼 cat 將發送一條錯誤消息到標準錯誤輸出,同時還將已存在的那個文件複製一份到標準輸出。除非將它們重定向,否則兩條消息都將出現在屏幕上。

[root@QUFENGBIN test]# cat y
This is y
[root@QUFENGBIN test]# cat x
cat: x: No such file or directory
[root@QUFENGBIN test]# cat x y
cat: x: No such file or directory
This is y

將命令的標準輸出重定向時,發送到標準錯誤輸出的輸出結果將不受影響,仍然出現在屏幕上。

[root@QUFENGBIN test]# cat x y > hold
cat: x: No such file or directory
[root@QUFENGBIN test]# cat hold
This is y
[root@QUFENGBIN test]# cat x y 1> hold1 2>hold2
[root@QUFENGBIN test]# cat hold1
This is y
[root@QUFENGBIN test]# cat hold2
cat: x: No such file or directory

複製文件描述符:在下一個例子中,1> 將標準輸出重定向到文件hold。然後,2>&1 聲明文件描述符 2 爲文件描述符 1 的副本。結果是, 標準輸出和標準錯誤輸出均被重定向到文件hold中。

root@QUFENGBIN test]# cat x y 1>hold 2>&1
[root@QUFENGBIN test]# cat hold
cat: x: No such file or directory
This is y

在上面這個示例中,1>hold 放在了 2>&1 的前面。如果將它們的順序顛倒的話,在標準輸出重定向到文件 hold 之前,標準錯誤輸出就已經拷貝了標準輸出的一個副本。這樣一來,就只有標準輸出被重定向到文件hold。

[root@QUFENGBIN test]# cat x y 2>&1 1>hold
cat: x: No such file or directory
[root@QUFENGBIN test]# cat hold
This is y
[root@QUFENGBIN test]# cat x y 2>&1
cat: x: No such file or directory
This is y

Bourne Again Shell所支持的重定向操作符如下表。

操作符 含義
<filename 將標準輸入重定向爲文件 filename
>filename 除非文件 filename 已存在並且設置了 noclobber 標記,否則標準輸出將被重定向到文件 filename 。如果文件 filename 不存在且沒有設置 noclobber 標記,那麼重定向操作將創建該文件
>|filename 即使文件 filename 已存在並且設置了 noclobber 標記,仍將標準輸出重定向到該文件
>>filename 除非文件 filename 已存在並且設置了 noclobber 標記,否則標準輸出將被重定向到文件 filename,並將內容添加到原文件的末尾。如果文件 filename 不存在且沒有設置 noclobber 標記,那麼將創建該文件
<&m 從文件描述符m複製標準輸入
[n]>&m 從文件描述符m複製標準輸出或者文件描述符n(如果命令中指定了n)
[n]<&- 關閉標準輸入或者文件描述符n(如果指定了n)
[n]>&- 關閉標準輸出或者文件描述符n(如果指定了n)

2.1.4 編寫一個簡單的shell腳本

shell 腳本是包含 shell 可執行命令的文件。shell 腳本中的命令可以是用戶在 shell 提示符後面輸入的任何命令。除了可以使用用戶在命令行下面輸入的命令之外,shell 腳本還可以使用控制流(control flow)命令(亦稱爲控制結構(control structure))。使用這組命令可以改變腳本中命令的執行順序。

1.chmod:使文件可執行

任何用戶想把文件名作爲命令行執行,都必須具備執行訪問權。如果該文件是一個 shel l腳本,用戶嘗試執行這個文件時,還必須具備讀訪問權限。而在執行一個二進制可執行文件(已編譯程序)時,並不需要讀訪問權限。

[root@QUFENGBIN test]# ll whoson
-rw-r--r-- 1 root root 41 Apr 30 20:09 whoson
[root@QUFENGBIN test]# cat whoson
date
echo "User Currently Logged In"
who
[root@QUFENGBIN test]# whoson
-bash: whoson: command not found
[root@QUFENGBIN test]# ./whoson
-bash: ./whoson: Permission denied
[root@QUFENGBIN test]# chmod u+x whoson
[root@QUFENGBIN test]# ./whoson
Mon Apr 30 21:36:18 CST 2018
User Currently Logged In
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-30 11:55 (118.74.57.231)
[root@QUFENGBIN test]# whoson
-bash: whoson: command not found
[root@QUFENGBIN test]# PATH=$PATH:.
[root@QUFENGBIN test]# whoson
Mon Apr 30 21:36:53 CST 2018
User Currently Logged In
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-30 11:55 (118.74.57.231)

2.#! 指定shell

在 shell 腳本文件的第一行可以放置一行特殊的字符串,告訴操作系統使用哪個 shell 來執行這個文件。因爲操作系統在試圖 exec 文件之前檢查該程序的開頭字符串,這些字符讓操作系統不必進行失敗的嘗試。如果腳本的前兩個字符是 #! ,那麼系統將這兩個字符後面的那些字符作爲用來執行該腳本的命令解釋器的絕對路徑名。它可以是任何程序的路徑名,而不僅僅是 shell 。

3.#開始一行註釋

4.執行 shell 腳本(fork、exec、source)

fork 和 exec 系統調用:用戶在命令行上輸入一條命令之後,shell 將 fork 一個新的進程,以創建當前 shell 進程的一個副本(子shell)。這個新的進程將試圖 exec(execute,執行)該命令。與 fork 一樣,exec 例程也是由操作系統執行(系統調用)。如果該命令是一個二進制可執行程序,比如編譯好的 C 程序,那麼 exec 執行成功,系統調用該可執行程序將新創建的子 shell 覆蓋掉。而如果這個命令是一個 shell 腳本,exec 執行失敗。當 exec 失敗時,將會假設該命令是一個 shell 腳本,子 shell 將執行腳本中的命令。與登錄 shell 期望從命令行讀取輸入不同,子 shell 從文件(shell 腳本)中獲取輸入。所以,如果不具備 shell 腳本文件的執行權限,那麼,用戶可以使用 bash 命令來 exec 一個 shell 直接運行該腳本,這樣就可以運行腳本中的命令。

5.fork、exec、source 區別

fork  ( ~/test/test01.sh) :如果 shell 中包含執行命令,那麼子命令並不影響父級的命令,在子命令執行完後再執行父級命令。子級的環境變量不會影響到父級。
說明:fork 是最普通的, 就是直接在腳本里面用 ~/test/test01.sh 來調用 test01.sh 這個腳本。運行的時候開一個 sub-shell 執行調用的腳本,sub-shell 執行的時候, parent-shell 還在。sub-shell 執行完畢後返回 parent-shell.。sub-shell 從 parent-shell 繼承環境變量,但是 sub-shell 中的環境變量不會帶回 parent-shell。
exec (exec ~/test/test01.sh):執行子級的命令後,不再執行父級命令。
說明:exec 與 fork不同,不需要新開一個 sub-shell 來執行被調用的腳本。被調用的腳本與父腳本在同一個 shell 內執行。但是使用 exec 調用一個新腳本以後, 父腳本中 exec 行之後的內容就不會再執行了。這是 exec 和source 的區別。
source (source ~/test/test01.sh):執行子級命令後繼續執行父級命令,同時子級設置的環境變量會影響到父級的環境變量。
說明:與 fork 的區別是不新開一個 sub-shell 來執行被調用的腳本,而是在同一個 shell 中執行。 所以被調用的腳本中聲明的變量和環境變量,都可以在主腳本中得到和使用。

[root@QUFENGBIN test]# chmod u+x test01.sh
[root@QUFENGBIN test]# chmod u+x test02.sh
[root@QUFENGBIN test]# cat test01.sh
#!/bin/bash
A=B
echo "PID for test01.sh before exec/source/fork:$$"
export A
echo "test01.sh: \$A is $A"
case $1 in
        exec)
                echo "using exec…"
                exec ./test02.sh ;;
        source)
                echo "using source…"
                . ./test02.sh ;;
        *)
                echo "using fork by default…"
                ./test02.sh ;;
esac
echo "PID for test01.sh after exec/source/fork:$$"
echo "test01.sh: \$A is $A"
[root@QUFENGBIN test]# cat test02.sh
#!/bin/bash
echo "PID for test02.sh: $$"
echo "test02.sh get \$A=$A from test01.sh"
A=C
export A
echo "test02.sh: \$A is $A"
[root@QUFENGBIN test]# ./test01.sh
PID for test01.sh before exec/source/fork:1259
test01.sh: $A is B
using fork by default…
PID for test02.sh: 1260
test02.sh get $A=B from test01.sh
test02.sh: $A is C
PID for test01.sh after exec/source/fork:1259
test01.sh: $A is B
[root@QUFENGBIN test]# ./test01.sh exec
PID for test01.sh before exec/source/fork:1261
test01.sh: $A is B
using exec…
PID for test02.sh: 1261
test02.sh get $A=B from test01.sh
test02.sh: $A is C
[root@QUFENGBIN test]# ./test01.sh source
PID for test01.sh before exec/source/fork:1262
test01.sh: $A is B
using source…
PID for test02.sh: 1262
test02.sh get $A=B from test01.sh
test02.sh: $A is C
PID for test01.sh after exec/source/fork:1262
test01.sh: $A is C

2.2 參數和變量

什麼是環境變量
bash shell 通過一個叫做環境變量(environment variable)的特性來存儲有關 shell 會話和工作環境的信息(這也是它們被稱爲環境變量的信息)。這項特性允許你在內存中存儲數據,一遍程序或 shell 在運行的腳本能夠輕鬆的訪問它們。在 bash shell 中,環境變量分爲兩類:全局變量、局部變量
全局環境變量
全局環境變量對於 shell 會話和所有生成的子 shell 都是可見的。局部變量則只對於創建它們的 shell 可見。Linux 系統在開始 bash 會話前就設置了一些全局環境變量。系統環境變量基本上都使用全大寫字母,以區別於普通用戶的環境變量。要查看全局環境變量,可使用 env 命令或者 printenv 命令。
變量
在 shell 中,shell 參數(shell parameter)與用戶可訪問的某個值相關,有幾種不同的 shell 參數。參數的名字由字母、數字和下劃線組成,常被稱爲 shell 變量(shell variable),或者簡稱爲變量(variable)。變量名必須以字母或者下劃線開頭,而不能是數字。
用戶創建的變量
用戶命令或賦值的 shell 變量稱爲用戶創建的變量(user-created variable)。用戶可以在任何時候修改用戶創建的變量的值,或者將其設置爲只讀。還可以將用戶創建的變量變成全局的。全局變量(又稱爲環境變量)可以被任何shell和從最初shell創建的其它程序訪問。這裏有一個命名約定,即全局變量只使用大寫字母,而其它變量則使用大小寫混合命名
關鍵字變量
關鍵字 shell 變量(keyword shell variable,簡稱爲關鍵字變量)對於 shell 而言,具有特殊的意義,它們的名字一般比較短而且有助於記憶。當用戶啓動 shell 的時候(比如登錄),shell 將從環境中繼承幾個關鍵字變量。HOME 和 PATH 就屬於這樣的變量。
位置參數和特殊參數
位置參數和特殊參數的名字並不像變量名。其中,大多數參數的名字都只由一個字符組成(比如1、?和#等),並且像其他所有變量一樣,在引用它們時一般在其名字前面加上美元符號(如$1、$?和$#)。這些參數的值反映了用戶與 shell 交互的不同方面。無論何時,用戶輸入的一行命令中的每個參數都將成爲位置參數(positional parameter)的值。用戶使用位置參數可以訪問命令行參數,在編寫 shell 腳本時將用到這項功能。內置命令 set 可以用來對位置參數賦值。其他經常需要用到的 shell 腳本值,比如最後一次執行的命令名、命令行參數的個數以及最近執行命令的狀態,這些值均保存在特殊參數(special parameter)中。用戶不能對特殊參數賦值。

2.2.1 用戶創建的變量/引用變量

[root@aminglinux_01 ~]# person=alex
[root@aminglinux_01 ~]# echo person
person
[root@aminglinux_01 ~]# echo $person
alex

命令echo $person顯示變量person的值,而不是顯示$person,這是因爲不會將$person作爲參數傳遞給echo。由於名字開頭出現$,因而shell識別出這是一個變量的名字,並將變量的值代入,同時將該值傳遞給echo。內置命令echo顯示該變量的值,而不是它的名字,然而echo絕不會知道用戶調用它時使用了變量
引用$:如果將開頭的$用單引號引起來,就可以阻止shell代入變量的值。雙引號不能阻止代入(會把引號之間的所有字符代入,包括單引號),而單引號和反斜槓符號\都可以阻止代入。

[root@aminglinux_01 ~]# echo $person
alex
[root@aminglinux_01 ~]# echo "$person"
alex
[root@aminglinux_01 ~]# echo '$person'
$person
[root@aminglinux_01 ~]# echo \$person
$person
[root@aminglinux_01 ~]# echo "'$person'"
'alex'
[root@aminglinux_01 ~]# echo "'\$person'"
'$person'

空格符:雙引號不能阻止變量替換,但是可以關閉大多數其他字符的特殊含義。

[root@aminglinux_01 ~]# person="alex and jenny"
[root@aminglinux_01 ~]# echo $person
alex and jenny
[root@aminglinux_01 ~]# person=alex and jenny
-bash: and: 未找到命令

當引用一個包含製表符和多個相連空格符的變量時,需要使用引號來保留這些空格。如果沒有將該變量用引號引起來,在將其傳遞給工具之前,shell將把每個空白字符構成的串壓縮成單個空格符:

[root@aminglinux_01 ~]# person="alex    and     jenny"
[root@aminglinux_01 ~]# echo $person
alex and jenny
[root@aminglinux_01 ~]# echo "$person"
alex    and     jenny

賦值中的路徑名展開:當引用一個包含未被引號引起來的特殊字符的變量時,所有 shell 均將這些字符解釋爲特殊字符。

[root@aminglinux_01 test]# ls
1.txt  2.txt  alex.1  alex.2
[root@aminglinux_01 test]# memo=alex*
[root@aminglinux_01 test]# echo $memo
alex.1 alex.2
[root@aminglinux_01 test]# echo "$memo"
alex*

PS:語法 $VARIABLE 是 ${VARIABLE} 的特殊情形,後者要更加通用,它將變量名用${}括起來。花括號將變量名隔離起來。將一個變量和一個字符串連接起來的時候,花括號是必要的:

[root@aminglinux_01 test]# PREF=counter
[root@aminglinux_01 test]# WAY=$PREFclock
[root@aminglinux_01 test]# FAKE=$PREFfeit
[root@aminglinux_01 test]# echo $WAY $FAKE

[root@aminglinux_01 test]# WAY=${PREF}clock
[root@aminglinux_01 test]# FAKE=${PREF}feit
[root@aminglinux_01 test]# echo $WAY $FAKE
counterclock counterfeit

Bourne Again Shell使用特殊的變量$1、$2、$3直到$9,通過位置參數引用命令行中的參數。如果需要引用第9個以後的參數,就必須使用花括號:${10}。命令的名字保存在$0中。
unset:刪除變量

2.2.2 變量屬性

1. readonly :使變量值不可更改

可以使用內置命令readonly確保某個變量的值不被改變。

[root@aminglinux_01 test]# person=jenny
[root@aminglinux_01 test]# echo $person
jenny
[root@aminglinux_01 test]# readonly person
[root@aminglinux_01 test]# person=alex
-bash: person: 只讀變量

當不帶參數使用內置命令readonly時,它會顯示所有隻讀變量的列表。

[root@aminglinux_01 test]# readonly
declare -r BASHOPTS="checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath"
declare -ir BASHPID
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -ir EUID="0"
declare -ir PPID="1018"
declare -r SHELLOPTS="braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor"
declare -ir UID="0"
declare -r person="jenny"

2. declare 和 typeset :爲變量賦予屬性

內置命令 declare 和 typeset(這是同一個命令的兩個名字)可以用來設置 shell 變量的屬性和值。下表列出了5種屬性。
屬性 含義
-a 聲明一個數組變量
-f 聲明一個函數名變量
-i 聲明一個整型變量
-r 聲明變量爲只讀,也可使用 readonly
-x 輸出變量(設置爲全局變量),也可用 export

列出變量屬性:如果不帶任何參數或者選項,那麼內置命令 declare 將列出所有 shell 變量。不帶任何參數運行 set 命令,也會得到同樣結果。如果內置命令 declare 帶有選項,但是沒有變量名作爲參數,那麼該命令將列出所有具有指定屬性集合的 shell 變量。

2.2.3 關鍵字變量

關鍵字變量既可以通過繼承而來,也可以在 shell 啓動時聲明並初始化。

1. HOME : 用戶主目錄

默認情況下,用戶主目錄就是用戶登錄之後的工作目錄。當用戶創建其賬號時,其主目錄就已經創建下來了,這個文件名存放在 /etc/passwd 中。

[root@aminglinux_01 test]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

代字符(~):shell 使用 HOME 的值來展開路徑名,該路徑名使用簡寫形式(代字符 ~ )來表示用戶的主目錄

[root@aminglinux_01 test]# cd
[root@aminglinux_01 ~]# pwd
/root
[root@aminglinux_01 ~]# echo ~
/root

2. PATH :shell 查找程序的路徑

在向 shell 中輸入一個絕對路徑名或者相對路徑名,而不是一個簡單的文件名作爲命令時,shell 就會在指定的這個目錄下,用指定文件名查找可執行文件。如果該路徑名對應的文件不存在,shell 將會報告“ command not found ”(命令找不到)錯誤。如果指定文件存在,但是用戶沒有執行權限,或者是用戶沒有 shell 腳本的讀權限和執行權限,shell 將報告“ Permission denied ”(權限禁止)錯誤。
如果使用簡單的文件名作爲命令,shell 將搜索某些目錄,以查找用戶想要執行的程序。shell 在幾個目錄中搜索文件,查找與該命令具有相同的名字、且用戶具有執行權限(對於編譯好的程序)或者具有讀權限和執行權限(對於shell 腳本)的文件。shell 變量 PATH 控制着這些搜索路徑。

[root@QUFENGBIN ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/mysql/bin:/usr/local/openssl/bin:/root/bin

3. MAIL :保存電子郵件的地方

變量 MAIL 包含了保存用戶郵件的文件的路徑名。保存用戶郵件的文件就是該用戶的 mailbox ,通常是 /var/spool/mail/name ,其中 name 是用戶的登錄名。如果設置了 MAIL 但是沒有設置 MAILPATH,那麼郵件到達 MAIL 指定的文件時,shell 將提醒用戶。
變量 MAILPATH 包含了一個用冒號隔開的文件名列表。如果設置了這個變量,那麼當這個列表中的任何一個文件發生改變的時候(比如郵件到達時),shell 都將提醒用戶。可以在該列表中任何一個文件名後面加上一個問號(?),問號後面跟着一條消息。如果有新郵件,shell 就會顯示該消息。它取代了用戶登錄系統時因有郵件而出現的“ you have mail ”消息。
變量 MAILCHECK 規定了shell以多大的額度(以秒計)檢查新郵件。默認值是60秒。如果將該值設置爲0,shell 將在顯示每個提示符之前檢查新郵件。

[root@QUFENGBIN ~]# echo $MAIL
/var/spool/mail/root
[root@QUFENGBIN ~]# echo $MAILPATH

[root@QUFENGBIN ~]# echo $MAILCHECK
60

4. PS1 :用戶主提示符

Bourne Again Shell 默認提示符是一個美元符號($)。如果以 root 身份運行 bash ,那麼提示符是 # 號。變量 PS1 保存了 shell 用來提示用戶輸入命令的提示符串。當用戶修改 PS1 的值時,用戶的提示符就會發生改變。

[root@QUFENGBIN ~]# echo $PS1
[\u@\h \W]\$
[root@QUFENGBIN ~]# PS1="[\u@\h \W \!]$ "
[root@QUFENGBIN ~ 7]$

PS1="[\u@\h \W !]$ "顯示的提示符的格式爲[user@host directory event]$,其中user是用戶名,host爲本機域名中第1個點號(.)之前的主機名,directory爲工作目錄的基名,event爲當前命令的事件編號。

[root@QUFENGBIN ~ 11]$ PS1="\h \$"
QUFENGBIN $PS1="\@ \u $ "
10:45 AM root $ PS1="\$ "
$
下表給出了一些 PS1 符號
符號 在提示符中的顯示
\$ 如果以root身份運行,就顯示爲#,否則就是$
\w 工作目錄的路徑名
\W 工作目錄的基名
\! 當前事件(歷史)編號
\d 按照“工作日/月/日期”格式顯示的日期
\h 機器的主機名,不包括域名
\H 機器全名,包括域名
\u 當前用戶的用戶名
\@ 按照“12小時,AM/PM”格式顯示的當前時間
\T 按照12小時制“HH:MM:SS”格式顯示當前時間
\A 按照24小時制“HH:MM”格式顯示當前時間
\t 按照24小時制“HH:MM:SS”格式顯示當前時間

5. PS2 :用戶次提示符

[root@QUFENGBIN ~]# echo $PS2
>
[root@QUFENGBIN ~ 17]$ echo 'hello world
> !!!'
hello world
!!!
[root@QUFENGBIN ~ 18]$ PS2="next: "
[root@QUFENGBIN ~ 19]$ echo "hello world
next: 2"
hello world
2

6. PS3 :菜單提示符、PS4 :調試提示符

[root@QUFENGBIN ~]# echo $PS3

[root@QUFENGBIN ~]# echo $PS4
+

7. IFS :分隔輸入字段(分詞)

IFS(Internet Field Separator,內部字段分隔符)shell變量指定了在命令行中用來分隔參數的字符,其默認值爲空格符、製表符和換行符無論IFS的值是什麼,用戶總可以使用一個或者多個空格符或者製表符分隔命令行中的不同參數,這裏假設這些字符並沒有被引用或者轉義。     
當爲IFS指派字符值的時候,這些字符也可以分隔字段,但是只有在展開的時候纔可以這樣。這種命令行解釋方式稱爲分詞(word splitting)。

[root@QUFENGBIN ~]# a=x:y:z
[root@QUFENGBIN ~]# cat $a
cat: x:y:z: No such file or directory
[root@QUFENGBIN ~]# IFS=":"
[root@QUFENGBIN ~]# cat $a
cat: x: No such file or directory
cat: y: No such file or directory
cat: z: No such file or directory

shell 根據在 IFS 中發現的分隔符劃分命令行中所有被展開的詞。如果不進行展開,就不會進行分詞。

[root@QUFENGBIN ~]# IFS="p"
[root@QUFENGBIN ~]# export VAR
[root@QUFENGBIN ~]# aa=export
[root@QUFENGBIN ~]# echo $aa
ex ort

儘管連續出現的多個空格或製表符被視爲一個分隔符,但是隻要出現別的字段分隔符字段,那麼每一次出現就被視爲一個分隔符。

8. CDPATH :擴大cd的範圍

使用 CDPATH 變量,用戶可以用一個簡單的文件名作爲參數傳遞給內置命令 cd ,就將工作目錄改變到某個目錄,而這個目錄並不是工作目錄的子目錄。
如果沒有設置 CDPATH,而只是指定了一個簡單的文件名作爲調用 cd 的參數,那麼 cd 將在工作目錄中查找具有與該參數相同的文件名的子目錄。而如果設置了 CDPATH,cd 將在 CDPATH 列表中的目錄搜索具有與該參數相同的文件名的子目錄。如果 cd 找到了一個子目錄,那麼該目錄就成爲工作目錄。
如果 CDPATH 中沒有包含工作目錄,並且在 CDPATH 中均搜索失敗,那麼 cd 將在工作目錄下搜索。如果希望 cd 首先搜索工作目錄,那麼可將空字符串作爲 CDPATH 的第1項。空字符串用兩個冒號(::)表示。如果內置命令cd 的參數是一個絕對路徑——以斜槓(/)開頭——則 shell 不考慮 CDPATH 。

2.2.4 shell特殊變量

2.3 特殊字符(待補充)

字符 用途 字符 用途
換行符 啓動命令的執行 ; 分隔命令
() 將命令分組給子shell執行,或者是標示函數 & 在後臺執行命令
| 管道 > 重定向標準輸出
>> 追加標準輸出 < 重定向標準輸入
<< Here文檔 * 模糊文件引用中的零個或者多個字符組成的串
? 模糊文件引用中的任何單個字符 \ 引用後面的字符
' 引用字符串,阻止所有替換 " 引用字符串,只允許變量替換和命令替換
`...` 執行命令替換 [] 模糊文件引用中的字符類別
$ 引用某個變量 .(內置句點) 執行命令(只在行首)
# 開始一行註釋 {} 用來封裝函數體
;(內置空串) 返回true &&(布爾“與”) 只有左邊的命令成功(返回的退出狀態值爲0)才執行右邊的命令
||(布爾“或”) 只有左邊的命令失敗(返回非零退出狀態值)才執行右邊的命令 !(布爾“非”) 反轉命令的退出狀態值
$() 執行命令替換(優先形式) [] 計算算術表達式的值

2.4 進程

進程(process)是一條命令在Linux上的執行。用戶登錄時啓動的shell與其他一樣,也是一條命令,或者進程。當用戶在命令行中輸入一個Linux工具名時,就啓動了一個進程。當用戶運行一個shell腳本的時候,系統創建另一個shell進程,併爲腳本的每行命令創建另外的進程。根據用戶調用腳本方式的不同,腳本既可以由當前shell運行,也可由當前shell的一個子shell運行。後一種方式更加普遍。如果用戶輸入一個shell內置命令如cd,此時系統並不會創建進程。

2.4.1 進程結構

fork系統調用:進程結構類似於文件結構,它也是一個層次式結構,有父進程、子進程甚至根進程(root)。父進程可以創建(fork)子進程,子進程又可以創建其他進程。術語fork,就像道路中的岔道口一樣,將一個進程變成兩個。創建一個新的進程的系統操作例程(或者系統調用)叫做fork。
當系統啓動時,Linux開始執行,它首先啓動init進程。這個進程稱爲自發進程(spontaneous process),其PID編號爲1。這個進程在進程結構中的地位與文件結構中根目錄的地位相同:它是系統進程和所有用戶進程的祖先。

2.4.2 shell的父子關係

用於登錄某個虛擬控制器終端或在GUI中運行終端仿真器時所啓動的默認的交互shell,是一個父shell。在CLI提示符後輸入 /bin/bash 命令或其他等效的 bash 命令時,會創建一個新的shell程序。這個shell程序被稱爲子shell(child shell)。子shell也擁有CLI提示符,同樣會等待命令輸入。運行shell腳本也能夠創建出子shell。
2aa93f97e4a20a6f634d8b4d1e971e66.png
171bfb4c6388e18ceda240c76f703c06.png

2.4.3 進程列表

可以在一行中指定要依次運行的一系列命令。這可以通過命令列表來實現,只需要在命令之間加入分號(;)即可。
e0993c7c87f0f3662e323041591a8b96.png
在上面的例子中,所有的命令依次執行,不存在任何問題。不過這並不是進程列表。命令列表要想成爲進程列表,這些命令必須包含在括號裏。
儘管多出來的括號看起來沒有什麼太大的不同,但起到的效果確是非同尋常。括號的加入使命令列表變成了進程列表,生成了一個子shell來執行對應的命令。
說明 進程列表是一種命令分組(command grouping)。另一種命令分組是將命令放入花括號中,並在命令列表尾部加上分號(;)。語法爲 { command; } 。使用花括號進行命令分組並不會像進程列表那樣創建出子shell。
要想知道是否生成了子shell,得藉助一個使用了環境變量的命令。這個命令就是 echo $BASH_SUBSHELL 。如果該命令返回 0 ,就表明沒有子shell。如果返回1 或者其他更大的數字,就表明存在子shell。
90e874d0d1ca76de59b7914c23d0b060.png

2.5 命令歷史機制

歷史機制是一項對C shell的改造功能,它維護了用戶最近發出的命令行(亦稱爲事件)的列表。內置命令history顯示了歷史列表的內容

2.5.1 控制歷史機制的變量

HISTSIZE變量的值決定了在某次會話期間歷史列表保存的事件數目。該值的範圍正常情況下是100到1000。
當從shell中退出時,最近執行的命令將保存在HISTFILE變量指定的文件中(默認是~/.bash_history)。下一次啓動shell時,將用這個文件來初始化歷史列表。變量HISTFILESIZE的值決定了保存在HISTFILE中的歷史行數(不一定與HISTSIZE相同)。HISTSIZE保存了會話期間記憶的事件數目,HISTFILESIZE則保存了會話之間記憶的數目,HISTFILE指定了保存歷史列表的文件。

[root@aminglinux_01 ~]# echo $HISTSIZE      #在一次會話期間保存的最大事件數目
1000
[root@aminglinux_01 ~]# echo $HISTFILE      #歷史文件的位置
/root/.bash_history
[root@aminglinux_01 ~]# echo $HISTFILESIZE  #會話之間保存的事件最大數目
1000

事件編號:Bourne Again Shell連續地爲每行命令指派了一個事件編號(event number)。如果在PS1中包含“!”,則可以將事件編號作爲bash提示符的一部分顯示出來。

[root@aminglinux_01 ~]# echo $PS1
[\u@\h \W]\$
[root@aminglinux_01 ~]# PS1="[\u@\h \W \!]\$"
[root@aminglinux_01 ~ 193]$

輸入history命令可以顯示歷史列表中的事件。事件列表按照最早的事件排在列表頂部的順序排列。

[root@aminglinux_01 ~ 194]$history | tail
  185  info bash builtin
  186  info bash
  187  man bash
  188  echo $HISTSIZE
  189  echo $HISTFILE
  190  echo $HISTFILESIZE
  191  echo $PS1
  192  PS1="[\u@\h \W \!]\$"
  193  history
  194  history | tail

2.5.2 重新執行和編輯命令

可以重新執行歷史列表中的任何事件。可以採用3種方式來瀏覽、修改和重新執行前面已執行的命令,即使用內置命令fc、使用感嘆號(!)命令以及Readline庫(暫不介紹),該庫使用vi編輯器來編輯和執行事件。

1. fc:顯示、編輯和重新執行命令

2. 使用感嘆號(!)引用事件

!!命令重新執行前一個事件,!\$符號表示前一個命令行中最後一個字。可以使用事件的絕對編號、相對編號或者事件中包含的文本來引用該事件。所有的事件引用(稱爲事件標誌符)均以感嘆號(!)開頭。感嘆號後面的一個或多個字符指定某個事件。
事件標誌符:事件標誌符指定了命令歷史中的某條命令。如下表:
標誌符 含義
! 除非後面緊跟着空格符、換行符、=或者(,否則立即開始某個歷史事件
!! 前一個命令
!n 歷史列表中編號爲n的命令
!-n 往前第n條命令
!string 最近以string開頭的命令行
!?string[?] 包含string的最近命令行,最後的?是可選的
!# 當前命令(目前敲入的部分)
!{event} event爲一個事件標示符。花括號將event與左右的文本隔開。比如,!{-3}3 表示後面跟着3的第3個最近執行的命令
[root@aminglinux_01 ~ 205]$ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!!
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!205
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!-1
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$history 10
  197  fc -l
  198  type -a fc
  199  ll /usr/bin/fc
  200  type -a fc
  201  history | tail
  202  history
  203  history | tail
  204  ll
  205  ls -l 3.txt
  206  history 10
[root@aminglinux_01 ~ 207]$!l
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 208]$!?ll?
ll
總用量 8
-rw-r--r--. 1 root root   25 5月   7 13:18 3.txt
-rw-------. 1 root root 1418 4月  14 21:55 anaconda-ks.cfg
drwxr-xr-x. 2 root root   60 5月   7 15:45 test

3. 字標誌符

字標誌符(word designator)指定了事件中的某個字或一組字。如下表:
字標誌符 含義
n 第n個字。一般情況下,字0就是命令名
^ 第1個字。(緊隨命令名)
$ 最後那個字
m-n 最後那個字從編號m到n的所有字,如果忽略m,那麼m默認爲0(0-n)
n* 從第n個到最後那個之間的所有字
* 除命令名之外的所有字。與1*相同
% 最近的匹配?string?搜索的那個字

這些字的編號從0開始(命令行上的第一個字,通常指命令名),接着是1(緊隨命令名的第1個字),直到n(命令行上的最後一個字)。
爲了指定前一個事件中某個特定的字,在事件標誌符(如!14)後面跟着一個冒號和一個表示該字在這個命令中的標號。如,!14:3指定了事件14中命令名後第3個字。使用一個用連字符隔開的兩個字標誌符,可以指定字範圍。

[root@aminglinux_01 ~ 209]$echo 1 2 3 4 5
1 2 3 4 5
[root@aminglinux_01 ~ 210]$echo !209:2
echo 2
2
[root@aminglinux_01 ~ 211]$echo !209:^
echo 1
1
[root@aminglinux_01 ~ 212]$!209:0 !209:$
echo 5
5
[root@aminglinux_01 ~ 213]$echo !209:2-4
echo 2 3 4
2 3 4
[root@aminglinux_01 ~ 214]$!209:0-$
echo 1 2 3 4 5
1 2 3 4 5

如果一個事件只包含了單個命令,那麼字編號就與參數編號對應。如果事件包含了多條命令,那麼對於第一條之後的命令來說,這種對應關係不再成立。

[root@aminglinux_01 ~ 215]$!209 ; echo 6 7 8 9
echo 1 2 3 4 5 ; echo 6 7 8 9
1 2 3 4 5
6 7 8 9
[root@aminglinux_01 ~ 216]$echo !215:7
echo echo
echo
[root@aminglinux_01 ~ 217]$echo !215:4-8
echo 4 5 ; echo 6
4 5
6
[root@aminglinux_01 ~ 218]$echo !215:6-7
echo ; echo

[root@aminglinux_01 ~ 219]$echo !$
echo echo
echo

4. 修飾符

有時候需要改變正要執行的命令的某些方面。通過在字標誌符後面或者事件標誌符後面(如果沒有字標誌符)放置一個或多個修飾符,就可以修改事件或事件的某個字。每個修飾符前面必須有一個冒號(:)。
替換修飾符:替換修飾符(substitute modifier)要比其他修飾符複雜。下面有個例子:

[root@aminglinux_01 ~ 222]$cad ~/3.txt
-bash: cad: 未找到命令
[root@aminglinux_01 ~ 223]$!!:s/cad/cat
cat ~/3.txt
this is a test
test
THIS

替換修飾符的語法:[g]s/old/new/
old爲原字符串(並非正則表達式),new表示代替old的那個字符串。替換修飾符用new替換old的第1次出現。在s前面放上g將造成全局替換,即將old的所有出現都替換掉。
其他修飾符:除替換修飾符外,修飾符還可以對由事件標誌符和可選字標誌符選取的事件部分進行簡單的編輯。可以使用多個修飾符,彼此之間用冒號(:)隔開。

[root@aminglinux_01 ~ 227]$ls ~/3.txt
/root/3.txt
[root@aminglinux_01 ~ 228]$!!:p
ls ~/3.txt
[root@aminglinux_01 ~ 228]$!!:h:p
ls ~
下表列出了除替換修飾符之外的事件修飾符
修飾符 功能
e(extension) 刪除掉除文件擴展名之外的所有內容
h(head) 刪除路徑名的最後部分
p(print-not) 顯示命令,但不要執行
q(quote) 引用該替換,以防止對其進行進一步的替換
r(root) 刪除文件擴展名
t(tail) 刪除掉路徑名中除末尾之外的所有元素
x 與q類似,除了單獨引用替換中的每個字

2.6 函數

shell函數類似於shell腳本,裏面存放了一系列可在稍後執行的命令。然而,因爲shell將函數存放在物理內存(RAM)而不是磁盤文件中,所以,shell訪問函數的速度要比訪問腳本的速度快得多。shell還對函數預處理(解析),因此其啓動速度也要比腳本快得多。最後,shell函數的執行和調用是在同一個shell中進行的。
shell函數的聲明可以放在~/.bash_profile初始化文件中,或者放在使用該函數的腳本中,又或者直接放在命令行。可以使用內置命令unset刪除函數。一旦用戶註銷,shell將不再保持這些函數。
PS:如果某個shell變量和函數名稱相同,那麼可用unset刪除shell變量。再次用相同的命令,就會刪除這個函數。
聲明一個shell函數的語法如下:
[function] function_name()
{
commands
)

2.7 控制bash的特性和選項

2.8 處理命令行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章