3.Shell的基本功能


3.Shell的基本功能
Bash是Bourne-Again Shell的縮寫。
Bourne Shell的內部命令在Bash中同樣適用。
3.1 Shell語法
3.1.1 Shell操作
shell讀取和執行命令時執行下面的操作:
從文件腳本或啟動"-c"選項的字符串參數中,或者用戶的終端上讀取輸入。
按照引用中所述規則把輸入分解為單詞和運算符。這些符號用元字符分隔。該步驟還進行別名擴展。
把符號解析為簡單命令和復雜命令。
進行各種shell擴展,並把擴展后的符號分解為文件名、命令和參數的列表。
進行必要的重定向,並把重定向運算符及其參數從參數列表中去掉。
執行得到的命令。
(可選的)等待命令結束並收集其退出狀態。

3.1.1.1 引用
引用在shell中用以去除某些字符或單詞的特殊含義,可以用來禁止對特殊字符的特殊處理,使得保留字不再被認為是保留字,或者禁止參數擴展。
shell中每個元字符在shell中都有特殊的含義,必須引用后才能代表其自身。
Bash中引用的三種機制:轉義字符、單引用、雙引用。

3.1.1.2 轉移字符
在Bash中沒有轉義的反斜杠\是轉義字符,它能保留其下一個字符的字面含義,除非這個字符是換行符。
如果\在行尾,且\沒有被引用,則\是行連續符,即\和換行符會從輸入流中被刪除,並被完全忽略掉。

3.1.1.3 單引用
把字符串用單引號'引用能保留引號內各個字符的字面含義。在單引號中不允許再出現單引號,即使它已經由反斜杠轉義。
3.1.1.4 雙引用
特殊含義的字符:
$獲取變量值
‘保持字符串的字面值
\\對\進行轉移,即保留一個\
!獲取歷史擴展
*獲取集合全部元素
@獲取集合元素個數
在雙引號中含有上述特殊字符時,該特殊字符的功能依然有效。
把字符串用雙引號"引用能保留引號內各個字符的字面含義,除非這些字符是$、'、\\、!。
在雙引號中$和'繼續保留其特殊的功能;
在雙引號中\后面的字符是$、'、"、\\或者換行符時,\的含義是將后面的字符原樣輸出(即去除后面字符的特殊含義,\自身會被刪除);
在雙引號中\后面的字符是沒有特殊含義的普通字符時,\被作為普通字符原樣輸出;
在雙引號中出現"時,需要給"加\(\")。
在雙引號中出現!時,會導致歷史擴展,如果想保留!的字面含義,則需要給!前增加\(即\!)。
特殊變量*和@在雙引號中特殊含義繼續有效。

3.1.2 ANSI標准C引用
$'string'的單詞會被特殊處理,會擴展成一個字符串。
轉義字符會按照ANSI C標准被替換,轉義字符包括:
\a 警告響鈴,
\b 退格刪除,
\e 轉義字符(不屬於ANSI C),
\f 走紙換頁,
\n 新行,
\r 換行,
\t 水平制表符,
\v 垂直制表符,
\\ 反斜杠,
\' 單引號,
\cx 一個控制字符CTRL-X,
\nn 由八進制數nnn代表的一個八位字符(例如:字符A,二進制01000001,十進制65,八進制101,十六進制41),
\xHH 由十六進制數HH代表的一個八位字符。
擴展的結果是一個單引用,就好像$符不存在一樣。
在很多命令(tr\awk的IFS等變量)中都需要指定單個字符,應該使用ANSI標准C引用,而不能用雙引號轉義。
例如: tr $'\n' ' ' file 替換命令tr,將換行符替換為空格,即把文件file的所有行用空格連接在一起。

3.1.2.1 Local專用的翻譯
雙引用的字符串在$后面,將使得該字符串根據當前的local被翻譯過來。
如果當前的local是C或者POSIX,則忽略$。
如果字符串被翻譯或者替換,則替換后的字符串是雙引用的。

3.1.3 注釋
在shell中,如果打開了內部命令shopt的interactive_comments選項,以#開頭的單詞將使得該單詞及本行所有其它單詞都被忽略。
默認,交互式shell已經打開了interactive_comments選項;如果關閉interactive_comments選項則不允許注釋。

3.2 Shell命令
Shell命令由簡單命令、復雜命令、管道命令的組成。
3.2.1 簡單命令
簡單命令包括空白符分隔的多個單詞,其結尾是一個shell控制運算符,
其中第一個單詞指定要執行的命令,后續單詞都是這個命令的參數。
簡單命令的返回狀態是POSIX中waitpid函數規定的推出狀態,如果該命令由一個信號n終止,則其退出狀態是128+n。

3.2.2 管道
管道是由控制字符|或|&分隔開的一系列簡單命令組成。
語法:[time [-p]] [!] 命令一 [[|或|&] 命令二 ... ]
管道里面每個命令的輸出都經由管道與下一個命令的輸入相連接;即每個命令都去讀上一個命令的輸出作為該命令的輸入。
time 指管道執行完畢后輸出其執行時間的統計信息,包括執行該命令所花費的總時間(鍾表時間)以及用戶和系統時間。
-p選項 使得輸出的形式和POSIX中的規定相同。
! 對返回狀態邏輯取反。
| 指命令一的標准輸出作為命令二的輸入。
|& 指命令一的標准錯誤輸出作為命令二的輸入。
管道里每個命令都是在自己的子shell中執行,管道的退出狀態是其中最后一個命令的退出狀態;
如果打開了pipefail選項,則管道的退出狀態是最后一個(最右側)返回非零的那個命令的狀態;如果所有命令都成功執行,則返回零。

3.2.3 命令隊列
命令隊列是由一個或者管道通過運算符;、&、&&、||連接而成,最后還可以(可選的)由;、&、或換行符結束。
與命令隊列是由控制運算符&&分隔的一個或多個管道。
語法:命令一 && 命令二 [ && 命令三 ...] 按照左結合方式執行
功能:當命令一執行成功(返回值為零)時才會依次執行后續命令,當遇到非零時,返回狀態值。
或命令隊列是由控制運算符||分隔的一個或多個管道。
語法:命令一 || 命令二 [ ||命令三 ...] 按照左結合方式執行
功能:當命令一執行不成功(返回值非零)時才會依次執行后續命令,當遇到零時返回狀態值。
與隊列和或隊列的返回值是最后一個被執行的命令的返回值。
由;分隔的命令將相繼執行,shell依次等待每個命令的結束,整個返回狀態是最后一個執行命令的返回狀態。
換行符(\n)作為命令分隔符時與;作為命令分隔符等價。
&作為命令分隔符時,則命令在后台執行,shell不等待命令的結束,其返回狀態為0(命令執行成功),異步執行命令的標准輸入將被重定向到/dev/null。

3.2.4 復合命令
復合命令是shell的編程結構體。
每個結構體都是以保留字或者控制運算符開頭,然后以與之對應的保留字或者控制運算符結束。
任何與復合命令相關的重定向都作用於該復合命令里邊的所有命令,除非顯示覆蓋。
Bash提供了循環結構、條件結構,以及將命令分組並將分組整體執行的機制。

3.2.4.1 循環結構
";"與換行符等價,即將復合命令寫成一行時必須加";",將復合命令寫成多行時不必加";"。
三種循環結構:
A.until
語法: until 測試命令; do 命令塊; done
until 測試命令
do
測試命令
done
功能:測試命令返回非零值(不成功)就執行命令塊。其返回值是命令塊中最后一個被執行的命令的返回值,如果命令塊未被執行則返回零。

B.while
語法: while 測試命令; do 命令塊; done
until 測試命令
do
測試命令
done
功能:測試命令返回零(成功)就執行命令塊。其返回值是命令塊中札后一個被執行的命令的返回值。如果命令塊沒有被執行則返回零。

C.for
語法1:for 變量 [ in 集合 ]; do 命令塊; done
for 變量 [ in 集合 ]
do
命令塊;
done
功能:將集合中的每個元素逐個賦值給變量,並執行一次命令塊。其返回值是命令塊中最后一個被執行的命令的返回值。如果對集合中沒有得到任何元素,則不執行任何命令,並返回零。
語法2:for (( 表達式一;表達式二;表達式三 )); do 命令塊; done
for (( 表達式一;表達式二;表達式三 ))
do
命令塊;
done
功能:計算表達式一,判斷表達式二是否為真(非0),不為真則退出for循環;為真則先執行命令塊,再執行表達式三,最后再次判斷表達式二是否為真...如此循環,直到表達式二不為真退出for循環。
如果省略某個表達式,則被省略的表達式為真,即省略表達式二,則for循環無法退出。死循環語法:for (( ; ; )); do 命令塊; done
其返回值是命 令塊中札后一個被執行的命令的返回值。如果表達式的值都是假的,則返回假。
continue命令:退出本次循環。
break命令:退出本層循環。

3.2.4.2 條件結構
A.if
語法:
if 測試命令一
then
命令塊一;
[elif 測試命令二
then
命令塊二; ]
...
[else
其他命令塊; ]
fi
功能:首先判斷測試命令一,如果是真,則執行命令塊一;如果是假,則依次判斷測試命令二。
測試命令二如果是真,則執行命令塊二;如果是假,則執行其他命令塊。
整個命 令的返回值是最后一個被執行的命令的返回值。如果沒有一個條件為真,則返回零

B.case
語法:
case 變量 in
模式一 | 模式二 ) 命令塊;;
模式三) 命令塊;;
*) 命令塊;;
esac
功能:case命令會選擇性的執行變量所匹配的第一個模式對應的命令塊。
"|"用來分隔多個模式,")"用來結束模式列表。
模式列表和其對應的命令塊叫做一個分句,每個分句都必須由";;"、",&"、";;&"結束。
如果打開了shell的 nocasematch選項,則匹配時將忽略字母的大小寫。
變量在匹配之前要經過波浪號擴展、參數擴展、命令替換、算術擴展以及引用去除, 而每個模式也要經過波浪號擴展、參數擴展、命令替換、算術擴展等步驟。
case 分句的數量量不限的,但是每個分句都以";;"、",&"、";;&"結束。朂先匹配的模式決定了哪個命令塊被執行。
分句結束符";;"、",&"、";;&"的區別:
如果使用了";;"來結束分句,則匹配第一個模式以后就不會再匹配其它模式。
如果使用了",&"來結束分句,則執行命令塊后,如果下面還有其它分句,就繼續執行該分句。
如果使用了";;&"來結束分句,則執行命令塊后,如果下面還有其它分句,就檢查其模式是否匹配;如果其模式為真,繼續執行其對應的命令塊。
返回值:如果任何模式都不匹配,該命令的返回狀態是零;否則,返回最后一個被執行的命令的返回值。

