<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>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                隨著我們的腳本變得越來越復雜,當腳本運行錯誤,執行結果出人意料的時候, 我們就應該查看一下原因了。 在這一章中,我們將會看一些腳本中出現地常見錯誤類型,同時還會介紹幾個可以跟蹤和消除問題的有用技巧。 ## 語法錯誤 一個普通的錯誤類型是語法。語法錯誤涉及到一些 shell 語法元素的拼寫錯誤。大多數情況下,這類錯誤 會導致 shell 拒絕執行此腳本。 在以下討論中,我們將使用下面這個腳本,來說明常見的錯誤類型: ~~~ #!/bin/bash # trouble: script to demonstrate common errors number=1 if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi ~~~ 參看腳本內容,我們知道這個腳本執行成功了: ~~~ [me@linuxbox ~]$ trouble Number is equal to 1. ~~~ ### 丟失引號 如果我們編輯我們的腳本,并從跟隨第一個 echo 命令的參數中,刪除其末尾的雙引號: ~~~ #!/bin/bash # trouble: script to demonstrate common errors number=1 if [ $number = 1 ]; then echo "Number is equal to 1. else echo "Number is not equal to 1." fi ~~~ 觀察發生了什么: ~~~ [me@linuxbox ~]$ trouble /home/me/bin/trouble: line 10: unexpected EOF while looking for matching `"' /home/me/bin/trouble: line 13: syntax error: unexpected end of file ~~~ 這個腳本產生了兩個錯誤。有趣地是,所報告的行號不是引號被刪除的地方,而是程序中后面的文本行。 我們能知道為什么,如果我們跟隨丟失引號文本行之后的程序。bash 會繼續尋找右引號,直到它找到一個, 其就是這個緊隨第二個 echo 命令之后的引號。找到這個引號之后,bash 變得很困惑,并且 if 命令的語法 被破壞了,因為現在這個 fi 語句在一個用引號引起來的(但是開放的)字符串里面。 在冗長的腳本中,此類錯誤很難找到。使用帶有語法高亮的編輯器將會幫助查找錯誤。如果安裝了 vim 的完整版, 通過輸入下面的命令,可以使語法高亮生效: ~~~ :syntax on ~~~ ### 丟失或意外的標記 另一個常見錯誤是忘記補全一個復合命令,比如說 if 或者是 while。讓我們看一下,如果 我們刪除 if 命令中測試之后的分號,會出現什么情況: ~~~ #!/bin/bash # trouble: script to demonstrate common errors number=1 if [ $number = 1 ] then echo "Number is equal to 1." else echo "Number is not equal to 1." fi ~~~ 結果是這樣的: ~~~ [me@linuxbox ~]$ trouble /home/me/bin/trouble: line 9: syntax error near unexpected token `else' /home/me/bin/trouble: line 9: `else' ~~~ 再次,錯誤信息指向一個錯誤,其出現的位置靠后于實際問題所在的文本行。所發生的事情真是相當有意思。我們記得, if 能夠接受一系列命令,并且會計算列表中最后一個命令的退出代碼。在我們的程序中,我們打算這個列表由 單個命令組成,即 [,測試的同義詞。這個 [ 命令把它后面的東西看作是一個參數列表。在我們這種情況下, 有三個參數: $number,=,和 ]。由于刪除了分號,單詞 then 被添加到參數列表中,從語法上講, 這是合法的。隨后的 echo 命令也是合法的。它被解釋為命令列表中的另一個命令,if 將會計算命令的 退出代碼。接下來遇到單詞 else,但是它出局了,因為 shell 把它認定為一個 保留字(對于 shell 有特殊含義的單詞),而不是一個命令名,因此報告錯誤信息。 ### 預料不到的展開 可能有這樣的錯誤,它們僅會間歇性地出現在一個腳本中。有時候這個腳本執行正常,其它時間會失敗, 這是因為展開結果造成的。如果我們歸還我們丟掉的分號,并把 number 的數值更改為一個空變量,我們 可以示范一下: ~~~ #!/bin/bash # trouble: script to demonstrate common errors number= if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi ~~~ 運行這個做了修改的腳本,得到以下輸出: ~~~ [me@linuxbox ~]$ trouble /home/me/bin/trouble: line 7: [: =: unary operator expected Number is not equal to 1. ~~~ 我們得到一個相當神秘的錯誤信息,其后是第二個 echo 命令的輸出結果。這問題是由于 test 命令中 number 變量的展開結果造成的。當此命令: ~~~ [ $number = 1 ] ~~~ 經過展開之后,number 變為空值,結果就是這樣: ~~~ [ = 1 ] ~~~ 這是無效的,所以就產生了錯誤。這個 = 操作符是一個二元操作符(它要求每邊都有一個數值),但是第一個數值是缺失的, 這樣 test 命令就期望用一個一元操作符(比如 -z)來代替。進一步說,因為 test 命令運行失敗了(由于錯誤), 這個 if 命令接收到一個非零退出代碼,因此執行第二個 echo 命令。 通過為 test 命令中的第一個參數添加雙引號,可以更正這個問題: ~~~ [ "$number" = 1 ] ~~~ 然后當展開操作發生地時候,執行結果將會是這樣: ~~~ [ "" = 1 ] ~~~ 其得到了正確的參數個數。除了代表空字符串之外,引號應該被用于這樣的場合,一個要展開 成多單詞字符串的數值,及其包含嵌入式空格的文件名。 ## 邏輯錯誤 不同于語法錯誤,邏輯錯誤不會阻止腳本執行。雖然腳本會正常運行,但是它不會產生期望的結果, 歸咎于腳本的邏輯問題。雖然有不計其數的可能的邏輯錯誤,但下面是一些在腳本中找到的最常見的 邏輯錯誤類型: 1. 不正確的條件表達式。很容易編寫一個錯誤的 if/then/else 語句,并且執行錯誤的邏輯。 有時候邏輯會被顛倒,或者是邏輯結構不完整。 2. “超出一個值”錯誤。當編寫帶有計數器的循環語句的時候,為了計數在恰當的點結束,循環語句 可能要求從 0 開始計數,而不是從 1 開始,這有可能會被忽視。這些類型的錯誤要不導致循環計數太多,而“超出范圍”, 要不就是過早的結束了一次迭代,從而錯過了最后一次迭代循環。 3. 意外情況。大多數邏輯錯誤來自于程序碰到了程序員沒有預見到的數據或者情況。這也 可以包括出乎意料的展開,比如說一個包含嵌入式空格的文件名展開成多個命令參數而不是單個的文件名。 ### 防錯編程 當編程的時候,驗證假設非常重要。這意味著要仔細得計算腳本所使用的程序和命令的退出狀態。 這里有個實例,基于一個真實的故事。為了在一臺重要的服務器中執行維護任務,一位不幸的系統管理員寫了一個腳本。 這個腳本包含下面兩行代碼: ~~~ cd $dir_name rm * ~~~ 從本質上來說,這兩行代碼沒有任何問題,只要是變量 dir_name 中存儲的目錄名字存在就可以。但是如果不是這樣會發生什么事情呢?在那種情況下,cd 命令會運行失敗, 腳本會繼續執行下一行代碼,將會刪除當前工作目錄中的所有文件。完成不是期望的結果! 由于這種設計策略,這個倒霉的管理員銷毀了服務器中的一個重要部分。 讓我們看一些能夠提高這個設計的方法。首先,在 cd 命令執行成功之后,再運行 rm 命令,可能是明智的選擇。 ~~~ cd $dir_name && rm * ~~~ 這樣,如果 cd 命令運行失敗后,rm 命令將不會執行。這樣比較好,但是仍然有可能未設置變量 dir_name 或其變量值為空,從而導致刪除了用戶家目錄下面的所有文件。這個問題也能夠避免,通過檢驗變量 dir_name 中包含的目錄名是否真正地存在: ~~~ [[ -d $dir_name ]] && cd $dir_name && rm * ~~~ 通常,當某種情況(比如上述問題)發生的時候,最好是終止腳本執行,并對這種情況提示錯誤信息: ~~~ if [[ -d $dir_name ]]; then if cd $dir_name; then rm * else echo "cannot cd to '$dir_name'" >&2 exit 1 fi else echo "no such directory: '$dir_name'" >&2 exit 1 fi ~~~ 這里,我們檢驗了兩種情況,一個名字,看看它是否為一個真正存在的目錄,另一個是 cd 命令是否執行成功。 如果任一種情況失敗,就會發送一個錯誤說明信息到標準錯誤,然后腳本終止執行,并用退出狀態 1 表明腳本執行失敗。 ### 驗證輸入 一個良好的編程習慣是如果一個程序可以接受輸入數據,那么這個程序必須能夠應對它所接受的任意數據。這 通常意味著必須非常仔細地篩選輸入數據,以確保只有有效的輸入數據才能被程序用來做進一步地處理。在前面章節 中我們學習 read 命令的時候,我們遇到過一個這樣的例子。一個腳本中包含了下面一條測試語句, 用來驗證一個選擇菜單: ~~~ [[ $REPLY =~ ^[0-3]$ ]] ~~~ 這條測試語句非常明確。只有當用戶輸入是一個位于 0 到 3 范圍內(包括 0 和 3)的數字的時候, 這條語句才返回一個 0 退出狀態。而其它任何輸入概不接受。有時候編寫這類測試條件非常具有挑戰性, 但是為了能產出一個高質量的腳本,付出還是必要的。 > 設計是時間的函數 > > 當我還是一名大學生,在學習工業設計的時候,一位明智的教授說過一個項目的設計程度是由 給定設計師的時間量來決定的。如果給你五分鐘來設計一款能夠 “殺死蒼蠅” 的產品,你會設計出一個蒼蠅拍。如果給你五個月的時間,你可能會制作出激光制導的 “反蒼蠅系統”。 > > 同樣的原理適用于編程。有時候一個 “快速但粗糙” 的腳本就可以解決問題, 但這個腳本只能被其作者使用一次。這類腳本很常見,為了節省氣力也應該被快速地開發出來。 所以這些腳本不需要太多的注釋和防錯檢查。相反,如果一個腳本打算用于生產使用,也就是說, 某個重要任務或者多個客戶會不斷地用到它,此時這個腳本就需要非常謹慎小心地開發了。 ## 測試 在各類軟件開發中(包括腳本),測試是一個重要的環節。在開源世界中有一句諺語,“早發布,常發布”,這句諺語就反映出這個事實(測試的重要性)。 通過提早和經常發布,軟件能夠得到更多曝光去使用和測試。經驗表明如果在開發周期的早期發現 bug,那么這些 bug 就越容易定位,而且越能低成本 的修復。 在之前的討論中,我們知道了如何使用 stubs 來驗證程序流程。在腳本開發的最初階段,它們是一項有價值的技術 來檢測我們的工作進度。 讓我們看一下上面的文件刪除問題,為了輕松測試,看看如何修改這些代碼。測試原本那個代碼片段將是危險的,因為它的目的是要刪除文件, 但是我們可以修改代碼,讓測試安全: ~~~ if [[ -d $dir_name ]]; then if cd $dir_name; then echo rm * # TESTING else echo "cannot cd to '$dir_name'" >&2 exit 1 fi else echo "no such directory: '$dir_name'" >&2 exit 1 fi exit # TESTING ~~~ 因為在滿足出錯條件的情況下代碼可以打印出有用信息,所以我們沒有必要再添加任何額外信息了。 最重要的改動是僅在 rm 命令之前放置了一個 echo 命令, 為的是把 rm 命令及其展開的參數列表打印出來,而不是執行實際的 rm 命令語句。這個改動可以安全的執行代碼。 在這段代碼的末尾,我們放置了一個 exit 命令來結束測試,從而防止執行腳本其它部分的代碼。 這個需求會因腳本的設計不同而變化。 我們也在代碼中添加了一些注釋,用來標記與測試相關的改動。當測試完成之后,這些注釋可以幫助我們找到并刪除所有的更改。 ### 測試案例 為了執行有用的測試,開發和使用好的測試案例是很重要的。這個要求可以通過謹慎地選擇輸入數據或者運行邊緣案例和極端案例來完成。 在我們的代碼片段中(是非常簡單的代碼),我們想要知道在下面的三種具體情況下這段代碼是怎樣執行的: 1. dir_name 包含一個已經存在的目錄的名字 2. dir_name 包含一個不存在的目錄的名字 3. dir_name 為空 通過執行以上每一個測試條件,就達到了一個良好的測試覆蓋率。 正如設計,測試也是一個時間的函數。不是每一個腳本功能都需要做大量的測試。問題關鍵是確定什么功能是最重要的。因為 測試若發生故障會存在如此潛在的破壞性,所以我們的代碼片在設計和測試段期間都應值得仔細推敲。 ## 調試 如果測試暴露了腳本中的一個問題,那下一步就是調試了。“一個問題”通常意味著在某種情況下,這個腳本的執行 結果不是程序員所期望的結果。若是這種情況,我們需要仔細確認這個腳本實際到底要完成什么任務,和為什么要這樣做。 有時候查找 bug 要牽涉到許多監測工作。一個設計良好的腳本會對查找錯誤有幫助。設計良好的腳本應該具備防衛能力, 能夠監測異常條件,并能為用戶提供有用的反饋信息。 然而有時候,出現的問題相當稀奇,出人意料,這時候就需要更多的調試技巧了。 ### 找到問題區域 在一些腳本中,尤其是一些代碼比較長的腳本,有時候隔離腳本中與出現的問題相關的代碼區域對查找問題很有效。 隔離的代碼區域并不總是真正的錯誤所在,但是隔離往往可以深入了解實際的錯誤原因。可以用來隔離代碼的一項 技巧是“添加注釋”。例如,我們的文件刪除代碼可以修改成這樣,從而決定注釋掉的這部分代碼是否導致了一個錯誤: ~~~ if [[ -d $dir_name ]]; then if cd $dir_name; then rm * else echo "cannot cd to '$dir_name'" >&2 exit 1 fi # else # echo "no such directory: '$dir_name'" >&2 # exit 1 fi ~~~ 通過給腳本中的一個邏輯區塊內的每條語句的開頭添加一個注釋符號,我們就阻止了這部分代碼的執行。然后可以再次執行測試, 來看看清除的代碼是否影響了錯誤的行為。 ### 追蹤 在一個腳本中,錯誤往往是由意想不到的邏輯流導致的。也就是說,腳本中的一部分代碼或者從未執行,或是以錯誤的順序, 或在錯誤的時間給執行了。為了查看真實的程序流,我們使用一項叫做追蹤(tracing)的技術。 一種追蹤方法涉及到在腳本中添加可以顯示程序執行位置的提示性信息。我們可以添加提示信息到我們的代碼片段中: ~~~ echo "preparing to delete files" >&2 if [[ -d $dir_name ]]; then if cd $dir_name; then echo "deleting files" >&2 rm * else echo "cannot cd to '$dir_name'" >&2 exit 1 fi else echo "no such directory: '$dir_name'" >&2 exit 1 fi echo "file deletion complete" >&2 ~~~ 我們把提示信息輸出到標準錯誤輸出,讓其從標準輸出中分離出來。我們也沒有縮進包含提示信息的語句,這樣 想要刪除它們的時候,能比較容易找到它們。 當這個腳本執行的時候,就可能看到文件刪除操作已經完成了: ~~~ [me@linuxbox ~]$ deletion-script preparing to delete files deleting files file deletion complete [me@linuxbox ~]$ ~~~ bash 還提供了一種名為追蹤的方法,這種方法可通過 -x 選項和 set 命令加上 -x 選項兩種途徑實現。 拿我們之前的 trouble 腳本為例,給該腳本的第一行語句添加 -x 選項,我們就能追蹤整個腳本。 ~~~ #!/bin/bash -x # trouble: script to demonstrate common errors number=1 if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi ~~~ 當腳本執行后,輸出結果看起來像這樣: ~~~ [me@linuxbox ~]$ trouble + number=1 + '[' 1 = 1 ']' + echo 'Number is equal to 1.' Number is equal to 1. ~~~ 追蹤生效后,我們看到腳本命令展開后才執行。行首的加號表明追蹤的跡象,使其與常規輸出結果區分開來。 加號是追蹤輸出的默認字符。它包含在 PS4(提示符4)shell 變量中。可以調整這個變量值讓提示信息更有意義。 這里,我們修改該變量的內容,讓其包含腳本中追蹤執行到的當前行的行號。注意這里必須使用單引號是為了防止變量展開,直到 提示符真正使用的時候,就不需要了。 ~~~ [me@linuxbox ~]$ export PS4='$LINENO + ' [me@linuxbox ~]$ trouble 5 + number=1 7 + '[' 1 = 1 ']' 8 + echo 'Number is equal to 1.' Number is equal to 1. ~~~ 我們可以使用 set 命令加上 -x 選項,為腳本中的一塊選擇區域,而不是整個腳本啟用追蹤。 ~~~ #!/bin/bash # trouble: script to demonstrate common errors number=1 set -x # Turn on tracing if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi set +x # Turn off tracing ~~~ 我們使用 set 命令加上 -x 選項來啟動追蹤,+x 選項關閉追蹤。這種技術可以用來檢查一個有錯誤的腳本的多個部分。 ### 執行時檢查數值 伴隨著追蹤,在腳本執行的時候顯示變量的內容,以此知道腳本內部的工作狀態,往往是很用的。 使用額外的 echo 語句通常會奏效。 ~~~ #!/bin/bash # trouble: script to demonstrate common errors number=1 echo "number=$number" # DEBUG set -x # Turn on tracing if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi set +x # Turn off tracing ~~~ 在這個簡單的示例中,我們只是顯示變量 number 的數值,并為其添加注釋,隨后利于其識別和清除。 當查看腳本中的循環和算術語句的時候,這種技術特別有用。 ## 總結 在這一章中,我們僅僅看了幾個在腳本開發期間會出現的問題。當然,還有很多。這章中描述的技術對查找 大多數的常見錯誤是有效的。調試是一種藝術,可以通過開發經驗,在知道如何避免錯誤(整個開發過程中不斷測試) 以及在查找 bug(有效利用追蹤)兩方面都會得到提升。 ## 拓展閱讀 * Wikipedia 上面有兩篇關于語法和邏輯錯誤的短文: [http://en.wikipedia.org/wiki/Syntax_error](http://en.wikipedia.org/wiki/Syntax_error) [http://en.wikipedia.org/wiki/logic_error](http://en.wikipedia.org/wiki/logic_error) * 網上有很多關于技術層面的 bash 編程的資源: [http://mywiki.wooledge.org/BashPitfalls](http://mywiki.wooledge.org/BashPitfalls) [http://tldp.org/LDP/abs/html/gotchas.html](http://tldp.org/LDP/abs/html/gotchas.html) [http://www.gnu.org/software/bash/manual/html_node/Reserved-Word-Index.html](http://www.gnu.org/software/bash/manual/html_node/Reserved-Word-Index.html) * 想要學習從編寫良好的 Unix 程序中得知的基本概念,可以參考 Eric Raymond 的《Unix 編程的藝術》這本 偉大的著作。書中的許多想法都能適用于 shell 腳本: [http://www.faqs.org/docs/artu/](http://www.faqs.org/docs/artu/) [http://www.faqs.org/docs/artu/ch01s06.html](http://www.faqs.org/docs/artu/ch01s06.html) * 對于真正的高強度的調試,參考這個 Bash Debugger: [http://bashdb.sourceforge.net/](http://bashdb.sourceforge.net/)
                  <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>

                              哎呀哎呀视频在线观看