<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                在我們 bash 學習旅程中的最后一站,我們將看一些零星的知識點。當然我們在之前的章節中已經 涵蓋了很多方面,但是還有許多 bash 特性我們沒有涉及到。其中大部分特性相當晦澀,主要對 那些把 bash 集成到 Linux 發行版的程序有用處。然而還有一些特性,雖然不常用, 但是對某些程序問題是很有幫助的。我們將在這里介紹它們。 ## 組命令和子 shell bash 允許把命令組合在一起。可以通過兩種方式完成;要么用一個 group 命令,要么用一個子 shell。 這里是每種方式的語法示例: 組命令: ~~~ { command1; command2; [command3; ...] } ~~~ 子 shell: ~~~ (command1; command2; [command3;...]) ~~~ 這兩種形式的不同之處在于,組命令用花括號把它的命令包裹起來,而子 shell 用括號。值得注意的是,鑒于 bash 實現組命令的方式, 花括號與命令之間必須有一個空格,并且最后一個命令必須用一個分號或者一個換行符終止。 那么組命令和子 shell 命令對什么有好處呢? 盡管它們有一個很重要的差異(我們馬上會接觸到),但它們都是用來管理重定向的。 讓我們考慮一個對多個命令執行重定向的腳本片段。 ~~~ ls -l > output.txt echo "Listing of foo.txt" >> output.txt cat foo.txt >> output.txt ~~~ 這些代碼相當簡潔明了。三個命令的輸出都重定向到一個名為 output.txt 的文件中。 使用一個組命令,我們可以重新編 寫這些代碼,如下所示: ~~~ { ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt ~~~ 使用一個子 shell 是相似的: ~~~ (ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt ~~~ 使用這樣的技術,我們為我們自己節省了一些打字時間,但是組命令和子 shell 真正閃光的地方是與管道線相結合。 當構建一個管道線命令的時候,通常把幾個命令的輸出結果合并成一個流是很有用的。 組命令和子 shell 使這種操作變得很簡單: ~~~ { ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr ~~~ 這里我們已經把我們的三個命令的輸出結果合并在一起,并把它們用管道輸送給命令 lpr 的輸入,以便產生一個打印報告。 在下面的腳本中,我們將使用組命令,看幾個與關聯數組結合使用的編程技巧。這個腳本,稱為 array-2,當給定一個目錄名,打印出目錄中的文件列表, 伴隨著每個文件的文件所有者和組所有者。在文件列表的末尾,腳本打印出屬于每個所有者和組的文件數目。 這里我們看到的結果(縮短的,為簡單起見),是給定腳本的目錄為 /usr/bin 的時候: ~~~ [me@linuxbox ~]$ array-2 /usr/bin /usr/bin/2to3-2.6 root root /usr/bin/2to3 root root /usr/bin/a2p root root /usr/bin/abrowser root root /usr/bin/aconnect root root /usr/bin/acpi_fakekey root root /usr/bin/acpi_listen root root /usr/bin/add-apt-repository root root . /usr/bin/zipgrep root root /usr/bin/zipinfo root root /usr/bin/zipnote root root /usr/bin/zip root root /usr/bin/zipsplit root root /usr/bin/zjsdecode root root /usr/bin/zsoelim root root File owners: daemon : 1 file(s) root : 1394 file(s) File group owners: crontab : 1 file(s) daemon : 1 file(s) lpadmin : 1 file(s) mail : 4 file(s) mlocate : 1 file(s) root : 1380 file(s) shadow : 2 file(s) ssh : 1 file(s) tty : 2 file(s) utmp : 2 file(s) ~~~ 這里是腳本代碼列表(帶有行號): ~~~ #!/bin/bash # array-2: Use arrays to tally file owners declare -A files file_group file_owner groups owners if [[ ! -d "$1" ]]; then echo "Usage: array-2 dir" >&2 exit 1 fi for i in "$1"/*; do owner=$(stat -c %U "$i") group=$(stat -c %G "$i") files["$i"]="$i" file_owner["$i"]=$owner file_group["$i"]=$group ((++owners[$owner])) ((++groups[$group])) done # List the collected files { for i in "${files[@]}"; do printf "%-40s %-10s %-10s\n" \ "$i" ${file_owner["$i"]} ${file_group["$i"]} done } | sort echo # List owners echo "File owners:" { for i in "${!owners[@]}"; do printf "%-10s: %5d file(s)\n" "$i" ${owners["$i"]} done } | sort echo # List groups echo "File group owners:" { for i in "${!groups[@]}"; do printf "%-10s: %5d file(s)\n" "$i" ${groups["$i"]} done } | sort ~~~ 讓我們看一下這個腳本的運行機制: 行5: 關聯數組必須用帶有 -A 選項的 declare 命令創建。在這個腳本中我們創建了如下五個數組: files 包含了目錄中文件的名字,按文件名索引 file_group 包含了每個文件的組所有者,按文件名索引 file_owner 包含了每個文件的所有者,按文件名索引 groups 包含了屬于索引的組的文件數目 owners 包含了屬于索引的所有者的文件數目 行7-10:查看是否一個有效的目錄名作為位置參數傳遞給程序。如果不是,就會顯示一條使用信息,并且腳本退出,退出狀態為1。 行12-20:循環遍歷目錄中的所有文件。使用 stat 命令,行13和行14抽取文件所有者和組所有者, 并把值賦給它們各自的數組(行16,17),使用文件名作為數組索引。同樣地,文件名自身也賦值給 files 數組。 行18-19:屬于文件所有者和組所有者的文件總數各自加1。 行22-27:輸出文件列表。為做到這一點,使用了 “${array[@]}” 參數展開,展開成整個的數組元素列表, 并且每個元素被當做是一個單獨的詞。從而允許文件名包含空格的情況。也要注意到整個循環是包裹在花括號中, 從而形成了一個組命令。這樣就允許整個循環輸出會被管道輸送給 sort 命令的輸入。這是必要的,因為 展開的數組元素是無序的。 行29-40:這兩個循環與文件列表循環相似,除了它們使用 “${!array[@]}” 展開,展開成數組索引的列表 而不是數組元素的。 ## 進程替換 雖然組命令和子 shell 看起來相似,并且它們都能用來在重定向中合并流,但是兩者之間有一個很重要的不同。 然而,一個組命令在當前 shell 中執行它的所有命令,而一個子 shell(顧名思義)在當前 shell 的一個 子副本中執行它的命令。這意味著運行環境被復制給了一個新的 shell 實例。當這個子 shell 退出時,環境副本會消失, 所以在子 shell 環境(包括變量賦值)中的任何更改也會消失。因此,在大多數情況下,除非腳本要求一個子 shell, 組命令比子 shell 更受歡迎。組命令運行很快并且占用的內存也少。 我們在第20章中看到過一個子 shell 運行環境問題的例子,當我們發現管道線中的一個 read 命令 不按我們所期望的那樣工作的時候。為了重現問題,我們構建一個像這樣的管道線: ~~~ echo "foo" | read echo $REPLY ~~~ 該 REPLY 變量的內容總是為空,是因為這個 read 命令在一個子 shell 中執行,所以它的 REPLY 副本會被毀掉, 當該子 shell 終止的時候。因為管道線中的命令總是在子 shell 中執行,任何給變量賦值的命令都會遭遇這樣的問題。 幸運地是,shell 提供了一種奇異的展開方式,叫做進程替換,它可以用來解決這種麻煩。進程替換有兩種表達方式: 一種適用于產生標準輸出的進程: ~~~ <(list) ~~~ 另一種適用于接受標準輸入的進程: ~~~ >(list) ~~~ 這里的 list 是一串命令列表: 為了解決我們的 read 命令問題,我們可以雇傭進程替換,像這樣: ~~~ read < <(echo "foo") echo $REPLY ~~~ 進程替換允許我們把一個子 shell 的輸出結果當作一個用于重定向的普通文件。事實上,因為它是一種展開形式,我們可以檢驗它的真實值: ~~~ [me@linuxbox ~]$ echo <(echo "foo") /dev/fd/63 ~~~ 通過使用 echo 命令,查看展開結果,我們看到子 shell 的輸出結果,由一個名為 /dev/fd/63 的文件提供。 進程替換經常被包含 read 命令的循環用到。這里是一個 read 循環的例子,處理一個目錄列表的內容,內容創建于一個子 shell: ~~~ #!/bin/bash # pro-sub : demo of process substitution while read attr links owner group size date time filename; do cat <<- EOF Filename: $filename Size: $size Owner: $owner Group: $group Modified: $date $time Links: $links Attributes: $attr EOF done < <(ls -l | tail -n +2) ~~~ 這個循環對目錄列表的每一個條目執行 read 命令。列表本身產生于該腳本的最后一行代碼。這一行代碼把從進程替換得到的輸出 重定向到這個循環的標準輸入。這個包含在管道線中的 tail 命令,是為了消除列表的第一行文本,這行文本是多余的。 當腳本執行后,腳本產生像這樣的輸出: ~~~ [me@linuxbox ~]$ pro_sub | head -n 20 Filename: addresses.ldif Size: 14540 Owner: me Group: me Modified: 2009-04-02 11:12 Links: 1 Attributes: -rw-r--r-- Filename: bin Size: 4096 Owner: me Group: me Modified: 2009-07-10 07:31 Links: 2 Attributes: drwxr-xr-x Filename: bookmarks.html Size: 394213 Owner: me Group: me ~~~ ## 陷阱 在第10章中,我們看到過程序是怎樣響應信號的。我們也可以把這個功能添加到我們的腳本中。然而到目前為止, 我們所編寫過的腳本還不需要這種功能(因為它們運行時間非常短暫,并且不創建臨時文件),大且更復雜的腳本 可能會受益于一個信息處理程序。 當我們設計一個大的,復雜的腳本的時候,若腳本仍在運行時,用戶注銷或關閉了電腦,這時候會發生什么,考慮到這一點非常重要。 當像這樣的事情發生了,一個信號將會發送給所有受到影響的進程。依次地,代表這些進程的程序會執行相應的動作,來確保程序 合理有序的終止。比方說,例如,我們編寫了一個會在執行時創建臨時文件的腳本。在一個好的設計流程,我們應該讓腳本刪除創建的 臨時文件,當腳本完成它的任務之后。若腳本接收到一個信號,表明該程序即將提前終止的信號, 此時讓腳本刪除創建的臨時文件,也會是很精巧的設計。 為滿足這樣需求,bash 提供了一種機制,眾所周知的 trap。陷阱由被恰當命令的內部命令 trap 實現。 trap 使用如下語法: ~~~ trap argument signal [signal...] ~~~ 這里的 argument 是一個字符串,它被讀取并被當作一個命令,signal 是一個信號的說明,它會觸發執行所要解釋的命令。 這里是一個簡單的例子: ~~~ #!/bin/bash # trap-demo : simple signal handling demo trap "echo 'I am ignoring you.'" SIGINT SIGTERM for i in {1..5}; do echo "Iteration $i of 5" sleep 5 done ~~~ 這個腳本定義一個陷阱,當腳本運行的時候,這個陷阱每當接受到一個 SIGINT 或 SIGTERM 信號時,就會執行一個 echo 命令。 當用戶試圖通過按下 Ctrl-c 組合鍵終止腳本運行的時候,該程序的執行結果看起來像這樣: ~~~ [me@linuxbox ~]$ trap-demo Iteration 1 of 5 Iteration 2 of 5 I am ignoring you. Iteration 3 of 5 I am ignoring you. Iteration 4 of 5 Iteration 5 of 5 ~~~ 正如我們所看到的,每次用戶試圖中斷程序時,會打印出這條信息。 構建一個字符串形成一個有用的命令序列是很笨拙的,所以通常的做法是指定一個 shell 函數作為命令。在這個例子中, 為每一個信號指定了一個單獨的 shell 函數來處理: ~~~ #!/bin/bash # trap-demo2 : simple signal handling demo exit_on_signal_SIGINT () { echo "Script interrupted." 2>&1 exit 0 } exit_on_signal_SIGTERM () { echo "Script terminated." 2>&1 exit 0 } trap exit_on_signal_SIGINT SIGINT trap exit_on_signal_SIGTERM SIGTERM for i in {1..5}; do echo "Iteration $i of 5" sleep 5 done ~~~ 這個腳本的特色是有兩個 trap 命令,每個命令對應一個信號。每個 trap,依次,當接受到相應的特殊信號時, 會執行指定的 shell 函數。注意每個信號處理函數中都包含了一個 exit 命令。沒有 exit 命令, 信號處理函數執行完后,該腳本將會繼續執行。 當用戶在這個腳本執行期間,按下 Ctrl-c 組合鍵的時候,輸出結果看起來像這樣: ~~~ [me@linuxbox ~]$ trap-demo2 Iteration 1 of 5 Iteration 2 of 5 Script interrupted. ~~~ > 臨時文件 > > 把信號處理程序包含在腳本中的一個原因是刪除臨時文件,在腳本執行期間,腳本可能會創建臨時文件來存放中間結果。 命名臨時文件是一種藝術。傳統上,在類似于 unix 系統中的程序會在 /tmp 目錄下創建它們的臨時文件,/tmp 是 一個服務于臨時文件的共享目錄。然而,因為這個目錄是共享的,這會引起一定的安全顧慮,尤其對那些用 超級用戶特權運行的程序。除了為暴露給系統中所有用戶的文件設置合適的權限,這一明顯步驟之外, 給臨時文件一個不可預測的文件名是很重要的。這就避免了一種為大眾所知的 temp race 攻擊。 一種創建一個不可預測的(但是仍有意義的)臨時文件名的方法是,做一些像這樣的事情: > > tempfile=/tmp/$(basename $0).$.$RANDOM > > 這將創建一個由程序名字,程序進程的 ID(PID)文件名,和一個隨機整數組成。注意,然而,該 $RANDOM shell 變量 只能返回一個范圍在1-32767內的整數值,這在計算機術語中不是一個很大的范圍,所以一個單一的該變量實例是不足以克服一個堅定的攻擊者的。 > > 一個比較好的方法是使用 mktemp 程序(不要和 mktemp 標準庫函數相混淆)來命名和創建臨時文件。 這個 mktemp 程序接受一個用于創建文件名的模板作為參數。這個模板應該包含一系列的 “X” 字符, 隨后這些字符會被相應數量的隨機字母和數字替換掉。一連串的 “X” 字符越長,則一連串的隨機字符也就越長。 這里是一個例子: > > tempfile=$(mktemp /tmp/foobar.$.XXXXXXXXXX) > > 這里創建了一個臨時文件,并把臨時文件的名字賦值給變量 tempfile。因為模板中的 “X” 字符會被隨機字母和 數字代替,所以最終的文件名(在這個例子中,文件名也包含了特殊參數 $$ 的展開值,進程的 PID)可能像這樣: > > /tmp/foobar.6593.UOZuvM6654 > > 對于那些由普通用戶操作執行的腳本,避免使用 /tmp 目錄,而是在用戶家目錄下為臨時文件創建一個目錄, 通過像這樣的一行代碼: > > [[ -d $HOME/tmp ]] || mkdir $HOME/tmp ## 異步執行 有時候需要同時執行多個任務。我們已經知道現在所有的操作系統若不是多用戶的但至少是多任務的。 腳本也可以構建成多任務處理的模式。 通常這涉及到啟動一個腳本,依次,啟動一個或多個子腳本來執行額外的任務,而父腳本繼續運行。然而,當一系列腳本 以這種方式運行時,要保持父子腳本之間協調工作,會有一些問題。也就是說,若父腳本或子腳本依賴于另一方,并且 一個腳本必須等待另一個腳本結束任務之后,才能完成它自己的任務,這應該怎么辦? bash 有一個內置命令,能幫助管理諸如此類的異步執行的任務。wait 命令導致一個父腳本暫停運行,直到一個 特定的進程(例如,子腳本)運行結束。 ### 等待 首先我們將演示一下 wait 命令的用法。為此,我們需要兩個腳本,一個父腳本: ~~~ #!/bin/bash # async-parent : Asynchronous execution demo (parent) echo "Parent: starting..." echo "Parent: launching child script..." async-child & pid=$! echo "Parent: child (PID= $pid) launched." echo "Parent: continuing..." sleep 2 echo "Parent: pausing to wait for child to finish..." wait $pid echo "Parent: child is finished. Continuing..." echo "Parent: parent is done. Exiting." ~~~ 和一個子腳本: ~~~ #!/bin/bash # async-child : Asynchronous execution demo (child) echo "Child: child is running..." sleep 5 echo "Child: child is done. Exiting." ~~~ 在這個例子中,我們看到該子腳本是非常簡單的。真正的操作通過父腳本完成。在父腳本中,子腳本被啟動, 并被放置到后臺運行。子腳本的進程 ID 記錄在 pid 變量中,這個變量的值是 $! shell 參數的值,它總是 包含放到后臺執行的最后一個任務的進程 ID 號。 父腳本繼續,然后執行一個以子進程 PID 為參數的 wait 命令。這就導致父腳本暫停運行,直到子腳本退出, 意味著父腳本結束。 當執行后,父子腳本產生如下輸出: ~~~ [me@linuxbox ~]$ async-parent Parent: starting... Parent: launching child script... Parent: child (PID= 6741) launched. Parent: continuing... Child: child is running... Parent: pausing to wait for child to finish... Child: child is done. Exiting. Parent: child is finished. Continuing... Parent: parent is done. Exiting. ~~~ ## 命名管道 在大多數類似也 Unix 的操作系統中,有可能創建一種特殊類型的餓文件,叫做命名管道。命名管道用來在 兩個進程之間建立連接,也可以像其它類型的文件一樣使用。雖然它們不是那么流行,但是它們值得我們去了解。 有一種常見的編程架構,叫做客戶端-服務器,它可以利用像命名管道這樣的通信方式, 也可以使用其它類型的進程間通信方式,比如網絡連接。 最為廣泛使用的客戶端-服務器系統類型是,當然,一個 web 瀏覽器與一個 web 服務器之間進行通信。 web 瀏覽器作為客戶端,向服務器發出請求,服務器響應請求,并把對應的網頁發送給瀏覽器。 命令管道的行為類似于文件,但實際上形成了先入先出(FIFO)的緩沖。和普通(未命令的)管道一樣, 數據從一端進入,然后從另一端出現。通過命令管道,有可能像這樣設置一些東西: ~~~ process1 > named_pipe ~~~ 和 ~~~ process2 < named_pipe ~~~ 表現出來就像這樣: ~~~ process1 | process2 ~~~ ### 設置一個命名管道 首先,我們必須創建一個命名管道。使用 mkfifo 命令能夠創建命令管道: ~~~ [me@linuxbox ~]$ mkfifo pipe1 [me@linuxbox ~]$ ls -l pipe1 prw-r--r-- 1 me me 0 2009-07-17 06:41 pipe1 ~~~ 這里我們使用 mkfifo 創建了一個名為 pipe1 的命名管道。使用 ls 命令,我們查看這個文件, 看到位于屬性字段的第一個字母是 “p”,表明它是一個命名管道。 ### 使用命名管道 為了演示命名管道是如何工作的,我們將需要兩個終端窗口(或用兩個虛擬控制臺代替)。 在第一個終端中,我們輸入一個簡單命令,并把命令的輸出重定向到命名管道: ~~~ [me@linuxbox ~]$ ls -l > pipe1 ~~~ 我們按下 Enter 按鍵之后,命令將會掛起。這是因為在管道的另一端沒有任何接受數據。當這種現象發生的時候, 據說是管道阻塞了。一旦我們綁定一個進程到管道的另一端,該進程開始從管道中讀取輸入的時候,這種情況會消失。 使用第二個終端窗口,我們輸入這個命令: ~~~ [me@linuxbox ~]$ cat < pipe1 ~~~ 然后產自第一個終端窗口的目錄列表出現在第二個終端中,并作為來自 cat 命令的輸出。在第一個終端 窗口中的 ls 命令一旦它不再阻塞,會成功地結束。 ## 總結 嗯,我們已經完成了我們的旅程。現在剩下的唯一要做的事就是練習,練習,再練習。 縱然在我們的長途跋涉中,我們涉及了很多命令,但是就命令行而言,我們只是觸及了它的表面。 仍留有成千上萬的命令行程序,需要去發現和享受。開始挖掘 /usr/bin 目錄吧,你將會看到! ## 拓展閱讀 * bash 手冊頁的 “復合命令” 部分包含了對組命令和子 shell 表示法的詳盡描述。 * bash 手冊也的 EXPANSION 部分包含了一小部分進程替換的內容: * 《高級 Bash 腳本指南》也有對進程替換的討論: [http://tldp.org/LDP/abs/html/process-sub.html](http://tldp.org/LDP/abs/html/process-sub.html) * 《Linux 雜志》有兩篇關于命令管道的好文章。第一篇,源于1997年9月: [http://www.linuxjournal.com/article/2156](http://www.linuxjournal.com/article/2156) * 和第二篇,源于2009年3月: [http://www.linuxjournal.com/content/using-named-pipes-fifos-bash](http://www.linuxjournal.com/content/using-named-pipes-fifos-bash)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看