C.select
select與for語法格式一致。
語法:select 變量 [ in 集合 ]; do 命令塊; done
select 變量 [ in 集合 ]
do
命令塊;
done
功能:集合列表將會打印到標准錯誤輸出流中,並且每個元素前都會加上一個序數。默認集合為$@。
之后顯示"# ?"提示符,並且從標准輸入讀取一行的輸入,
如果輸入的為空,則重新顯示集合和提示符;
如果輸入了 EOF字符,select 命令將結束;(測試不成功)
輸入的值被存放在變量 REPLY 中;
如果輸入的是數字,則和元素的序數匹配,匹配成功后將元素值賦值給變量,匹配不成功時將空值賦值給變量。
每次選擇之后都會執行命令塊,然后再次顯示"# ?"提示符。如果在命令塊中遇到break 命令,則退出select 命令。
例子:用戶從當前目錄中選擇一個文件,並顯示用戶選擇的文件名及其序號
select fname in *;
do
echo you picked $fname \($REPLY\);
# break;
done

D.((...))
語法:(( 算術表達式 ))
功能:對算術表達式求值,等價於 let "算術表達式 "
返回值:如果這個值不是零,則返回狀態是零,否則返回1。

E.[[...]]
語法:[[ 條件表達式 ]]
功能: 對條件表達式求值,並根據其結果返回0或者1
條件表達式可以通過下面的運算符組合:
(表達式):使用()可以改變運算符的正常優先級。
!表達式:對表達式邏輯取反
表達式一 && 表達式二:對表達式一和表達式二進行邏輯與操作,即表達式一和表達式二均為真時結果為真,否則為假。
表達式一 || 表達式二:對表達式一和表達式二進行邏輯或操作,即表達式一和表達式二均為假時結果為假,否則為真。

3.2.4.3 命令組合
命令組合:將一系列命令放在一起作為整體執行。
A.()
把一系列命令放在括號中間就會創建一個子shell環境,並在這個子shell中執行該列表中的每個語句。
就是因為命令列表是在子shell中執行的,所以在子shell結束后,其中的變量賦值將不再有效。

B.{}
把一系列命令放在大括號中間,這一系列命令就會在當前shell中執行,而不是創建子shell。命令列表后面的;(或者換行符)是必須的。
多個命令在一行書寫時用“;”分隔,“;”只有分隔作用,無其他任何含義。
任何命令的末尾都可以添加“;”,單獨成行的命令也可以不用添加“;”,因為換行符也起到命令分隔的作用,所以二者是等價的。
為了代碼的美觀性、一致性、可讀性,建議在命令末尾使用“;”。

命令組合符()和{}的區別:
()命令在子shell中執行,{}命令在當前shell中執行。
{}是保留字,{}與命令列表之間必須用空白符或其它shell的元字符分開;
而()是運算符,()與命令列表之間沒有用空白符分開也會被shell當作獨立的符號。
這兩種結構的命令返回值都是其中命令列表的返回值。

3.2.5 協同進程
協同進程coprocess是指一個shell命令前面有coproc保留字,
協同進程在子shell中異步執行,
協同進程和其父shell進程之間有雙向的管道。
語法:coproc [NAME] 命令 [重定向]
功能:創建一個名為NAME的協同進程,如果不指定[NAME]時,默認名稱為COPROC。
如果命令是一個簡單命令時,則不能指定 NAME,否則它會被當作命令的第一個單詞。
當 coproc 執行時,shell會在父進程中創建一個名為 NAME 的數組變量。
命令的標准輸出通過管道和父進程的一個文件描述符相連;該文件描述符被賦給 NAME[0]。
命令的標准輸入通過管道和父進程的一個文件描述符相連;該文件描述符被賦給 NAME[1]。
這個管道是在命令當中指定的任何重定向之前就建立了。
這些文件描述符可以在shell命令和重定向中通過標准的單詞擴展而當作參數使用。
用來執行協同進程的子shell的進程號保留在數組變量 NAME[PID] 中。
可以使用內部命令wait來等待協同命令的結束。
協同進程的返回狀態是其中命令的返回狀態。

