<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          43 個 Bash 編程易錯總結(jié)大全!

          共 6410字,需瀏覽 13分鐘

           ·

          2022-06-08 23:53

          在公眾號后臺回復(fù):JGNB,可獲取杰哥原創(chuàng)的 PDF 手冊。

          Bash Pitfalls[1]?文章介紹了 40 多條日常 Bash ?編程中,老手和新手都容易忽略的錯誤編程習(xí)慣。每條作者在給出錯誤的范例上,詳細(xì)分析與解釋錯誤的原因,同時給出正確的改寫建議。文中有不少引用的文章,也值得大家仔細(xì)閱讀。仔細(xì)閱讀了這篇文章后,收獲很多,不感獨享,把這篇文章以半翻譯半筆記的形式分享給大家。

          1. for i in $(ls *.mp3)

          Bash 寫循環(huán)代碼的時候,確實比較容易犯下面的錯誤:

          for?i?in?$(ls?*.mp3);?do????#?錯誤!
          ????some?command?$i?????????#?錯誤!
          done

          for?i?in?$(ls)??????????????#?錯誤!
          for?i?in?`ls`???????????????#?錯誤!

          for?i?in?$(find?.?-type?f)??#?錯誤!
          for?i?in?`find?.?-type?f`???#?錯誤!

          files=($(find?.?-type?f))???#?錯誤!
          for?i?in?${files[@]}????????#?錯誤!

          這里主要兩個問題:

          • 使用命令展開時不帶引號,其執(zhí)行結(jié)果會使用 IFS 作為分隔符,拆分成參數(shù)傳遞給 for 循環(huán)處理;
          • 不應(yīng)該讓腳本去解析 ls 命令的結(jié)果[2];

          我們不能避免某些文件名中包含空格,Shell 會對?$(ls *.mp3)?展開的結(jié)果會被做單詞拆分 (WordSplitting[3]) 的處理。假設(shè)有一個文件,名字為 01 - Don't Eat the Yellow Snow.mp3,for 循環(huán)處理的時候,會今次遍歷文件名中的每個單詞:01, -, Don't, Eat 等等:

          $?for?i?in?$(ls?*.mp3);?do?echo?$i;?done
          01
          -
          Don't
          Eat
          the
          Yellow
          Snow.mp3

          比這更差的情況是,上面命令展開的結(jié)果可能被 Shell 進(jìn)一步處理,比如文件名展開[4]。比如,ls 執(zhí)行的結(jié)果中包含*號,按照通配符的規(guī)則 , * 號會被展開成當(dāng)前目錄下的所有文件 :

          $?touch?"1*.mp3"?"1.mp3"?"11.mp3"?"12.mp3"
          $?for?i?in?$(ls?*.mp3);?do?echo?$i;?done
          1*.mp3?1.mp3?11.mp3?12.mp3
          1.mp3
          11.mp3
          12.mp3
          1.mp3
          11.mp3
          12.mp3

          不過,在這種場景下,你即使加上引號,也是無濟(jì)于事的:

          $?for?i?in?"$(ls?*.mp3)";?do?echo?--$i--;?done
          --1*.mp3?1.mp3?11.mp3?12.mp3--

          加上引號后,ls 執(zhí)行的結(jié)果會被當(dāng)成一個整體,所以 for 循環(huán)只會執(zhí)行一次,達(dá)不到預(yù)期的效果。

          事實上,這種情況下,根本不需要使用 ls 命令。ls 命令的結(jié)果本身就設(shè)計成給人讀的,而不是給腳本解析的。正確的處理方法是,直接使用文件名展開(通配符)的功能:

          $?for?i?in?*.mp3;?do
          >?????echo?"$i"
          >?done
          1*.mp3
          1.mp3
          11.mp3
          12.mp3

          文件名展開是位于各種展開(花括號展開、變量替換、命令展開等)功能中的最后一個環(huán)節(jié),所以不會有之前不帶引號的命令展開的副作用。如果你需要遞歸地處理文件,可以考慮使用 Find 命令[5]

          到這一步,之間的問題看樣子已經(jīng)修復(fù)了。但是,如果你進(jìn)一步思考,假設(shè)當(dāng)前目錄上沒有文件時會怎么樣?沒有文件的時候,*.mp3 不會被展開直接傳遞給 for 循環(huán)處理,所以這個時候循環(huán)還是會執(zhí)行一次。這種情況不是我們預(yù)期的行為。保險起見,可以在循環(huán)處理的時候,檢查下文件是否存在:

          #?POSIX
          for?i?in?*.mp3;?do
          ????[?-e?"$i"?]?||?continue
          ????some?command?"$i"
          done

          如果你有使用引號[6]和避免單詞拆分[7]的習(xí)慣,你完全可以避免很多錯誤。

          注意下循環(huán)體內(nèi)部的 "$i",這里會導(dǎo)致下面我們要說的另外一個比較容易犯的錯誤。

          2. cp $file $target

          上面的命令有什么問題呢?如果你提前知道,$file 和 $target 文件名中不會包含空格或者*號。否則,這行命令執(zhí)行前在經(jīng)過單詞拆分和文件名展開的時候會出現(xiàn)問題。所以,兩次強調(diào),在使用展開的地方切勿忘記使用引號:

          $?cp?--?"$file"?"$target"

          如果不帶引號,當(dāng)你執(zhí)行如下命令時就會出錯:

          $?file="01?-?Don't?Eat?the?Yellow?Snow.mp3"
          $?target="/tmp"
          $?cp?$file?$target
          cp:?cannot?stat?‘01’:?No?such?file?or?directory
          ..

          如果帶上引號,就不會有上面的問題,除非文件名以 '-' 開頭,在這種情況下,cp 會認(rèn)為你提供的是一個命令行選項,這個錯誤下面會介紹。

          3. 文件名中包含短橫 '-'

          文件名以 '-' 開頭會導(dǎo)致許多問題,*.mp3 這種通配符會根據(jù)當(dāng)前的locale[8]展開成一個列表,但在絕大多數(shù)環(huán)境下,'-' 排序的時候會排在大多數(shù)字母前。這個展開的列表傳遞給有些命令的時候,會錯誤的將-filename 解析成命令行選項。這里有兩種方法來解決這個問題。

          第一種方法是在命令和參數(shù)之間加上--,這種語法告訴命令不要繼續(xù)對--之后的內(nèi)容進(jìn)行命令行參數(shù) / 選項解析:

          $?cp?--?"$file"?"$target"

          這種方法可以解這個問題,但是你需要在每個命令后面都要加上--,而且依賴具體的命令解析的方式,如果一些命令不兼容這種約定俗成的規(guī)范,這種做法是無效的。

          另外一種方法是,確保文件名都使用相對或者絕對的路徑,以目錄開頭:

          for?i?in?./*.mp3;?do
          ????cp?"$i"?/target
          ????...
          done

          這種情況下,即使某個文件以-開頭,展開后文件名依然是 ./-foo.mp3 這種形式,完全不會有問題。

          4. [ $foo = "bar" ]

          這是一個與第 2 個問題類似的問題,雖然用到了引號,但是放錯了位置,對于字符串字面值,除非有特殊符號,否則不大需要用引號括起來。但是,你應(yīng)該把變量的值用括號括起來,從而避免它們包含空格或能通配符,這一點我們在前面的問題中都解釋過。

          這個例子在以下情況下會出錯:

          • 如果 [中的變量不存在,或者為空,這個時候上面的例子最終解析結(jié)果是:

            [?=?"bar"?]?#?錯誤?!

            并且執(zhí)行會出錯:unary operator expected,因為 = 是二元操作符,它需要左右各一個操作數(shù)。

          • 如果變量值包含空格,它首先在執(zhí)行之前進(jìn)行單詞拆分,因此 [命令看到的樣子可能是這樣的:

            [?multiple?words?here?=?"bar"?];

          正確的做法應(yīng)該是:

          #?POSIX
          [?"$foo"?=?bar?]

          這種寫法,在 POSIX 兼容的實現(xiàn)中都不會有問題,即使 $foo 以短橫 "-" 開頭,因為 POSIX 實現(xiàn)的 test 命令通過傳遞的參數(shù)來確定執(zhí)行的行為。

          只有一些非常古老的 shell 可能會遇到問題,這個時候你可以使用下面的寫法來解決(相信你肯定看到過這種寫法):

          #?POSIX?/?Bourne
          [?x"$foo"?=?xbar?]

          在 Bash 中,還有另外一種選擇是使用[[關(guān)鍵字[9]

          #?Bash?/?Ksh
          [[?$foo?==?bar?]]

          這里你不需要使用引號,因為在 [[里面參數(shù)不會進(jìn)行展開,當(dāng)然帶上引號也不會有錯。

          不過有一點要注意的是,[[里的 == 不僅僅是文本比較,它會檢查左邊的值是否匹配右側(cè)的表達(dá)式,== 右側(cè)的值加上引號,會讓它成為一個普通的字面量,*? 等通配符會失去特殊含義。

          5. cd $(dirname "$f")

          這又是一個引號的問題,命令展開的結(jié)果會進(jìn)一步地進(jìn)行單詞拆分或者文件名展開。因此下面的寫法才是正確的:

          cd?"$(dirname?"$f")"

          但是,上面引號的寫法可能比較怪異,你可能會認(rèn)為第一、二個引號,第三、四個引號是一組的。

          但是事實上,Bash 將命令替換里面的引號當(dāng)成一組,外面的當(dāng)成另外一組。如果你是用反引號的寫法,引號的行為就不是這樣的了,所以[$() 寫法更加推薦](http://mywiki.wooledge.org/BashFAQ/082 "$() 寫法更加推薦")。

          6. [ "$foo" = bar && "$bar" = foo ]

          不要在test 命令[10]內(nèi)部使用 &&,Bash 解析器會把你的命令分隔成兩個命令,在 && 之前和之后。你應(yīng)該使用下面的寫法:

          [?bar?=?"$foo"?]?&&?[?foo?=?"$bar"?]?#?POSIX
          [[?$foo?=?bar?&&?$bar?=?foo?]]???????#?Bash?/?Ksh

          盡量避免使用下面的寫法,雖然它是正確的,但是這種寫法可移植性不好,并且已經(jīng)在 POSIX-2008 中被廢棄:

          [?bar?=?"$foo"?-a?foo?=?"$bar"?]

          7. [[ $foo > 7 ]]

          原文作者認(rèn)為算術(shù)比較不應(yīng)該用 [[,而是用 ((,我沒弄明白是為什么。

          如果有理解的同學(xué),歡迎以評論回復(fù),謝謝。

          8. grep foo bar | while read -r; do ((count++)); done

          這種寫法初看沒有問題,但是你會發(fā)現(xiàn)當(dāng)執(zhí)行完后,count 變量并沒有變化。原因是管道后面的命令是在一個子 Shell[11]中執(zhí)行的。

          POSIX 規(guī)范并沒有說明管道的最后一個命令是不是在子 Shell 中執(zhí)行的。一些 shell,例如 ksh93 或者 Bash>=4.2 可以通過?shopt -s lastpipe?命令,指明管道中的最后一個命令在當(dāng)前 shell 中執(zhí)行。由于篇幅限制,在此就不展開,有興趣的可以看Bash FAQ #24[12]

          9. if [grep foo myfile]

          初學(xué)者會錯誤地認(rèn)為,[是 if 語法的一部分,正如 C 語言中的 if ()。但是事實并非如此,if 后面跟著的是一個命令,[是一個命令,它是內(nèi)置命令 test 的簡寫形式,只不過它要求最后一個參數(shù)必須是]。下面兩種寫法是一樣的:

          #?POSIX
          if?[?false?];?then?echo?"HELP";?fi
          if?test?false;?then?echo?"HELP";?fi

          兩個都是檢查參數(shù) "false" 是不是非空的,所以上面兩個語句都會輸出 HELP。

          if 語句的語法是:

          if COMMANDS
          then
          elif # optional
          then
          else # optional
          fi # required

          再次強調(diào),[是一個命令,它同其它常規(guī)的命令一樣接受參數(shù)。if 是一個復(fù)合命令,它包含其它命令,[并不是 if 語法中的一部分。

          如果你想根據(jù) grep 命令的結(jié)果來做事情,你不需要把 grep 放到 [里面,只需要在 if 后面緊跟 grep 即可:

          if?grep?-q?fooregex?myfile;?then
          ...
          fi

          如果 grep 在 myfile 中找到匹配的行,它的執(zhí)行結(jié)果為 0(true),then 后面的部分就會執(zhí)行。

          10. if [bar="$foo"]; then ...

          正如上一個問題中提到的,[是一個命令,它的參數(shù)之間必須用空格分隔。

          11. if [ [ a = b ] && [ c = d ] ]; then ...

          不要用把 [命令看成 C 語言中 if 語句的條件一樣,它是一個命令。

          如果你想表達(dá)一個復(fù)合的條件表達(dá)式,可以這樣寫:

          if?[?a?=?b?]?&&?[?c?=?d?];?then?...

          注意,if 后面有兩個命令,它們用 && 分開。等價于下面的寫法:

          if?test?a?=?b?&&?test?c?=?d;?then?...

          如果第一個 test(或者 [) 命令返回 false,then 后面的語句不會執(zhí)行;如果第一個返回 true,第二個 test 命令會執(zhí)行;只有第二個命令同樣返回 true 的情況下,then 后面的語句才會執(zhí)行。

          除此之外,還可以使用 [[關(guān)鍵字,因為它支持 && 的用法:

          if?[[?a?=?b?&&?c?=?d?]];?then?...

          12. read $foo

          read 命令中你不需要在變量名之前使用 $。如果你想把讀入的數(shù)據(jù)存放到名為 foo 的變量中,下面的寫法就夠了:

          read?foo

          或者,更加安全地方法:

          IFS=?read?-r?foo

          read $foo?會把一行的內(nèi)容讀入到變量中,該變量的名稱存儲在 $foo 中。所以兩者的含義是完全不一樣的。

          13. cat file | sed s/foo/bar/ > file

          你不應(yīng)該在一個管道中,從一個文件讀的同時,再往相同的文件里面寫,這樣的后果是未知的。

          你可以為此創(chuàng)建一個臨時文件,這種做法比較安全可靠:

          #?sed?'s/foo/bar/g'?file?>?tmpfile?&&?mv?tmpfile?file

          或者,如果你用得是 GNU Sed 4.x 以上的版本,可以使用-i 選項即時修改文件的內(nèi)容:

          #?sed?-i?'s/foo/bar/g'?file

          14. echo $foo

          這種看似無害的命令往往會給初學(xué)者千萬極大的困擾,他們會懷疑是不是因為 $foo 變量的值是錯誤的。事實卻是因為,$foo 變量在這里沒有使用雙引號,所以在解析的時候會進(jìn)行單詞拆分[13]文件名展開[14],最終導(dǎo)致執(zhí)行結(jié)果與預(yù)期大相徑庭:

          msg="Please?enter?a?file?name?of?the?form?*.zip"
          echo?$msg

          這里整句話會被拆分成單詞,然后其中的通配符會被展開,例如 *.zip。當(dāng)你的用戶看到如下的結(jié)果時,他們會怎樣想:

          Please?enter?a?file?name?of?the?form?freenfss.zip?lw35nfss.zip

          再舉一個例子(假設(shè)當(dāng)前目錄下有以 .zip 結(jié)尾的文件):

          var="*.zip"???#?var?包括一個星號,一個點號和?zip
          echo?"$var"???#?輸出?*.zip
          echo?$var?????#?輸出所有以?.zip?結(jié)尾的文件

          實際上,這里使用 echo 命令并不是絕對的安全。例如,當(dāng)變量的值包含-n 時,echo 會認(rèn)為它是一個合法的選項而不是要輸出的內(nèi)容(當(dāng)然如果你能夠保證不會有-n 這種值,可以放心地使用 echo 命令)。

          完全可靠的打印變量值的方法是使用 printf:

          printf?"%s\n"?"$foo"

          15. $foo=bar

          略過

          16. foo = bar

          當(dāng)賦值時,等號兩邊是不允許出現(xiàn)空格的,這同 C 語言不一樣。當(dāng)你寫下 foo = bar 時,shell 會將該命令解析成三個單詞,然后第一個單詞 foo 會被認(rèn)為是一個命令,后面的內(nèi)容會被當(dāng)作命令參數(shù)。

          同樣地,下面的寫法也是錯誤的:

          foo=?bar????#?WRONG!
          foo?=bar????#?WRONG!
          $foo?=?bar;?#?COMPLETELY?WRONG!

          正確的寫法應(yīng)該是這樣的:
          "prettyprint?lang-sh">
          foo=bar?????#?Right.
          foo="bar"???#?More?Right.

          17. echo <

          當(dāng)腳本需要嵌入大段的文本內(nèi)容時,here document[15]往往是一個非常有用的工具,它將其中的文本作為命令的標(biāo)準(zhǔn)輸入。不過,echo 命令并不支持從標(biāo)準(zhǔn)輸入讀取內(nèi)容,所以下面的寫法是錯誤的:

          #?This?is?wrong:
          echo?<Hello?world
          How's?it?going?
          EOF


          正確的方法是,使用 cat 命令來完成:

          #?This?is?what?you?were?trying?to?do:
          cat?<Hello?world
          How's?it?going?
          EOF

          或者可以使用雙引號,它也可以跨越多行,而且因為 echo 命令是內(nèi)置命令,相同情況下它會更加高效:

          echo?"Hello?world
          How's?it?going?"

          18. su -c 'some command'

          這種寫法“幾乎”是正確的。問題是,在許多平臺上,su 支持 -c 參數(shù),但是它不一定是你認(rèn)為的。比如,在 OpenBSD 平臺上你這樣執(zhí)行會出錯:

          $?su?-c?'echo?hello'
          su:?only?the?superuser?may?specify?a?login?class

          在這里,-c 是用于指定 login-class[16]。如果你想要傳遞 -c 'some command' 給 shell,最好在之前顯示地指定 username:

          $?su?root?-c?'some?command'?#?Now?it's?right.

          19. cd /foo; bar

          如果你不檢查 cd 命令執(zhí)行是否成功,你可以會在錯誤的目錄下執(zhí)行 bar 命令,這有可能會帶來災(zāi)難,比如 bar 命令是 rm -rf *。

          你必須經(jīng)常檢查 cd 命令執(zhí)行是否有錯誤,簡單的做法是:

          cd?/foo?&&?bar

          如果在 cd 命令后有多個命令,你可以選擇這樣寫:

          cd?/foo?||?exit?1
          bar
          baz
          bat?...?#?Lots?of?commands.

          出錯時,cd 命令會報告無法改變當(dāng)前目錄,同時將錯誤消息輸出到標(biāo)準(zhǔn)錯誤,例如 "bash: cd: /foo: No such file or directory"。如果你想要在標(biāo)準(zhǔn)輸出同時輸出自定義的錯誤提示,可以使用復(fù)合命令(command grouping[17]):

          cd?/net?||?{?echo?"Can't?read?/net.?Make?sure?you've?logged?in?to?the?Samba?network,?and?try?again.";?exit?1;?}
          do_stuff
          more_stuff

          注意,在{號和 echo 之間需要有一個空格,同時}之前要加上分號。

          順便提一下,如果你要在腳本里頻繁改變當(dāng)前目錄,可以看看 pushd/popd/dirs 等命令,可能你在代碼里面寫的 cd/pwd 命令都是沒有必要的。

          說到這,比較下下面兩種寫法:

          find?...?-type?d?-print0?|?while?IFS=?read?-r?-d?''?subdir;?do
          ???here=$PWD
          ???cd?"$subdir"?&&?whatever?&&?...
          ???cd?"$here"
          done
          find?...?-type?d?-print0?|?while?IFS=?read?-r?-d?''?subdir;?do
          ???(cd?"$subdir"?||?exit;?whatever;?...)
          done

          下面的寫法,在循環(huán)中 fork 了一個子 shell 進(jìn)程,子 shell 進(jìn)程中的 cd 命令僅會影響當(dāng)前 ?shell 的環(huán)境變量,所以父進(jìn)程中的環(huán)境命令不會被改變;當(dāng)執(zhí)行到下一次循環(huán)時,無論之前的 cd ?命令有沒有執(zhí)行成功,我們會回到相同的當(dāng)前目錄。這種寫法相較前面的用法,代碼更加干凈。

          20. [ bar == "$foo" ]

          正確的用法 :

          [?bar?=?"$foo"?]?&&?echo?yes
          [[?bar?==?$foo?]]?&&?echo?yes

          21. for i in {1..10}; do ./something &; done

          你不應(yīng)該在 & 后面添加分號,刪除它:

          for?i?in?{1..10};?do?./something?&?done

          或者改成多行的形式:

          for?i?in?{1..10};?do
          ????./something?&
          done

          & 和分號一樣也可以用作命令終止符,所以你不要將兩個混用到一起。一般情況下,分號可以被換行符替換,但是不是所有的換行符都可以用分號替換。

          22. cmd1 && cmd2 || cmd3

          有些人喜歡把 && 和 || 作為 if...then...else...fi 的簡寫語法,在多數(shù)情況下,這種寫法沒有問題。例如:

          [[?-s?$errorlog?]]?&&?echo?"Uh?oh,?there?were?some?errors."?||?echo?"Successful."

          但是,這種結(jié)構(gòu)并不是在所有情況下都完全等價于 if...fi 語法。這是因為在 && 后面的命令執(zhí)行結(jié)束時也會生成一個返回碼,如果該返回碼不是真值(0 代表 true),|| 后面的命令也會執(zhí)行,例如:

          i=0
          true?&&?((i++))?||?((i--))
          echo?$i?#?輸出?0

          看起來上面的結(jié)果應(yīng)該是返回 1,但是結(jié)果卻是輸出 0,為什么呢?原因是這里 i++ 和 i-- 都執(zhí)行了一遍。

          其中,((i++)) 命令執(zhí)行算術(shù)運算,表達(dá)式計算的結(jié)果為 0。這里和 C 語言一樣,表達(dá)式的結(jié)果為 0 被認(rèn)為是 false。所以當(dāng) i=0 的時候,((i++)) 命令執(zhí)行的返回碼為 1(false),從而會執(zhí)行接下來的 ((i--)) 命令。

          如果我們在這里使用前綴自增運算符的話,返回的結(jié)果恰恰為 1,因為 ((++i)) 執(zhí)行的返回碼是 0(true):

          i=0
          true?&&?((?++i?))?||?((?--i?))
          echo?$i?#?Prints?1

          不過在你無法保證 y 的執(zhí)行結(jié)果是,絕對不要依靠 x && y || z 這種寫法。上面這種巧合,在 i 初始化為-1 時也會有問題。

          如果你喜歡代碼更加安全健壯,建議使用 if...fi 語法:

          i=0
          if?true;?then
          ???((i++))
          else
          ???((i--))
          fi

          echo?$i?#?輸出?1

          23. echo "Hello World!"

          在交互式的 Shell 環(huán)境下,你執(zhí)行以上命令會遇到下面的錯誤:

          bash:?!":?event?not?found

          這是因為,在默認(rèn)的交互式 Shell 環(huán)境下,Bash 發(fā)現(xiàn)感嘆號時會執(zhí)行歷史命令展開。在 Shell 腳本中,這種行為是被禁止的,所以不會發(fā)生錯誤。

          不幸地是,你認(rèn)為明顯正確地修復(fù)方法,也不能工作,你會發(fā)現(xiàn)反斜杠并沒有轉(zhuǎn)義感嘆號[18]

          #?echo?"hi\!"
          hi\!

          最簡單地方法是禁用 histexpand 選項,你可以通過 set +H 或者 set +o histexpand 命令來完成。

          下面四種寫法都可以解決:

          #?1.?使用單引號
          echo?'Hello?World!'

          #?2.?禁用?histexpand?選項
          set?+H
          echo?"Hello?World!"

          #?3.?重置?histchars
          histchars=

          #?4.?控制?shell?展開的順序,命令行歷史展開是在單詞拆分之前執(zhí)行的
          #?參見:Bash man 手冊的History Expansion一節(jié)
          exmark='!'
          echo?"Hello,?world$exmark"

          24. for arg in $*

          和大多數(shù) Shell 一樣,Bash 支持依次讀取單個命令行參數(shù)的語法。不過這并是 $*或者 $@,這兩種寫法都不正確,它們只能得到完整的參數(shù)列表,并非單獨的一個個參數(shù)。

          正確的語法是(沒錯要加上引號):

          for?arg?in?"$@"

          #?或者更簡單的寫法
          for?arg

          在腳本中遍歷所有參數(shù)是一個再普遍不過的需求,所以 for arg 默認(rèn)等價于 for arg in "$@"。$@ 使用雙引號后就有特殊的魔力,每個參數(shù)展開后成為一個獨立的單詞。("$@" 等價于 "$1" "$2" "$3" ...)

          下面是一個錯誤的例子 :

          for?x?in?$*;?do
          ???echo?"parameter:?'$x'"
          done

          執(zhí)行的結(jié)果為:

          $?./myscript?'arg?1'?arg2?arg3
          parameter:?'arg'
          parameter:?'1'
          parameter:?'arg2'
          parameter:?'arg3'

          正確的寫法:

          for?x?in?"$@";?do
          ???echo?"parameter:?'$x'"
          done

          執(zhí)行的結(jié)果為:

          $?./myscript?'arg?1'?arg2?arg3
          parameter:?'arg?1'
          parameter:?'arg2'
          parameter:?'arg3'

          上面正確的例子中,第一個參數(shù) 'arg 1' 在展開后依然是一個獨立的單詞,而不會被拆分成兩個。

          25. function foo()

          這種寫法不一定能夠兼容所有 shell,兼容的寫法是:

          foo()?{
          ??...
          }

          26. echo "~"

          波浪號展開(Tilde expansion)[19]僅當(dāng)~沒有引號的時候發(fā)生,在上面的例子中,只會向標(biāo)準(zhǔn)輸出打印~符號,而不是當(dāng)前用戶的家目錄路徑。

          當(dāng)用引號將路徑參數(shù)引起來時, 如果要用引號將相對于家目錄的路徑引起來時,推薦使用 $HOME 而不是 ~, 假如 $HOME 目錄是 "/home/my photos",路徑中包含空格。

          下面是幾組例子:

          "~/dir?with?spaces"?#?expands?to?"~/dir?with?spaces"
          ~"/dir?with?spaces"?#?expands?to?"~/dir?with?spaces"
          ~/"dir?with?spaces"?#?expands?to?"/home/my?photos/dir?with?spaces"
          "$HOME/dir?with?spaces"?#?expands?to?"/home/my?photos/dir?with?spaces"

          27. local varname=$(command)

          當(dāng)在函數(shù)中聲明局部變量時,local[20]作為一個獨立的命令,這種奇特的行為有時候可能會導(dǎo)致困擾。比如,當(dāng)你想要捕獲命令替換[21]的返回碼時,你就不能這樣做。local 命令的返回碼會覆蓋它。

          這種情況下,你只能分成兩行寫:

          local?varname
          varname=$(command)
          rc=$?

          28. export foo=~/bar

          export 與 local 命令一樣,并不是賦值語句的一部分。因此,在有些 Shell 下(比如 Bash),export foo=~/bar 會展開,但是有些(比如 Dash)卻不行。

          下面是兩種比較健壯的寫法:

          foo=~/bar;?export?foo????#?Right!
          export?foo="$HOME/bar"???#?Right!

          29. sed 's/$foo/good bye/'

          單引號內(nèi)部不會展開 $foo 變量,在這里可以換成雙引號:

          foo="hello";?sed?"s/$foo/good?bye/"

          但是要注意,如果你使用了雙引號,就需要考慮更多轉(zhuǎn)義的事情,具體可以看Quotes[22]這一頁。.

          30. tr [A-Z] [a-z]

          這里至少有三個問題。第一個問題是, [A-Z] 和 [a-z] 會被 shell 認(rèn)為是通配符。如果在當(dāng)前目錄下沒用文件名為單個字母的文件,這個命令似乎能正確執(zhí)行,否則會錯誤地執(zhí)行,也許你會在周末耗費許多小時來修復(fù)這個問題。

          第二個問題是,這不是 tr 命令正確的寫法,實際上,上面的命令會把 [轉(zhuǎn)換成 [,將任意大寫字符轉(zhuǎn)換成對應(yīng)的小寫字符,將] 轉(zhuǎn)換成],所以你根本不需要加上括號,這樣第一個問題就可以解決了。

          第三個問題是,上面的命令執(zhí)行結(jié)果依賴于當(dāng)前的?locale[23],A-Z 或者 a-z 不一定會代表 26 個 ASCII 字母。實際上,在一些語言環(huán)境下,z 位于字母表的中間位置。這個問題的解法,取決于你希望發(fā)生的行為是哪一種。

          如果你僅希望改變 26 個英文字母的大小寫(強制 locale 為 C):

          LC_COLLATE=C?tr?A-Z?a-z

          如果你希望根據(jù)實際的語言環(huán)境來轉(zhuǎn)換:

          tr?'[:upper:]'?'[:lower:]'

          31. ps ax | grep gedit

          這里的根本問題是正在運行的進(jìn)程名稱,本質(zhì)上是不可靠的。可能會有多個合法的 gedit 進(jìn)程,也有可能是別的東西偽裝成 gedit 進(jìn)程(改變執(zhí)行命令名稱是一件簡單的事情 ), 更多細(xì)節(jié)可以看ProcessManagement[24]這一篇文章。

          執(zhí)行以上命令,往往會在結(jié)果中包含 grep 進(jìn)程:

          #?ps?ax?|?grep?gedit
          10530??????????S??????6:23?gedit
          32118?pts/0????R+?????0:00?grep?gedit

          這個時候,需要過濾多余的結(jié)果:

          #?ps?ax?|?grep?-v?grep?|?grep?gedit

          上面的寫法比較丑陋,另外一種方法是:

          #?ps?ax?|?grep?[g]edit

          32. printf "$foo"

          如果 $foo 變量的值中包括 \ 或者 % 符號,上面命令的執(zhí)行結(jié)果可能會出乎你的意料之外。

          下面是正確的寫法:

          printf?%s?"$foo"
          printf?'%s\n'?"$foo"

          33. for i in {1..$n}

          Bash 的命令解釋器[25]會優(yōu)先展開大括號[26],所以這時大括號{}表達(dá)式里面看到的是文字上的 $n(沒有展開)。$n 不是一個數(shù)值,所以這里的大括號{}并不會展開成數(shù)字列表??梢?,這導(dǎo)致很難使用大括號來展開大小只能在運行時才知道的列表。

          可以用下面的方法:

          for?((i=1;?i<=n;?i++));?do
          ...
          done

          注:之前我也有寫過一篇文章來介紹這個問題:Shell 生成數(shù)字序列[27]。

          34. if [[ $foo = $bar ]]

          在 [[內(nèi)部,當(dāng) = 號右邊的值沒有用引號引起來,bash 會將它當(dāng)作模式來匹配,而不是一個簡單的字符串。所以,在上面的例子中 ,如果 bar 的值是一個*號,執(zhí)行的結(jié)果永遠(yuǎn)是 true。

          所以,如果你想檢查兩側(cè)的字符串是否相同,等號右側(cè)的值一定要用引號引起來。

          if?[[?$foo?=?"$bar"?]]

          如果你確實要執(zhí)行模式匹配,聰明的做法是取一個更加有意義的變量名(例如 $patt),或者加上注釋說明。

          35. if [[ $foo =~ 'some RE' ]]

          同上,如果 =~號右側(cè)的值加上引號,它會散失特殊的正則表達(dá)式含義,而變成一個普通的字符串。

          如果你想使用一個長的或者復(fù)雜的正則表達(dá)式,避免大量的反斜杠轉(zhuǎn)義,建議把它放在一個變量中:

          re='some?RE'
          if?[[?$foo?=~?$re?]]

          36. [ -n $foo ] or [ -z $foo ]

          這個例子中,$foo 沒有用引號引起來,當(dāng) $foo 包含空格或者 $foo 為空時都會出問題:

          $?foo="some?word"?&&?[?-n?$foo?]?&&?echo?yes
          -bash:?[:?some:?binary?operator?expected

          $?foo=""?&&?[?-n?$foo?]?&&?echo?yes
          yes

          正確的寫法是:

          [?-n?"$foo"?]
          [?-z?"$foo"?]
          [?-n?"$(some?command?with?a?"$file"?in?it)"?]

          [[?-n?$foo?]]
          [[?-z?$foo?]]

          37. [[ -e "$broken_symlink" ]] returns 1 even though $broken_symlink exists

          這里-e 選項是看文件是否存在,當(dāng)緊跟的文件是一個軟鏈接時,它不看軟鏈接是否存在,而是看實際指向的文件是否存在。所以當(dāng)軟鏈接損壞時,即實際指向的文件被刪除后,-e 的結(jié)果返回 1。

          所以如果你確實要判斷后面的文件是否存在,正確的寫法是:

          [[?-e?"$broken_symlink"?||?-L?"$broken_symlink"?]]

          38. ed file <<<"g/d{0,3}/s//e/g" fails

          ed 命令使用的正則語法,不支持 0 次出現(xiàn)次數(shù),下面的就可以正常工作:

          ed?file?<<<"g/d\{1,3\}/s//e/g"

          略過,現(xiàn)在很少會有人用 ed 命令吧。

          39. expr sub-string fails for "match"

          下面的例子多數(shù)情況下運行不會有問題:

          word=abcde
          expr?"$word"?:?".\(.*\)"
          bcde

          但是當(dāng) $work 不巧剛好是 match 時,就有可能出錯了(MAC OSX 下的 expr 命令不支持 match,所以依然能正常工作):

          word=match
          expr?"$word"?:?".\(.*\)"

          原因是 match 是 expr 命令里面的一個特殊關(guān)鍵字,針對 GNU 系統(tǒng),解決方法是在前面加一個 '+':

          word=match
          expr?+?"$word"?:?".\(.*\)"
          atch

          '+' 號可以讓 expr 命令忽略后續(xù) token 的特殊含義。

          另外一個建議是,不要再使用 expr 命令了,expr 能做的事情都可以用 Bash 原生支持的參數(shù)展開(Parameter Expansion[28])或者字符串展開(Substring Expansion)來完成。并且相同情況下,內(nèi)置的功能肯定比外部命令的效率要高。

          上面的例子,目的是為了刪除單詞中的首字符,可以這樣做:

          $?word=match
          $?echo?"${word#?}"????#?PE
          atch
          $?echo?"${word:1}"????#?SE
          atch

          40. On UTF-8 and Byte-Order Marks (BOM)

          多數(shù)情況下,UNIX 下 UTF-8 類型的文本不需要使用 ?BOM,文本的編碼是根據(jù)當(dāng)前語言環(huán)境,MIME 類型或者其它文件元數(shù)據(jù)信息確定的。人為閱讀時,不會因為在文件開始處加 BOM ?標(biāo)記而腚影響,但是當(dāng)文件要被腳本解釋執(zhí)行時,BOM 標(biāo)記會像 MS-DOS 下的換行符(^M)一樣奇怪。

          41. content=$(

          這里沒有什么錯誤,不過你要知道命令替換會刪除結(jié)尾多余的換行符。

          略過,原文給的優(yōu)化方法需要 Bash 4.2+ 以上的版本,手頭沒有這樣的環(huán)境。

          42. somecmd 2>&1 >>logfile

          這是一個很常見的錯誤,顯然你本來是想將標(biāo)準(zhǔn)輸出與標(biāo)準(zhǔn)錯誤輸出都重定向到文件 logfile 中,但是你會驚訝地發(fā)現(xiàn),標(biāo)準(zhǔn)錯誤依然輸出到屏幕中。

          這種行為的原因是,重定向[29]在命令執(zhí)行之前解析,并且是從左往右解析。上面的命令可以翻譯成,將標(biāo)準(zhǔn)錯誤輸出重定向到標(biāo)準(zhǔn)輸出(此刻是終端),然后將標(biāo)準(zhǔn)輸出重定向到文件 logfile 中。所以,到最后,標(biāo)準(zhǔn)錯誤并沒有重定向到文件中,而是依然輸出到終端:

          somecmd?>>logfile?2>&1

          更加詳細(xì)的說明見BashFAQ[30]。

          43. cmd; (( ! $? )) || die

          只有需要捕獲上一個命令的執(zhí)行結(jié)果進(jìn),才需要記錄 $? 的值,否則如果你只需要檢查上一個命令是否執(zhí)行成功,直接檢測命令:

          if?cmd;?then
          ????...
          fi

          或者使用 case 語句來檢測多個或能的返回碼:

          cmd
          status=$?
          case?$status?in
          ????0)
          ????????echo?success?>&2
          ????????;;
          ????1)
          ????????echo?'Must?supply?a?parameter,?exiting.'?>&2
          ????????exit?1
          ????????;;
          ????*)
          ????????echo?'Unknown?error,?exiting.'?>&2
          ????????exit?$status
          esac

          引用鏈接

          [1]

          Bash Pitfalls:?http://mywiki.wooledge.org/BashPitfalls

          [2]

          不應(yīng)該讓腳本去解析 ls 命令的結(jié)果:?http://mywiki.wooledge.org/ParsingLs

          [3]

          WordSplitting:?http://mywiki.wooledge.org/WordSplitting

          [4]

          文件名展開:?http://mywiki.wooledge.org/glob

          [5]

          使用 Find 命令:?http://mywiki.wooledge.org/UsingFind

          [6]

          使用引號:?http://mywiki.wooledge.org/Quotes

          [7]

          單詞拆分:?http://mywiki.wooledge.org/WordSplitting

          [8]

          locale:?http://mywiki.wooledge.org/locale

          [9]

          [[關(guān)鍵字:?http://mywiki.wooledge.org/BashFAQ/031

          [10]

          test 命令:?http://mywiki.wooledge.org/BashFAQ/031

          [11]

          子 Shell:?http://mywiki.wooledge.org/SubShell

          [12]

          Bash FAQ #24:?http://mywiki.wooledge.org/BashFAQ/024

          [13]

          單詞拆分:?http://mywiki.wooledge.org/WordSplitting

          [14]

          文件名展開:?http://mywiki.wooledge.org/glob

          [15]

          here document:?http://www.tldp.org/LDP/abs/html/here-docs.html

          [16]

          -c 是用于指定 login-class:?http://www.openbsd.org/cgi-bin/man.cgi?query=su&sektion=1

          [17]

          command grouping:?http://mywiki.wooledge.org/BashGuide/CompoundCommands

          [18]

          反斜杠并沒有轉(zhuǎn)義感嘆號:?https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html

          [19]

          波浪號展開(Tilde expansion):?https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html

          [20]

          local:?http://tldp.org/LDP/abs/html/localvar.html

          [21]

          命令替換:?http://mywiki.wooledge.org/CommandSubstitution

          [22]

          Quotes:?http://mywiki.wooledge.org/Quotes

          [23]

          locale:?http://mywiki.wooledge.org/locale

          [24]

          ProcessManagement:?http://mywiki.wooledge.org/ProcessManagement

          [25]

          命令解釋器:?http://mywiki.wooledge.org/BashParser

          [26]

          展開大括號:?http://mywiki.wooledge.org/BraceExpansion

          [27]

          Shell 生成數(shù)字序列:?https://kodango.com/generate-number-sequence-in-shell

          [28]

          Parameter Expansion:?http://mywiki.wooledge.org/BashFAQ/073

          [29]

          重定向:?http://wiki.bash-hackers.org/howto/redirection_tutorial

          [30]

          BashFAQ:?http://mywiki.wooledge.org/BashPitfalls#cat_file_.7C_sed_s.2Ffoo.2Fbar.2F_.3E_file

          鏈接:https://kodango.com/bash-pitfalls-part-1

          推薦閱讀:

          7 個非常實用的 Shell 拿來就用腳本實例!

          超硬核!11 個非常實用的 Python 和 Shell 拿來就用腳本實例!

          拿來就用的腳本案例?。ㄈ?/a>

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  五月婷亚洲精品AV天堂 | 蜜av在线 | 黄色无码电影 | 国产一级a一级a免费视频 | 亚洲视频三 |