3.3 Shell函數
shell函數把一組命令與單一的名稱相關聯,以便以后執行。
在執行時,它們就和常規命令一樣。
如果shell函數的名稱被當作一個簡單命令使用,與它相關聯的命令就會被執行。
shell函數是在當前的 shell環境中執行的,而不是創建新的進程來執行。
函數語法: [ function ] func_name() 復合命令塊 [ 重定向 ]
說明:
func_name是定義的函數名;
保留字 funnction是可選的,如果有 function 這個保留字,則可以省略括號()。
復合命令塊是函數體,函數體是包含在{和}之間的命令列表,也可以是上面列出的任何復合命令。
每當函數名被指定為一個命令名時,復合命令塊就會被執行。
當函數被執行時,與之相關的重定向也會同時被執行。
可以使用內部命令 unset 的"-f"選項來取消函數的定 義。
除非發生語法錯誤,或者一個同名並且為只讀的函數已經存在,函數定義的返回值是零。
執行時,函數的返回值是函數體內最后一個被執行命令的返回值。
注意,由於歷史的原因,通常情況下,函數體外的大括號與函數體之間必須用空白符或者換行符分開。
因為大括號是保留字,但是只有它們與其中間的命令列表空格或其它shell元字符分隔時才能被識別為保留字。
此外,使用大括號時,其中間的命令列表必須用逗號、&或者換行符結束。
函數執行時,傳遞給它的參數成為它執行期間的位置參數。
能擴展為位置參數個數的特殊參數#將隨之更新。特殊參數 0不變。
在函數執行時,變量FUNCNAME 的第一個元素被設為函數的名稱。
除了DEBUG和RETURN這兩個陷阱沒有被繼承以外,函數和其調用者之間在shell執行環境所有其它方面都完全乨樣。
如果用內部命令declare設置了函數的trace屬性,或者設置了內部命令set 的functrace選項,則函數也會繼承調用者的DEBUG和RETURN陷阱。
如果在函數里面執行了內部命令return,則函數的執行將結束,並且返回到調用函數那里的下一個命令。
任何與RETURN陷阱相關聯的命令都將在執行恢復前被執行。
函數結束時,位置參數以及特殊參數#的值恢復到函數被執行以前的狀態。
如果return帶有一個數值型參數,則這個參數就是函數的返回值;否則,函數的返回狀態是其返回前最后一個被執行命令的返回狀態。
函數本地的變量可以用內部命令 local 來聲明。local變量只對函數及它使用的命令是可見的。
函數名稱及其定義可以用內部命令typeset或者declare加上"-f"選項來列出;而typeset 或者 declare 加上"-f"選項只列出函數名;如果打開了 shell的extdebug選項,則還會列出源文件和行號。
通過內部命令 export 的"-f"選項可以把函數導出,使得它們在子shell中自動得以定義。
注意,如果shell函數和變量同名,則可能導致傳給shell子進程的環境中有多個完全一樣的名字。如果這樣會引發問題,就需要避免同名。
函數可以是遞歸的。對遞歸調用的次數沒有限制。

3.4 Shell參數
參數是能存儲值的實體;它可以是一個名稱、一個數字、或者下面列出的特殊字符之一。
變量是名稱所代表的參數。
每個變量都有值以及零個或多個屬性。
屬性通過內部命令declare來設置。
參數通過賦值來設置。
空字符串也是一個有效的值。
參數一旦設置以后,只能通過內部命令unset才能取消設置。
參數賦值語法: 名稱=[值]
如果沒有給定值,則變量被賦於空字符串。
所有的值都會進行大括號擴展、參數和變量擴展、命令替換、算術擴展、以及引用去除;不會進行單詞擴展、文件名擴展。
特殊變量"$@"會進行單詞擴展。
如果啟用了變量的 integer 屬性,則把該變量當作算術表達式求值, 即使沒有使用 $((...) 擴展 。
賦值語句還可以作為內部命令 alias、declare、typeset、export、readonly 和local 的參數。
賦值語句給shell變量或數組元素賦值時,可以使用"+="運算符附加或增加到變量原來的值中。
如果變量啟用了integer屬性並且使用"+=",則按照算術表達式對值進行求值,並把它加入到變量原來的值中后再求值。
如果對數組變量進行復合賦值時使用了"+=",則變量原來的值不會被覆蓋,新的值被附加到數組中下標最大的那個元素的后面 ,或者在鍵值數組中新增一個鍵值對。
如果對字符串變量使用它,則把值進行擴展並附加在變量的值后面。

3.4.1 位置參數
位置參數是由除了單個0以外的一個或多個數字表示的參數;它是在 shell啟動時由其參數賦值的,並且可以用內部命令 set 來重新賦值。
第N個位置參數可以表示為 ${N};如果 N只含有一個數字,也可以 表示為$N。含有多於一個數字的位置參數在擴展時必須放在大括號中。
位置參數不可以通過賦值語句來賦值;而應該用內部命令set 或者shift來設置或刪除。
在執行shell函數時,位置參數會暫時被更換。

3.4.2 特殊參數
shell會對一些參數特殊處理,這些參數只能使用而不能對它們賦值。
* 擴展為從1開始的所有位置參數。
如果它出現在雙引號中,則擴展為一個包含每個參數的單詞,參數之間用特殊變量IFS的第一個字符分隔。
即"$*" 和 "$1c$2c..." 是等價的;其中,c是特殊變量IFS的第一個字符。
如果IFS沒有設置,則參數之間用空格分隔。如果IFS為空,則參數直接相連,中間沒有分隔。
@ 擴展為從1開始的所有位置參數。
如果它出現在雙引號中,則每個參數都擴展為一個單詞;即"$@" 和 "$1c" "$2c"...是等價的,其中,c 是特殊變量IFS的第一個字符。
如果IFS沒有設置,則參數之間用空格分隔。如果 IFS為空,則參數直接相連,中間沒有分隔。
如果這樣的雙引號擴展發生在單詞里面,則第一個參數擴展后與原單詞的開始部分連在一起,而最后一個參數擴展后與原單詞的最后一個部分連在一起。
如果沒有位置參數,則 "$@" 和 $@ 擴展后為空,也即它們會被刪除。
# 擴展為位置參數的個數,用十進制表示。
? 擴展為最近在前台執行的命令的退出狀態。
- (連字符)擴展為當前的所有選項;這些選項是啟動時給定的,或者通過內部命令set打開的,或者由shell本身打當的(例如 -i 選項)。
$ 擴展為當前shell的進程號。在子shell() 中,擴展為啟動shell的進程號,而不是子shell的進程號。
! 擴展為最近在后台(異步)執行的命令的退出狀態。
0 擴展為shell或者shell腳本的名稱。它是在shell初始化時設置的。
如果Bash啟動時帶有包含命令的文件名參數,$0就被設為該文件名。
如果Bash啟動時帶有"-c"選項,則 $0 被設為待執行字符串后面的第一個參數(如果這個參數存在)。
否則,它就是用來啟動Bash的文件名,即(命令行的)第一個參數。
_ (下划線)在shell啟動時,設為啟動shell的絕對路徑,或者在執行環境或參數列表中所傳遞的待執行的shell腳本的絕對路徑。
隨后,擴展為前一條命令的最后一個參數擴展后的值。
還可設為每個已執行命令的絕對路徑,這些路徑是啟動時指定的並且導入到命令的執行環境中。
檢查郵件時,這個變量保存郵箱文件的文件名。

3.5 Shell擴展
命令行被拆分成符號以后要進行擴展;
擴展的方式有七種:大括號擴展、波浪號擴展、參數和變量擴展、命令替換、算術擴展、單詞拆分、文件名擴展。
擴展的順序是:大括號擴展,波浪號擴展,參數、變量和算術擴展以及命令替換(按從左到右的順序),單詞拆分,以及文件名擴展。
如果系統支持,則還有另外一種擴展,即進程替換;它和參數、變量和算術擴展以及命令替換是同時進行的。
只有大括號擴展,單詞擴展,以及文件名擴展在擴展時能夠改變單詞的數目。其它的擴展都是單個單詞擴展成單個單詞,唯一例外的是對"$@"和"${name[@]}"的擴展。
所有擴展完成后再進行引用去除。

3.5.1 大括號擴展
大括號擴展是一種能夠生成任意字符串的機制;它和文件名擴展是相似的,但是生成的文件名不一定存在。
進行大括號擴展的模式在形式上有一個可選的前綴;其后是一組用逗號分隔的字符串,或者是一個序列表達式,它們都在一對大括號之間;最后是一個可選的后綴。
前綴部分將放在大括號中每個字符串的前面,而后綴將放在每個結果的后面,它們都是從左到右進行擴展的。
例如:
bash$ echo a{d,c,b}e
ade ace abe
序列表達式的形式是 {x .. y [ 增量 ] }
其中,x 和 y 是整數或者單個字符;而可選的增量是一個整數。
如果使用了整數,則擴展成 x 與 y 之間的 每個整數,包括 x 和 y。
所使用的整數可以用"0"開頭,以使每個量都有同樣的寬度。
當 x 或者 y有前導 的零時,shell會試圖強制讓每個生成的量都含有同樣多的數位,如果不同就在前面補零。
如果使用了字符, 那么表達式就擴展成在字母表中 x 和 y 中間的每個字母,包括 x 和 y。
注意,x 和 y 必須有同樣的類型。
如果指定了增量,它就是每個量之間的差值。默認的增量是 1 或者 -1,根據情況而定,如果 x 比 y 小,則增量是 1;如果 x 比 y 大,則增量是 -1。
大括號擴展在其它所有擴展之前進行;在其它擴展中特殊的字符都被保留下來。
它完全是字面上的擴展。
Bash不會對擴展的上下文字或大括號之間的文本進行任何語義解釋。
為了避免與參數擴展沖突,大括 號擴展不會識別字符串中的"${"。
格式正確的大括號擴展必須包含沒有被引用的起始和結束大括號,還有至少一個未被引用的逗號或者序列表達式。
格式不正確的大括號擴展不會被處理。
為了防止被認為是大括號擴展的一部分,"{"或者","可以用反斜杠轉義。
當要生成的字符串具有的公共前綴比上面的例子更長時,大括號擴展通常用來簡寫:
mkdir /usr/local/src/bash/{old,new,dist,bugs}
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how ex}}

3.5.2 波浪號擴展
如果一個單詞以未被引用的波浪號"~"開頭,則其后的所有字符,直到第一個未被引用的斜杠(如果有未被引用的斜杠),都被看作波浪號前綴。
如果波浪號前綴里面的字符都沒有被引用,則其中波浪號后面的所有字符就被當成一個可能存在的登錄用戶名。
如果這個登錄名是個空字符串,波浪號就被替換成shell變量中 HOME 的值。
如果沒有設置 HOME,則替換成執行該腳本那個用戶的主目錄。
否則,波浪號前綴就被替換成其中指定的那個登錄名的主目錄。
如果波浪號前綴是"~+",它就會被shell變量PWD的值取代。
如果波浪號前綴是"~-",它就會被shell變量OLDPWD的值取代(如果這個值已經設置)。
在波浪號前綴中,如果波浪號后面的字符包含數字N,前面可能還有"+"或"-",那么這個波浪號前綴就會被目錄棧中相應的元素取代,這個元素是內部命令dirs以波浪號前綴中波浪號后面各字符作為參數所要顯示的內容。
如果波浪號前綴中除去波浪號的部分僅僅包含數字N,而沒有前導的"+"或"-",則認為指定了"+"。
如果登錄名是無效的,或者波浪號擴展失敗,則該單詞不會被處理。
每個變量在賦值時都會檢查緊接着":"或者"="是否含有波浪號前綴。如果找到,就進行波浪號擴展。
所以,在給 PATH、MAILPATH 以及 CDPATH 賦值時,可以使用帶波浪號的文件名;這時,shell會用擴展后的量進行賦值。
下面的例子顯示了Bash是如何處理未被引用的波浪號前綴:
~ $HOME 的值
~/foo "$HOME/foo"
~fred/foo 用戶 fred 主目錄中的子目錄 foo,即"$PWD/foo"
~+/foo "$PWD/foo"
~-/foo "${OLDPWD-'~-'}/foo"
~N 命令"dirs +N"所顯示的字符串
~+N 命令"dirs +N"所顯示的字符串
~-N 命令"dirs -N"所顯示的字符串

3.5.3 Shell參數擴展
字符"$"引導參數擴展,命令替換和算術擴展。
將要擴展的變量名或符號可以放在大括號中。
大括號雖然是可選的,但卻可以保護待擴展的變量,使得緊跟在大括號后面的部分名稱不會被擴展。
如果使用了大括號,則與這匹配的結束半邊是第一個沒有用反斜杠轉義或不屬於引用字符串的"}","}"不能嵌入在算術擴展、命令替換、或者參數擴展之中。
參數擴展語法: ${參數}
參數擴展功能: 結果用參數的值替換。
如果參數是包含多個數位的位置參數,或者參數后面的字符不應該當成是整個名稱的不部分,則大括號是必須的。
如果參數的第一個字符是個感嘆號,就表示某個級別的間接變量。感嘆號必須緊跟在大括號后面才表示間接變量。
Bash使用后續變量的值作為新變量的名稱,然后擴展這個新的變量,並用其值進行替換,而不是后續變量的值。這叫做間接擴展。
下面將介紹的${!前綴*}和 ${!名稱[@]}屬於例外情況。
正確使用間接變量:
在很多其它語中,可以用 $$A 來表示以 $A為名稱的間接變量,而Bash中不可以,即使 ${$A}也不可以;
Bash只識別感嘆號形式的間接變量。不過,這個功能在其它的shell中可能沒有,
所以為了增強可移植性,可以這樣寫:eval echo \$$B
在下面介紹的每種情況中,名稱都要進行波浪號擴展、參數擴展、命令替換和算術擴展。
如果不是進行字符串擴展,使用下面的形式時,Bash會檢查參數是否已經設置或者為空;只有當參數尚未設置時才會在測試時忽略冒號后面的結果。
換句話說,如果包含了冒號,則這個運算符會測試參數是否存在,同時還會檢 查它是否為空;如果忽略了冒號,就只檢查變量參數是否存在。
${參數:?單詞} 如果參數沒有設置或者為空,則替換為單詞;否則替換為參數的值。
${參數:=單詞} 如果參數沒有設置或者為空,則把擴展后的單詞賦給參數,然后替換為參數的值。對位置參數和特殊參數,不可以這樣進行賦值。
${參數:?單詞} 如果參數沒有設置或者為空,就把擴展后的單詞(如果沒有給出單詞,則代之以一條大意相同的相信)寫到標准錯誤輸出中。如果當前的shell是交互式的,退出shell。否則,替換為參數的值。
${參數:+單詞} 如果參數沒有設置或者為空,不進行任何替換;否則,替換為擴展后的單詞。
${參數:偏移量}
${參數:偏移量:長度} 擴展為參數中從偏移量開始的不超過長度個字符。
如果沒有指定長度,擴展為參數中從偏移量開始的子字符串。
長度和偏移量都是算術表達式。這又叫做"子字符串擴展"。
長度的值必須是個大於或等於零的數字。如果長度的值是個小於零的數,它就會被當成參數所表示的字符串中從結尾開始的偏移量。
如果參數是"@",結果就是從偏移量開始的第長度個位置參數。
如果參數是帶有“@”或“*”下標的下標數組名,則結果是該數組中從 ${參數[偏移量]} 開始的長度個元素。
負的偏移量是從數組中比最大的下標大一的數字開始的。
對鍵值數組進行子字符串擴展的結果沒有定 義。
注意,負數的偏移量與冒號之間至少得有一個空格,這樣可以避免與“:-”擴展相混淆。
查找子字符串的下標是從 0 開始的;但是如果使用了位置參數,則默認從 1 開始。
如果使用位置參數時偏移量是 0,則把 $@添加到結果前面。
${!前綴*}
${!前綴@} 擴展為名稱中含有前綴的變量,以特殊變量 IFS 的第一個字符分隔。如果使用了"@",並且在雙引號內擴展,則每個變量都擴展成單獨的單詞。
${!名稱@}
${!名稱*} 如果名稱是個數組變量,擴展成名稱內數組下標或者鍵名列表。
如果名稱不是數組變量,則如果名稱已經設置就擴展為 0,否則擴展為空值。
如果使用了"@",並且在雙引號內擴展,則每個鍵或下標都擴展成單獨的單詞。
${#參數} 替換為參數擴展后所含的字符數目。如果參數是"*"或"@",則替換為位置參數的個數。如果參數是帶有下標"*"或"@"的數組名,則在做的為該數組中元素的個數。
${參數#單詞}
${參數##單詞} 單詞被擴展成一個模式,就像文件名擴展一樣。
如果這個模式與參數擴展后的值開始部分匹配,則替換的結果是該模式與參數擴展后的值朼短的匹配部分(指“#”)或者最長的匹配部分(指“##”)刪除以后的字符串。
如果參數是“*”或“@”,則模式刪除操作就對位置參數依次進行,擴展的結果就是所得到的位置參數列表。
如果參數是帶有下標“*”或“@”的數組名,則模式刪除操作就對數組元素依次進行,擴展的結果就是所得到的數組元素列表。
${參數%單詞}
${參數%%單詞} 單詞被擴展成一個模式,就像文件名擴展一樣。
如果這個模式與參數擴展后的值開始部分匹配,則替換的結果是該模式與參數擴展后的值最短的匹配部分(指“%”)或者最長的匹配部分(指“%%”)刪除以后的字符串。
如果參數是“*”或“@”,則模式刪除操作就對位置參數依次進行,擴展的結果就是所得到的位置參數列表。
如果參數是帶有下標“*”或“@”的數組名,則模式刪除操作就對數組元素依次進行,擴展的結果就是所得到的數組元素列表。
${參數/模式/字符串} 模式被擴展成一個模式,就像文件名擴展一樣。參數被擴展后,與模式匹配的最長部分用字符串來取代。
如果模式以“/”開頭,則所有與之匹配的部分都用字符串來取代;而通常情況下,只取代第一個匹配的部分。
如果模式以“#”開頭,它就只能和參數擴展后的開始部分匹配;如果模式以“%”開頭,它就只能和參數擴展后的結尾部分匹配。
如果字符串為空,則與模式匹配的部分將被刪除;這時,模式后面的 / 也可以省略。
如果參數是帶有下標“*”或“@”的數組名,則模式刪除操作就對數組元素依次進行,擴展的結果就是所得到的數組元素列表。
${參數^模式}
${參數^^模式}
${參數,模式}
${參數,,模式} 這種擴展會改變參數中字母符號的大小寫。模式被擴展成一個模式,就像文件名擴展一樣。
運算符“^”把匹配部分的小寫字母轉換為大寫;運算符“,”把匹配部分的大寫字母轉換為小寫。
“^^”和“,,”擴展轉換參數擴展后的每個匹配的字符,而“^”和“,”擴展只轉換參數擴展后第一個匹配的字符。
如果省略了模式,則將它當成“?”,這就與每個字符都匹配。
如果參數是“*”或“@”,則大小寫轉換操作就對位置參數依次進行,擴展的結果就是所得到的位置參數列表。
如果參數是帶有下標“*”或“@”的數組名,則模式刪除操作就對數組元素依次進行,擴展的結果就是所得到的數組元素列表。

3.5.4 命令替換
命令替換是用命令的輸出取代命令本身。
語法:$( 命令 ) 或者 ` 命令 `
功能:Bash先執行命令,並把該命令的標准輸出中最后面的換行符刪除,用結果取代命令替換。
這個時候中間的換行符不刪除,但是它們可能在單詞拆分時被刪除。
命令替換形式 $(cat 文件名) 可以用與其等價但速度更快的 $(< 文件名) 來取代。
如果使用了舊式的反引號,反斜杠就保留其字面含義,除非它后面是“$”、“`”夢或者“\”。第一個不是出 現在反斜杠后面的反引號結束命令替換。
如果使用了 $( 命令 ) 的形式,則括號中間的所有字符組成命令,沒有任何一個會被特殊處理。
命令替換可以嵌套。
使用反引號形式進行嵌套時,里面的反引號需要用反斜杠轉義。
如果命令替換出現在雙引號之間,則其結果不會進行單詞拆分和文件名擴展。

3.5.5 算術擴展
算術擴展可以對算術表達式求值並替換成的求值的結果。
語法:$(( 表達式 ))
功能:處理表達式時,就好像它在雙引號之間,但圓括號中間的雙引號不會被特殊處理。
表達式中的所有符號都會進行參數擴展,命令替換和引用去除。
算術表達式也可以嵌套。
求值時按照Shell的算術運算規則進行。
如果表達式是無效的,Bash就會在標准錯誤輸出中打印一條錯誤信息,並且不會進行任何替換。

3.5.6 進程替換
如果系統支持命名管道(fifo)或能夠以"/dev/fd"方式來命名打開的文件,則也就支持進程替換。
語法:<( 命令列表 ) 或者 >( 命令列表 )
功能:將"/dev/fd"下的某個文件的名稱作為一個參數轉遞給當前的命令。
在運行時,進程的命令列表的輸入和輸出與一個命名管道或者“/dev/fd”目錄里面的某個文件相關聯。
如果使用了 >( 命令列表 ) 這種形式,對該文件的寫入就為命令列表提供了輸入。
如果使用了 <( 命令列表 ) 這種形式,在需要得到命令列表的輸出時,應該去讀取作為參數轉遞的文件。
注意,< 或 > 與左邊括號之間不能有任何空格,否則這種結構就會被解釋成重定向。
如果系統支持,進程替換就和參數及變量擴展、命令替換、還有算術擴展同時進行。

3.5.7 單詞拆分
在單詞拆分時,shell會掃描參數擴展、命令替換和算術運算的結果,如果它們不是在雙引號之間進行的。
shell會把$IFS中的每個字符都當成分隔符,並按照這些字符把其它擴展的結果拆分成單詞。
如果$IFS沒有設置,或者它的值和默認的 <space><tab><newline> 完全一樣,
那么在前述擴展的結果中開頭或結尾出現的由 <space>、<tab>、或者 <newline> 構成的序列就會被忽略;
而由$IFS中任意字符組成的序列如果不是出現在頭部或尾部就成為單詞的分隔符。
如果$IFS的值不是默認的,並且含有由空格和制表符這兩個空白符(稱為$IFS分隔符)構成的空白符,則如果在單詞的頭部或尾部出現就會被刪除。
$IFS中除了空白符以外的任何字符,包括與其毗鄰的空白符,就成為字段的分隔符;
由$IFS空白符組成的序列也是分隔符。如果$IFS的值為空,則不拆分單詞。
明確表示的空參數 ("" 或 '')會被保留下來。
由沒有設置值的參數擴展后得到的未被引用的隱含空參數會被刪除。
如果沒有設置值的參數在雙引號之間擴展,則結果的空值會被保留。
注意,如果沒有進行擴展,則也不會進行單詞拆分。

3.5.8 文件名擴展
單詞拆分以后,BASH會在每個單詞中搜索字符"*"、"?"、和"[",除非打當了"-f"選項。
如果找到其中一個,則把這個單詞當作一個模式,並把與之匹配的文件名按字母順序排列來取代它。
如果沒有找到匹配的文件名,並且禁止了SHELL的 nullglob 選項,則不處理該單詞;
如果打開了nullglob選項,並且沒有找到匹配的文件名,這個單詞就會被刪除。
如果打開了failglob 選項,並且沒有找到匹配的文件名,則打印一條錯誤信息,並且不執行當前命令。
如果打當了nocaseglob 選項,則匹配時不區分字母字符的大小寫。
如果一個模式用於生成文件名,則對於文件名開頭或緊跟在斜杠后面的“.”必須明確匹配,除非打當了 shell的dotglob選項。
匹配文件名時,斜杠也必須明確匹配。
在其它情況下,".“不會特殊處理。
關於 nocaseglob、nullglob、failglob、以及 dotglob 選項,請參見shopt命令。
shell變量GLOBIGNORE可以用來限制匹配的文件名。
如果設置了 GLOBIGNORE,並且匹配的文件名中又與GLOBIGNORE中的模式匹配的,將會從列表中刪除。
如果設置了 GLOBIGNORE,並且它的值不為空,則文件名"."和".."總是被忽略。
但同時,給GLOBIGNORE設置一個非空的值會讓shell的dotglob選項也生效,使得所有以"."開頭的文件名也會被匹配。
為了像以往一樣忽略以“.”開頭的文件名,可以讓“.*”成為GLOBIGNORE 的模式之一。
如果取消GLOBIGNORE設置,則 dotglob 也會被取消。

3.5.8.1 模式匹配
除了下面介紹的特殊模式字符,模式中出現的任何其它字符都與其自身相匹配。
模式中不能使用nul字符。反斜杠可以用來轉義其后面的字符;用於轉義的字符在匹配時會被忽略。
特殊模式字符必須轉義以后才能按照字面含義匹配。
特殊模式字符有如下含義:
* 匹配任何字符串,包括空字符串。
如果打開了shell的 globstar 選項,並且在文件名擴展的行文中使用了“*”,連續使用兩個“*”字符的模式會匹配所有文件以及零個或多個文件夾和子文件夾。
如果連續兩個“*”並且后面有個“/”,那么它只匹配文件夾和子文件夾。
? 匹配任意單個字符。
[...] 匹配方括號中的任一字符。由連字符分隔的一對字符是一個“范圍表達式”;
在當前語言區域(locale)和字符集中,按順序在這兩個字符之間的任一字符,包括這兩個字符本身,都會被匹配。
如果"["之后的第一個字符是"!"或者"^",則匹配沒有出現在方括號中的任一字符。
如果要匹配"-",可以把它放在方括號中第一個或最后一個位置。
如果要匹配“]”,可以把它放在方括號中第一個位置。
范圍表達式中字符地排列順序是由當前的語言和shell變量LC_COLLATE決定的。
例如,在默認的 C 語言區域中,“[a-dx-z]”和"[abcdxyz]"是等價的。
在許多語言區域中,字符都是按詞典順序排列的;在這些語言區域中,“[a-dx-z]“和“[abcdxyz]“通常是不等價的,例如,它可能與“[aBbCcDdxXyYz]”等價。
為了方括號表達式中使用在傳統意義上的范圍,可以把環境變量LC_COLLATE 或 LC_ALL 設為"C"以強制使用 C 語言區域。
在“[”和“]”中間,可以用 [:類別:] 這樣的語法形式來指定“字符類別”;其中的類別是POSIX標准中定義的下列類別之一:
alnum 匹配所有字母和數字
alpha 匹配所有字母
ascii 匹配所有(ASCII)字符
blank 匹配所有空白符
cntrl 匹配所有控制字符 (即ASCII中的二十個字符)
digit 匹配所有的數字(0-9)
graph 匹配所有可顯示字符(可打印字符中,空格和退格符不可顯示)
lower 匹配所有小寫字母
print 匹配可打印字符(非控制字符都可打印)
punct 匹配所有標點符號
space 匹配空格
upper 匹配所有大寫字母
word 匹配單詞里面的字符(大小寫字母)
xdigit 匹配所有十六進制數字(0-9 和 A-F)
字符類別和該類別中的任一字符匹配。字符類別 word 匹配字母,數字和下划線。
在“[”和“]”中間, 還可以用 [=c=] 這樣的語法形式來指定一個等價的字符類,它匹配當前語言中區域為c的所有字符。
在“[”和“]”中間,[.名稱.]這樣的語法形式和名稱為名稱的語 區域相匹配。
如果用內部命令 shopt 打開了shell的 extglob 選項,Bash就可以識別幾個擴展的模式匹配運算符。
在下面的敘述中,模式列表是由“|”分隔的一個或多個模式。可以使用下面的一個或多個子模式構成復合模式。
?(模式列表) 與模式列表匹配零次或一次。
*(模式列表) 與模式列表匹配零次或多次。
+(模式列表) 與模式列表匹配一次或多次。
@(模式列表) 與模式列表中的模式之一匹配。
!(模式列表) 與模式列表中的任一模式之外的字符匹配。

3.5.9 引用去除
經過上述擴展以后,對於所有沒有被引用的字符"\"、"'"、以及",如果它們不是由上述任何一種擴展產生的,就會被刪除。

3.6 重定向
在執行命令之前,shell可能會用特殊的方式把它的輸入和輸出重新定向。
重定向還可以用來在當前的shell執行環境中打當和關閉文件。
下面講的重定向運算符可以出現在一個簡單命令的前面、中間的任何地方,也可以出現在命令的后面。
重定向按照出現的順序從左到右處理。
在下文中,如果省略了文件描述符,並且重定向運算符的第一個字符是“<”,則指的是重定向標准輸入(文件描述符為 0);
重定向運算符的第一個字符是“>”,則指的是重定向標准輸出(文件描述符為 1)。
在下文中,重定向運算符后的單詞,如果沒有特別說明,要進行大括號擴展、波浪號擴展、參數擴展、 命令替換、算術擴展、引用去除、文件名擴展、以及單詞拆分。
如果擴展后產生多個單詞,Bash就會報錯。
注意,重定向的順序是很重要的。
例如,ls > 目錄列表 2>&1 會把標准輸出(文件描述符為1)和標准錯誤輸出(文件描述符為2)重定向到文件目錄列表中,
而命令 ls 2>&1 > 目錄列表 僅把標准輸出重定向到文件目錄列表中,因為在標准輸出被重定向到文件目錄列表中之前,標准錯誤輸出已經被復制到標准輸出中了。
在進行重定向時,Bash會對下表列出的幾個文件特殊處理:
/dev/fd/fd 如果fd是個有效的整數,則復制文件描述符fd。
/dev/stdin 復制文件描述符 0。
/dev/stdout 復制文件描述符 1。
/dev/stderr 復制文件描述符 2。
/dev/tcp/主機名/端口號 如果主機名是個有效的主機名稱或因特網地址,並且端口號是整數型的端口號或服務名稱,Bash會試圖打開一個到相應套接字端口的TCP連接。
/dev/udp/主機名/端口號 如果主機名是個有效的主機名稱或因特網地址,並且端口號是整數型的端口號或服務名稱,Bash會試圖打開一個到相應套接字端口的UDP連接。
如果不能打開或者創建文件,重定向就會失敗。要謹慎使用比9大的文件描述符進行重定向,因為它們可能會和shell內部使用的文件描述符相沖突。

3.6.1 輸入重定向
輸入重定向會打開單詞擴展后所形成的文件名以備讀取,並將其作為文件描述符 n;
如果沒有指定n則將其作為標准輸入(文件描述符為 0)。
輸入重定向的一般格式是: [n]<單詞
3.6.2 輸出重定向
輸出重定向會打開單詞擴展后所形成的文件名以備寫入,並將其作為文件描述符n;
如果沒有指定n則將其作為標准輸出(文件描述符為 1)。
如果文件不存在,就首先創建它;如果已存在,就將它清空使得長度為零。
輸出重定向的一般格式是: [n]>[|]單詞
如果重定向運算符是">",並且已經打當了內部命令 set 的 noclobber 選項,則當單詞擴展后所形成的文件名且是一個普通文件時,重定向將失敗。
如果重定向運算符是">|",或者重定向運算符是">"且沒有打當noclobber 選項,則即使單詞擴展后所形成的文件名存在,重定向也會進行。

3.6.3 輸出重定向的追加
這種類型的輸出重定向會打開單詞擴展后所形成的文件名以備追加,並將其作為文件描述符n;
如果沒有指定n則將其作為標准輸出(文件描述符為 1)。
如果文件不存在,就首先創建它。
追加輸出重定向的一般格式是:[n]>>單詞

3.6.4 輸出和錯誤輸出重定向
這種結構把標准輸出(文件描述符為 1)和標准錯誤輸出(文件描述符為 2)同時重定向到單詞擴展后所形成的文件中。
標准輸出和標准錯誤輸出重定向的形式有兩種:&>單詞 和 >&單詞
這兩種形式中,優先使用第一種。
它們和下面的形式在語法上是等價的: >單詞 2>&1

3.6.5 輸出和錯誤輸出重定向的追加
這種結構把標准輸出(文件描述符為 1)和標准錯誤輸出(文件描述符為 2)同時追加重定向到單詞擴展后所形成的文件中。
標准輸出和標准錯誤輸出重定向的追加用下面的形式: &>>單詞 和 >>單詞 2>&1

3.6.6 即插即用文本
這種形式的重定向指示 shell從當前文本源中讀取輸入,直到遇到其中一行只包含單詞(結尾不含空白符)。
這時,已經讀取的所有行都被當作命令的標准輸入。
即插即用文本的格式是:<<[-]單詞 即插即用文本 結束符
單詞不會進行參數擴展,命令替換,算術擴展,或文件名擴展。
如果單詞中任一字符被引用,則結束符是單詞進行引用去除后的結果,這時不會對即插即用文本進行擴展。
如果單詞沒有被引用,則即插即用文本中的所有行都會進行參數擴展、命令替換、和算術擴展;
這時,字符序列\newline就會被忽略,並且只能用"\"來引用字符"\"、"$"和"'"。
如果重定向運算符是"<<-",則輸入行和結束符所在行中所有的在行開頭的制表符都會被刪除。
這樣,在shell腳本中的即插即用文本就能夠自然的縮進。

3.6.7 即插即用字符串
即插即用字符串是即插即用文本的一種變體,
其形式是:<<< 單詞
功能:擴展單詞並作為標准輸入提供給命令。

3.6.8 文件描述符的復制
運算符 [n]<&單詞 用來復制輸入文件描述符。
如果單詞擴展后是一個或多個數字,則文件描述符n就是與這個數字對應的文件描述符的拷貝。
如果單詞中的數字沒有指定要打當並讀取的文件描述符,就會發生重定向錯誤。
如果單詞擴展后的值是"-",就會關閉文件描述符n。
如果沒有指定n,則使用標准輸入(文件描述符為 0)。
運算符 [n]>&單詞 用來復制輸出文件描述符。
如果沒有指定n,則使用標准輸出(文件描述符為 1)。
如果單詞中的數字沒有指定要打開並寫入的文件描述符,就會發生重定向錯誤。
作為特殊情況,如果省略n,並且單詞擴展后不是一個或多個數字,則標准輸出和標准錯誤輸出就會按前面說的規則重定向。

3.6.9 文件描述符的移動
重定向運算符 [n]<&數字- 把文件描述符數字轉移到文件描述符n上;
如果沒有指定n,則轉移到標准輸入(文件描述符為 0)上。
轉移到n后,(文件描述符)數字就會被關閉。
類似的,重定向運算符 [n]>&數字- 把文件描述符數字轉移到文件描述符n上;
如果沒有指定n,則轉移到標准輸出(文件描述符為 1)上。

3.6.10 打開文件描述符已備讀出和寫入
重定向運算符 [n]<>單詞
可以打開單詞擴展后的文件名以同時准備讀取和寫入,其文件描述符為n;
如果沒有指定n,則使用文件描述符0。如果文件不存在,則首先創建它。

3.7 命令的執行
3.7.1 簡單命令的擴展
執行簡單命令時,shell會從左到右地進行下列擴展、賦值、重定向:
被分析器當作(在命令名稱之前的)變量賦值和重定向的單詞,將被保存下來以備后續處理。
不是變量賦值和重定向的單詞會被擴展。如果擴展以后還有單詞,則其中的第一個會被當作命令的名稱,剩余的當作該命令的參數。
進行前面所說的重定向.
每個變量賦值語句中"="后面的文本在賦給變量之前會進行大括號擴展、參數擴展、命令替換、算術擴 展和引用去除。
如果結果沒有產生命令名稱,則變量賦值會影響到當前的shell環境;
否則,變量被加到命令的執行環境中, 而不會影響當前的shell環境。
如果某個賦值語句試圖給只讀變量賦值,就會發生錯誤,命令就會退出並返回一個非零的狀態。
如果結果沒有產生命令名稱,也會進行重定向,但是不會影響到當前的shell環境。
如果重定向發生錯誤,命令就會退出並返回一個非零的狀態。
如果擴展以后產生了命令名稱,則按如前所說的步驟執行;否則,命令結束。
如果某個擴展含有命令替換,則命令的退出狀態是替換中所執行的最后一個命令的退出狀態。
如果沒有命令替換,則結束命令並返回狀態零。

3.7.2 命令的搜索和執行
命令拆分成單詞以后,結果就得到一個簡單命令及其可選的參數列表。這時,執行下面的操作。
如果命令名中不含斜杠,shell試圖找到它。如果有一個同名的shell函數,則執行該函數。
如果這個名稱不是函數,shell會在內部命令列表中搜索它。如果找到,則執行該內部命令。
如果這個名稱既不是函數名,也不是內部命令,並且不含有斜杠,Bash會搜索 $PATH 中每個目錄里面同名的可執行文件。
Bash使用一個散列表來保存可執行文件的絕對路徑,以避免重復搜索 $PATH 路徑。
只有當命令不在散列表中時才去搜索 $PATH 中的所有目錄。
如果搜索失敗,shell就會去找一個叫 command_not_found_handle 的shell函數定義。
如果這個函數存在,就調用這個函數,並把原來的命令及其參數當作參數傳遞給它;這個函數的就成為shell的退出狀態。
如果這個函數也沒有定義,shell會打印一條錯誤信息並返回狀態127。
如果搜索成功,或者命令名中含有一個或多個斜杠,shell就會在獨立的執行環境中執行這個命令。參數0設為指定的命令名,如果有剩余的參數,將傳作為參數傳遞給命令。
如果因為文件格式和可執行文件不同導致執行失敗,並且該文件不是一個目錄名稱,它就會被當成一個shell腳本並按照shell腳本的方法執行。
如果命令不是異步執行的,shell會等待它的結束並收集其返回狀態

3.7.3 命令執行的環境
shell的執行環境包括下列內容:
shell啟動時,打當它所繼承的文件;這些文件可以用內部命令 exec 來重定向
當前工作目錄;它是由 cd,pushd 或 popd 設置的,或者 女奨奧奬奬 啟動時就繼承的
創建文件所用的掩碼;它是由 umask 設置,或者繼承自shell的父進程
當前的陷阱,由 trap 設置
shell參數;它是通過變量賦值或者set設置的,或者從shell的父進程環境中繼承來的
shell函數;它是在執行期間定義的,或者從shell的父進程環境中繼承來的
啟動時(默認的或通過命令行參數)或通過set打當的選項
用 shopt打開的選項
用 alias定義的shell別名
各個進程號,包括后台作業(參見命令隊列),以及 $$ 和 $PPID 的值。
要執行一個不是內部命令或者shell函數的簡單命令時,它會在包含下列內容的單獨環境中執行。
除非特別 說明,下面的值都從shell中繼承。
shell打開的文件,包括命令重定向時所作的修改和增加
當前工作目錄
創建文件所用的掩碼
標志為可以導出的shell變量和函數,以及命令從環境中導入的變量。
shell捕獲的陷阱被重置為從shell父進程中繼承來的值;shell忽略的陷阱也會被忽略
在這種單獨的環境里啟動的命令不會影響shell的執行環境。
命令替換,組合在括號中的命令,以及異步命令都在子shell環境中;
子shell復制了shell環境,不同的是shell捕獲的陷阱被重置為從shell父進程中繼承來的值。
屬於管道中一部分的內部命令也在子shell環境中執行。
對子shell環境的改變不會影響到shell的執行環境。
用來執行命令替換的子shell會繼承父shell的"-e"選項。
如果不是在POSIX模式下,Bash會在這些子shell中去掉"-e"選項。
如果啟用了作業控制,並且命令后面有個"&",則命令的默認標准輸入是空文件"/dev/null";否則,命令將會繼承shell的重定向后的文件描述符。

3.7.4 環境
在一個程序啟動時,它會得到一個字符串數組,這就叫做環境。
這個數組有三列成對的名稱和值, 即"名稱=值"。
Bash提供了幾種操縱環境的方法。
啟動時,shell會檢查自己的環境,每找到一個名稱就創建一個變量,並自動標志這個變量,使得它可以導入到子進程中。
執行的命令都繼承這個環境。export 命令 和 "declare -x"可以在環境中增加和刪除變量或函數。
在環境中,如果一個參數的值被修改,這個新的值取代舊的值就會成為環境的一部分。
執行的命令所繼承的環境包括shell的初始環境,這些值可能在shell中被修改了,其中不包括用unset或"export -n"刪除了的,但包含 export 和"declare -x"命令所增加的部分。
簡單變量或函數的環境都可以通過加上參數賦值的前綴來暫時修改。
這些賦值語句只影響該命令所能訪問到的環境。
如果打開了"-k"選項,則所有賦值的變量都被加入到命令的環境中,而不僅僅是命令名前面的那些變量。
當Bash啟動外部命令時,$_就會被設置為這個命令的絕對路徑,並傳遞到變量的環境中。

3.7.5 退出狀態
命令執行以后,它的退出狀態就是系統調用waitpid或與其等價的函數所返回的值。
返回狀態總是介於0和255之間,具體將在下面解釋;125以上的值使用比較特殊。
shell的內部命令和復合命令的退出狀態也是位於這個范圍。
在某些情況下,shell將使用特殊的狀態來表示具體的錯誤狀態。
為了便於shell處理,對成功執行的命令,它的退出狀態是零;而非零的退出狀態表示失敗了。
使用這種看起來不直觀的方法是因為,這樣就可以很好的區分成功和多種不同的失敗狀態。
如果命令接收到一個值為N的關鍵信號而退出,Bash就會把128+N作為它的退出狀態。
如果命令沒有找到,用來執行它的子進程就會返回狀態127。
如果命令躍然找到但卻不是可執行的,就返回狀態126。
如果命令因為擴展或重定向時發生錯誤而失敗,則它的返回狀態比零大。
Bash的條件命令以及部分命令隊列使用了退出狀態。
Bash的所有內部命令都會在成功時返回狀態零,失敗時返回非零,所以它們可以用於條件命令和命令隊列中。
如果使用不正確,所有的內部命令都會返回狀態2。

3.7.6 信號
如果Bash是交互運行的,並且沒有任何陷阱,它就會忽略SIGTERM(所以“kill 0”不會殺死一個交互式的shell),但會捕獲並處理SIGINT(所以內部命令wait是可以中斷的)。
當Bash接收到SIGINT時,會退出任何正在進行的循環。
在任何情況下,Bash都會忽略SIGQUIT。
如果啟用了作業控制(參見作業控制),Bash會忽略SIGTTIN、SIGTTOU、SIGTSTP。
Bash啟動的非內部命令會使用shell從其父進程繼承的信號處理程序。如果沒有啟用作業控制,異步執行的命令除了有這些信號處理程序,還會忽略SIGINT和SIGQUIT。
因為命令替換而運行的命令會忽略鍵盤產生的作業控制信號SIGTTIN,SIGTTOU和SIGTSTP。
默認情況下,shell接收到SIGHUP后會退出。在退出之間,交互運行的shell會向所有的作業,不管是正在運行還是已經停止,重新發送SIGHUP。
對已經停止的作業,shell還會發送SIGCONT以確保它能夠接收到SIGHUP。
為了阻止shell向某個特定的作業發送SIGHUP信號,可以用內部命令disown(參見作業控制內部命令)將它從作業表中刪除,或者用disown -h讓它不接收SIGHUP。
如果使用 shopt打開了shell的huponexit選項,當一個交互運行的登錄 shell退出時,會向所有的作業發送 SIGHUP。
如果Bash在等待命令結束時接收到了一個信號,並且已經給這個信號設置了陷阱,則該陷阱直到命令結束時才會執行。
如果Bash通過內部命令wait在等待一個異步命令時接收到了一個信號,並且已經給這個信號設置了陷阱,則內部命令wait在執行完該陷阱后將立即返回一個大於128的狀態。

3.8 Shell腳本
shell腳本是包含shell命令的文本文件。
如果Bash啟動時把這個文件用作為第一個不是選項的參數, 並且沒有使用"-c"或"-s"選項,Bash會從該文件中讀取命令並執行,然后退出。
這種方式會產生一個非交互運行的shell;這個shell會首先在當前目錄中搜索該文件,如果沒有找到就會繼續搜索$PATH中的目錄。
Bash運行shell腳本時,會把特殊參數0設為該腳本的名稱,而不是shell的名稱。
如果還有其它參數,就把其余的參數當作位置參數;如果沒有其它參數,就不設置位置參數。
可以用chown命令打開shell腳本的執行位使得它成為可執行文件。如果Bash在$PATH中搜索命令時找到了這個文件,就會產生一個子shell來執行它。
換句話說,如果文件名是一個可執行的shell腳本,則執行:文件名 參數
就相當於執行:Bash 文件名 參數
這個子shell會重新初始化,效果就等同於啟動了一個新的shell來執行該腳本;
所不同的是,父進程存儲的各命令的路徑(參見內部命令hash)將會在子進程中沿用。
大多數Unix版本在執行命令時都包含這樣的機制:如果腳本的第一行由“#!”這兩個字符開頭,則這一行其余的內容就指定該腳本的解釋器。
因此,可以指定Bash、awk、Perl、或者其它解釋器,並在腳本的這行后面用這些語言來寫。
在腳本文件的第一行,解釋器名稱后面可以包含一個單一的選項,以作為該解釋器的參數;后面是腳本的名稱,再后面是其余參數。
在不能處理這些參數的操作系統里面,Bash會處理它們。
注意,有些比較老的Unix版本規定,解釋器名稱和其參數總長度不得超過32個字符。
Bash腳本的開頭通常是 #!/bin/bash(假定 Bash安裝在"/bin"下面),因為這樣能保證該腳本用Bash來解釋,即使它在其它shell下執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM