日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

shell十三问--shell教程

發布時間:2024/3/12 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 shell十三问--shell教程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么80%的碼農都做不了架構師?>>> ??

13_questions_of_shell

shell十三問--shell教程(markdown 版本)

##shell十三問之1: 何為shell?


shell是什么東西之前,不妨讓我們重新審視使用者和計算機系統的關系: (此處為使用者和計算機系統的關系圖)

我們知道計算機的運作不能離開硬件,但使用者卻無法直接操作硬件, 硬件的驅動只能通過一種稱為“操作系統(OS,Opertating System)”的軟件來管控。 事實上,我們每天所談的“linux”,嚴格來說只是一個操作系統(OS), 我們稱之為“內核(kernel)”。

然而,從使用者的角度來說,使用者沒有辦法直接操作一個kernel, 而是通過kernel的“外殼”程序,也就是所謂的shell,來與kernel溝通。 這也正是kernel跟shell的形象命名的的關系。如圖: (此處為kernel-->shell關系圖;)

從技術的角度來說,shell是一個使用者與系統的交互界面(interface), 只能讓使用者通過命令行(command line)來使用系統來完成工作。 因此,shell最簡單的定義就是----命令解釋器( Command Interpreter):

  • 將使用者的命令翻譯給kernel來處理;
  • 同時,將kernel的處理結果翻譯給使用者。

每次當我們完成系統登入(login), 我們就取得一個交互模式的shell, 也稱之為login shell 或者 primary shell。

若從進程(process)的角度來說,我們在shell所下達的命令,均是shell所產生的子進程。 這種現象,我暫可稱之為fork。

如果是執行shell腳本(shell script)的話,腳本中命令則是由另一個非交互模式的 子shell(sub shell)來執行的。 也就是primary shell產生sub shell的進程,而該sub shell 進程再產生script中所有命令的進程。 (關于進程,我們日后有機會在補充)

這里, 我們必須知道:kernel 與 shell 是不同的兩套軟件,而且都是可以被替換的:

  • 不同的OS使用不同的kernel;
  • 同一個kernel之上,也可以使用不同的shell;

在Linux的預設系統中,通常可以找到好幾種不同的shell, 且通常會被記錄在如下文件中:

/etc/shells

不同的shell有著不同的功能,且彼此各異,或者說“大同小異”。 常見的shell主要分為兩大主流:

  • sh:
    • burne shell (sh)
    • burne again shell (bash)
  • csh:
    • c shell (csh)
    • tc shell (tcsh)
    • korn shell (ksh) (FIXME)
  • 大部分的Linux操作系統的預設shell都是bash,其原因大致如下兩種:

    • 自由軟件
    • 功能強大 bash是gnu project最成功的產品之一,自推出以來深受廣大Unix用戶的喜愛, 且也逐漸成為不少組織的系統標準。

    ##shell十三問之2:shell prompt(PS1)與Carriage Return(CR)關系


    當你成功登陸一個shell終端的文字界面之后,大部分的情形下, 你會在屏幕上看到一個不斷閃爍的方塊或者底線(視不同的版本而別), 我們稱之為游標(cursor). cursor作用就是告訴你接下來你從鍵盤輸入的按鍵所插入的位置, 且每輸入一個鍵,cursor便向右移動一個格子, 如果連續輸入太多的話,則自動接在下一行輸入。

    假如你剛完成登陸,還沒有輸入任何按鍵之前, 你所看到的cursor所在的位置的同一行的左邊部分,我們稱之為提示符(prompt)。

    提示符的格式或因不同的版本而各有不同, 在Linux上,只需留意最接近游標的一個提示符號,通常是如下兩者之一:

    • $: 給一般用戶賬號使用;
    • #: 給root(管理員)賬號使用;

    事實上,shell prompt的意思很簡單: 告訴shell使用者,您現在可以輸入命令行了。

    我們可以說,使用者只有在得到shell prompt才能打命令行, 而cursor是指示鍵盤在命令行的輸入位置,使用者每輸入一個鍵, cursor就往后移動一個格,直到碰到命令行讀進CR(Carriage Return, 由Enter鍵產生)字符為止。 CR的意思也很簡單: 使用者告訴shell:老兄,你可以執行的我命令行了。 嚴格來說: 所謂的命令行, 就是在shell prompt與CR之間所輸入的文字。

    (question:為何我們這里堅持使用CR字符而不說Enter按鍵呢? 答案在后面的學習中給出)。

    不同的命令可以接受的命令的格式各有不同, 一般情況下,一個標準的命令行格式為如下所列:

    command-name options argument

    若從技術的細節上來看, shell會依據IFS(Internal Field Seperator) 將 command line 所輸入的文字給拆解為字段(word). 然后在針對特殊的字符(meta)先做處理,最后在重組整行command line。

    (注意:請務必理解以上兩句的意思,我們日后的學習中常回到這里思考。)

    其中IFS是shell預設使用的字段位分隔符號,可以由一個及多個如下按鍵組成:

    • 空白鍵(White Space)
    • 表格鍵(Tab)
    • 回車鍵(Enter)

    系統可以接受的命令的名稱(command-name)可以從如下途徑獲得:

    • 確的路徑所指定的外部命令
    • 命令的別名(alias)
    • shell內建命令(built-in)
    • $PATH之下的外部命令

    每一個命令行均必須包含命令的名稱,這是不能缺少的。

    ##shell十三問之3:別人echo、你也echo,是問echo知多少?

    承接上一章介紹的command line, 這里我們用echo這個命令加以進一步說明。

    溫習 標準的command line三個組成部分:command_name option argument

    echo是一個非常簡單、直接的Linux命令:

    $echo argument

    echo將argument送出到標準輸出(stdout),通常是在監視器(monitor)上輸出。

    Note:

    在linux系統中任何一個進程默認打開三個文件:stdin、stdout、stderr.

    stdin 標準輸入

    stdout 標準輸出

    stderr 標準錯誤輸出

    為了更好理解,不如先讓我們先跑一下echo命令好了:

    $echo$

    你會發現只有一個空白行,然后又回到了shell prompt上了。 這是因為echo在預設上,在顯示完argument之后,還會送出以一個換行符號 (new-line charactor). 但是上面的command echo并沒有任何argument,那結果就只剩一個換行符號。 若你要取消這個換行符號, 可以利用echo的-n 選項:

    $echo -n $

    不妨讓我們回到command line的概念上來討論上例的echo命令好了: command line只有command_name(echo)及option(-n),并沒有顯示任何argument。

    要想看看echo的argument,那還不簡單接下來,你可以試試如下的輸入:

    $echo first line first line $echo -n first line first line $

    以上兩個echo命令中,你會發現argument的部分顯示在你的屏幕, 而換行符則視 -n 選項的有無而別。 很明顯的,第二個echo由于換行符被取消了, 接下來的shell prompt就接在輸出結果的同一行了... ^_^。

    事實上,echo除了-n 選項之外,常用選項有:

    • -e: 啟用反斜杠控制字符的轉換(參考下表)
    • -E: 關閉反斜杠控制字符的轉換(預設如此)
    • -n: 取消行末的換行符號(與-e選項下的\c字符同意)

    關于echo命令所支持的反斜杠控制字符如下表:

    轉義字符字符的意義
    \aALERT / BELL(從系統的喇叭送出鈴聲)
    \bBACKSPACE, 也就是向左退格鍵
    \c取消行末之換行符號
    \EESCAPE, 脫字符鍵
    \fFORMFEED, 換頁字符
    \nNEWLINE, 換行字符
    \rRETURN, 回車鍵
    \tTAB, 表格跳位鍵
    \vVERTICAL TAB, 垂直表格跳位鍵
    \nASCII 八進制編碼(以x開頭的為十六進制),此處的n為數字
    \反斜杠本身

    Note: 上述表格的資料來自O'Reilly出版社的Learning the Bash Shell, 2nd Ed.

    或許,我們可以通過實例來了解echo的選項及控制字符:

    例一:

    $ echo -e "a\tb\tc\n\d\te\tf" a b c d e f $

    上例中,用\t來分割abc還有def,及用\n將def換至下一行。

    例二:

    $echo -e "\141\011\142\011\143\012\144\011\145\011\146" a b c d e f

    與例一中結果一樣,只是使用ASCII八進制編碼。

    例三:

    $echo -e "\x61\x09\x62\x09\x63\x0a\x64\x09\x65\x09\x66" a b c d e f

    與例二差不多,只是這次換用ASCII的十六進制編碼。

    例四:

    $echo -ne "a\tb\tc\nd\te\bf\a" a b c d f $

    因為e字母后面是退格鍵(\b),因此輸出結果就沒有e了。 在結束的時聽到一聲鈴響,是\a的杰作。 由于同時使用了-n選項,因此shell prompt緊接在第二行之后。 若你不用-n的話,那你在\a后再加個\c,也是同樣的效果。

    事實上,在日后的shell操作及shell script設計上, echo命令是最常被使用的命令之一。 比方說,使用echo來檢查變量值:

    $ A=B $ echo $A B $ echo $? 0

    Note: 關于變量的概念,我們留到以下的兩章跟大家說明。

    好了,更多的關于command line的格式, 以及echo命令的選項, 請您自行多加練習、運用了...

    ##shell十三問之4:""(雙引號)與''(單引號)差在哪?

    還是回到我們的command line來吧...

    經過前面兩章的學習,應該很清楚當你在shell prompt后面敲打鍵盤, 直到按下Enter鍵的時候,你輸入的文字就是command line了, 然后shell才會以進程的方式執行你所交給它的命令。 但是,你又可知道:你在command line中輸入的每一個文字, 對shell來說,是有類別之分的呢?

    簡單而言,(我不敢說精確的定義,注1), command line的每一個charactor, 分為如下兩種:

    • literal:也就是普通的純文字,對shell來說沒特殊功能;
    • meta: 對shell來說,具有特定功能的特殊保留元字符。

    Note:

    對于bash shell在處理comamnd line的順序說明, 請參考O'Reilly出版社的Learning the Bash Shell,2nd Edition, 第177-180頁的說明,尤其是178頁的流程圖:Figure 7-1 ...

    literal沒什么好談的, 像abcd、123456這些"文字"都是literal...(so easy? ^_^) 但meta卻常使我們困惑...(confused?) 事實上,前兩章,我們在command line中已碰到兩個 似乎每次都會碰到的meta:

    • IFS:有space或者tab或者Enter三者之一組成(我們常用space)
    • CR: 由Enter產生;

    IFS是用來拆解command line中每一個詞(word)用的, 因為shell command line是按詞來處理的。 而CR則是用來結束command line用的,這也是為何我們敲Enter鍵, 命令就會跑的原因。

    除了常用的IFS與CR, 常用的meta還有:

    meta字符meta字符作用
    =設定變量
    $作變量或運算替換(請不要與shell prompt混淆)
    >輸出重定向(重定向stdout)
    <輸入重定向(重定向stdin)
    |命令管道
    &重定向file descriptor或將命令至于后臺(bg)運行
    ()將其內部的命令置于nested subshell執行,或用于運算或變量替換
    {}將期內的命令置于non-named function中執行,或用在變量替換的界定范圍
    ;在前一個命令執行結束時,而忽略其返回值,繼續執行下一個命令
    &&在前一個命令執行結束時,若返回值為true,繼續執行下一個命令
    ||在前一個命令執行結束時,若返回值為false,繼續執行下一個命令
    !執行histroy列表中的命令
    ......

    假如我們需要在command line中將這些保留元字符的功能關閉的話, 就需要quoting處理了。

    在bash中,常用的quoting有以下三種方法:

    • hard quote:''(單引號),凡在hard quote中的所有meta均被關閉;
    • soft quote:""(雙引號),凡在soft quote中大部分meta都會被關閉,但某些會保留(如$);
    • escape: \ (反斜杠),只有在緊接在escape(跳脫字符)之后的單一meta才被關閉;

    Note:

    在soft quote中被豁免的具體meta清單,我不完全知道, 有待大家補充,或通過實踐來發現并理解。

    下面的例子將有助于我們對quoting的了解:

    $ A=B C #空白符未被關閉,作為IFS處理 $ C:command not found. $ echo $A$ A="B C" #空白符已被關掉,僅作為空白符 $ echo $A B C

    在第一個給A變量賦值時,由于空白符沒有被關閉, command line 將被解釋為: A=B 然后碰到<IFS>,接著執行C命令 在第二次給A變量賦值時,由于空白符被置于soft quote中, 因此被關閉,不在作為IFS; A=B<space>C 事實上,空白符無論在soft quote還是在hard quote中, 均被關閉。Enter鍵字符亦然:

    $ A=`B > C > ' $ echo "$A" B C

    在上例中,由于enter被置于hard quote當中,因此不再作為CR字符來處理。 這里的enter單純只是一個斷行符號(new-line)而已, 由于command line并沒得到CR字符, 因此進入第二個shell prompt(PS2, 以>符號表示), command line并不會結束,直到第三行, 我們輸入的enter并不在hard quote里面, 因此沒有被關閉, 此時,command line碰到CR字符,于是結束,交給shell來處理。

    上例的Enter要是被置于soft quote中的話,CR字符也會同樣被關閉:

    $ A="B > C > " $ echo $A B C

    然而,由于 echo $A時的變量沒有置于soft quote中, 因此,當變量替換完成后,并作命令行重組時,enter被解釋為IFS, 而不是new-line字符。

    同樣的,用escape亦可關閉CR字符:

    $ A=B\ > C\ > $ echo $A BC

    上例中的,第一個enter跟第二個enter均被escape字符關閉了, 因此也不作為CR來處理,但第三個enter由于沒有被escape, 因此,作為CR結束command line。 但由于enter鍵本身在shell meta中特殊性,在 \ escape字符后面 僅僅取消其CR功能, 而不保留其IFS功能。

    你或許發現光是一個enter鍵所產生的字符,就有可能是如下這些可能:

    • CR
    • IFS
    • NL(New Line)
    • FF(Form Feed)
    • NULL
    • ...

    至于,什么時候解釋為什么字符,這個我就沒法去挖掘了, 或者留給讀者君自行慢慢摸索了...^-^

    至于soft quote跟hard quote的不同,主要是對于某些meta的關閉與否,以$來做說明:

    $ A=B\ C $ echo "$A" B C $ echo '$A' $A

    在第一個echo命令行中,$被置于soft quote中,將不被關閉, 因此繼續處理變量替換, 因此,echo將A的變量值輸出到屏幕,也就是"B C"的結果。

    在第二個echo命令行中,$被置于hard quote中,則被關閉, 因此,$只是一個$符號,并不會用來做變量替換處理, 因此結果是$符號后面接一個A字母:$A.

    練習與思考: 如下結果為何不同?

    tips: 單引號和雙引號,在quoting中均被關閉了。

    $ A=B\ C $ echo '"$A"' #最外面的是單引號 "$A" $ echo "'$A'" #最外面的是雙引號 'B C'

    在CU的shell版里,我發現很多初學者的問題, 都與quoting的理解有關。 比方說,若我們在awk或sed的命令參數中, 調用之前設定的一些變量時,常會問及為何不能的問題。

    要解決這些問題,關鍵點就是:區分出 shell meta 與 command meta

    前面我們提到的那些meta,都是在command line中有特殊用途的, 比方說{}就是將一系列的command line置于不具名的函數中執行(可簡單視為command block), 但是,awk卻需要用{}來區分出awk的命令區段(BEGIN,MAIN,END). 若你在command line中如此輸入:

    $ awk {print $0} 1.txt

    由于{}在shell中并沒有關閉,那shell就將{print $0}視為command block, 但同時沒有;符號作命令分隔,因此,就出現awk語法錯誤結果。

    要解決之,可用hard quote:

    awk '{print $0}'

    上面的hard quote應好理解,就是將原來的 {、<space>、$、}這幾個shell meta關閉, 避免掉在shell中遭到處理,而完整的成為awk的參數中command meta。

    Note:

    awk中使用的$0 是awk中內建的field nubmer,而非awk的變量, awk自身的變量無需使用$.

    要是理解了hard quote的功能,在來理解soft quote與escape就不難:

    awk "{print \$0}" 1.txt awk \{print \$0\} 1.txt

    然而,若要你改變awk的$0的0值是從另一個shell變量中讀進呢? 比方說:已有變量$A的值是0, 那如何在command line中解決 awk的$$A呢? 你可以很直接否定掉hard quote的方案:

    $ awk '{print $$A}' 1.txt

    那是因為$A的$在hard quote中是不能替換變量的。

    聰明的讀者(如你!),經過本章的學習,我想,你應該可以理解為 為何我們可以使用如下操作了吧:

    A=0 awk "{print \$$A}" 1.txt awk \{print\ \$$A\} 1.txt awk '{print $'$A'}' 1.txt awk '{print $'"$A"'}' 1.txt

    或許,你能給出更多方案... ^_^

    更多練習:

    • http://bbs.chinaunix.net/forum/viewtopic.php?t=207178 一個關于read命令的小問題: 很早以前覺得很奇怪:執行read命令,然后讀取用戶輸入給變量賦值, 但如果輸入是以空格鍵開始的話,這空格會被忽略,比如:
    read a #輸入: abc echo "$a" #只輸出abc

    原因: 變量a的值,從終端輸入的值是以IFS開頭,而這些IFS將被shell解釋器忽略(trim)。 應該與shell解釋器分詞的規則有關;

    read a #輸入:\ \ \ abc echo "$a" #只輸出abc

    需要將空格字符轉義

    Note:

    IFS Internal field separators, normally space, tab, and newline (see Blank Interpretation section). ...... Blank Interpretation After parameter and command substitution, the results of substitution
    are scanned for internal field separator characters (those found in IFS) and split into distinct arguments where such characters are found. Explicit null arguments ("" or '') are retained.
    Implicit null arguments(those resulting from parameters that have no values) are removed. (refre to: man sh)

    解決思路:

  • shell command line 主要是將整行line給分解(break down)為每一個單詞(word);
  • 而詞與詞之間的分隔符就是IFS (Internal Field Seperator)。
  • shell會對command line作處理(如替換,quoting等), 然后再按詞重組。(注:別忘了這個重組特性)
  • 當你用IFS來事開頭一個變量值,那shell會先整理出這個詞,然后在重組command line。 5.然而,你將IFS換成其他,那shell將視你哪些space/tab為“詞”,而不是IFS。那在重組時,可以得到這些詞。
  • 若你還是不理解,那來驗證一下下面這個例子:

    $ A=" abc" $ echo $A abc $ echo "$A" #note1abc $ old_IFS=$IFS $ IFS=; $ echo $Aabc $ IFS=$old_IFS $ echo $A abc

    Note:

  • 這里是用 soft quoting 將里面的 space 關閉,使之不是 meta(IFS), 而是一個literal(white space);
  • IFS=; 意義是將IFS設置為空字符,因為;是shell的元字符(meta);
  • 問題二:為什么多做了幾個分號,我想知道為什么會出現空格呢?

    $ a=";;;test" $ IFS=";" $ echo $a test $ a=" test" $ echo $a test $ IFS=" " $ echo $a test

    解答:

    這個問題,出在IFS=;上。 因為這個;在問題一中的command line上是一個meta, 并非";"符號本身。 因此,IFS=;是將IFS設置為 null charactor (不是space、tab、newline)。

    要不是試試下面這個代碼片段:

    $ old_IFS=$IFS $ read A ;a;b;c $ echo $A ;a;b;c $ IFS=";" #Note2 $ echo $A a b c

    Note:

    要關閉;可用";"或者';'或者\;。

    • http://bbs.chinaunix.net/forum/viewtopic.php?t=216729

    思考問題二:文本處理:讀文件時,如何保證原汁原味。

    cat file | while read i doecho $i done

    文件file的行中包含若干空,經過read只保留不重復的空格。 如何才能所見即所得。

    cat file | while read i doecho "X${i}X" done

    從上面的輸出,可以看出read,讀入是按整行讀入的; 不能原汁原味的原因:

  • 如果行的起始部分有IFS之類的字符,將被忽略;
  • echo $i的解析過程中,首先將$i替換為字符串, 然后對echo 字符串中字符串分詞,然后命令重組,輸出結果; 在分詞,與命令重組時,可能導致多個相鄰的IFS轉化為一個;
  • cat file | while read i doecho "$i" done

    以上代碼可以解決原因2中的,command line的分詞和重組導致meta字符丟失; 但仍然解決不了原因1中,read讀取行時,忽略行起始的IFS meta字符。

    回過頭來看上面這個問題:為何要原汁原味呢? cat命令就是原汁原味的,只是shell的read、echo導致了某些shell的meta字符丟失;

    如果只是IFS meta的丟失,可以采用如下方式: 將IFS設置為null,即IFS=;, 在此再次重申此處;是shell的meta字符,而不是literal字符; 因此要使用literal的 ;應該是\; 或者關閉meta 的(soft/hard) quoting的";"或者';'。

    因此上述的解決方案是:

    old_IFS=$IFS IFS=; #將IFS設置為null cat file | while read i doecho "$i" done IFS=old_IFS #恢復IFS的原始值

    現在,回過頭來看這個問題,為什么會有這個問題呢; 其本源的問題應該是沒有找到解決原始問題的最合適的方法, 而是采取了一個迂回的方式來解決了問題;

    因此,我們應該回到問題的本源,重新審視一下,問題的本質。 如果要精準的獲取文件的內容,應該使用od或者hexdump會更好些。

    ##shell十三問之5:問var=value 在export前后的差在哪?

    這次讓我們暫時丟開command line, 先了解一下bash變量(variable)吧...

    所謂的變量,就是利用一個固定的"名稱"(name), 來存取一段可以變化的"值"(value)。

    ###1. 變量設定(set) 在bash中, 你可以用"="來設定或者重新定義變量的內容:

    name=value

    在設定變量的時候,得遵守如下規則:

    • 等號左右兩邊不能使用分隔符號(IFS),也應避免使用shell的保留元字符(meta charactor);
    • 變量的名稱(name)不能使用$符號;
    • 變量的名稱(name)的首字符不能是數字(number)。
    • 變量的名稱(name)的長度不可超過256個字符。
    • 變量的名稱(name)及變量的值的大小寫是有區別的、敏感的(case sensitive,)

    如下是一些變量設定時常見的錯誤:

    A= B #=號前后不能有IFS 1A=B #變量名稱不能以數字開頭 $A=B #變量的名稱里有$ a=B #這跟a=b是不同的,(這不是錯誤,提醒windows用戶)

    如下則是可以接受的設定:

    A=" B" #IFS被關閉,參考前面的quoting章節 A1=B #并非以數字開頭 A=$B #$可用在變量的值內 This_Is_A_Long_Name=b #可用_連接較長的名稱或值,且有大小區別;

    ###2. 變量替換(substitution) shell 之所以強大,其中的一個因素是它可以在命令行中對變量作 替換(substitution)處理。 在命令行中使用者可以使用$符號加上變量名稱(除了用=定義變量名稱之外), 將變量值給替換出來,然后再重新組建命令行。

    比方:

    $ A=ls $ B=la $ C=/tmp $ $A -$B $C

    以上命令行的第一個$是shell prompt, 并不在命令行之內。 必須強調的是,我們所提的變量替換,只發生在command line上面。 (是的,請讓我們再次回到命令行吧!) 仔細分析,最后那行 command line,不難發現在被執行前(在輸入CR字符之前), $符號對每一個變量作替換處理(將變量的值替換出來再重組命令行), 最后會得出如下命令行:

    ls -la /tmp

    還記得第二章,我請大家"務必理解"的那兩句嗎? 若你忘了,我這里重貼一遍:

    Note:

    若從技術的細節來看,shell會依據IFS(Internal Field Seperator) 將command line所輸入的文字拆解為"字段"(word/field)。 然后再針對特殊字符(meta)先作處理,最后重組整行command line。

    這里的$就是command line中最經典的meta之一了, 就是作變量替換的。在日常的shell操作中, 我們常會使用echo命令來查看特定的變量的值, 例如:

    $ echo $A -$B $C

    我們已學過,echo命令只單純將其argument送至"標準輸出"(stdout, 通常是我們的屏幕)。 所以上面的命令會在屏幕上得到如下結果:

    ls -al /tmp

    這是由于echo命令在執行時,會先將$A (ls)、$B (la)跟$C (/tmp)給替換出來; 利用shell對變量的替換處理能力,我們在設定變量時就更為靈活了:

    A=B B=$A

    這樣,B的變量值就可繼承A變量"當時"的變量值了。 不過,不要以"數學邏輯"來套用變量的設定,比方說:

    A=B B=C

    這樣,并不會讓A的變量值變成C。再如:

    A=B B=$A A=C

    同樣也不會讓B的值變成C。

    上面是單純定義了兩個不同名稱的變量: A 與 B, 它們的取值分別是C與B。

    若變量被重復定義的話,則原有值為新值所取代。(這不正是"可變的量"嗎?^_^) 當我們在設定變量的時候,請記住這點:用一個名稱存儲一個數值, 僅此而已。

    此外, 我們也可以利用命令行的變量替換能力來"擴充"(append)變量的值:

    A=B:C:D A=$A:E

    這樣, 第一行我們設定A的值為"B:C:D", 然后,第二行再將值擴充為"B:C:D:E"。

    上面的擴充的范例,我們使用分隔符號(:)來達到擴充的目的, 要是沒有分隔符的話,如下是有問題的:

    A=BCD B=$AE

    因為第二次是將A的值繼承$AE的替換結果,而非$A再加E。 要解決此問題,我們可用更嚴謹的替換處理:

    A=BCD A=${A}E

    上例中,我們使用{}將變量名稱范圍給明確定義出來, 如此一來, 我們就可以將A的變量值從BCD給擴充為BCDE。

    Tips: 關于${name}事實上還可以做到更多的變量處理能力, 這些均屬于比較進階階段的變量處理,現階段暫不介紹了, 請大家自行參考資料。

    ###3. export 變量

    嚴格來說,我們在當前shell中所定義的變量,均屬于 "本地變量"(local variable), 只有經過export命令的 "輸出"處理,才能成為"環境變量"(environment variable):

    $ A=B $ export A

    或者

    $ export A=B

    經過export輸出處理之后,變量A就能成為一個環境變量 供其后的命令使用。在使用export的時候,請別忘記 shell在命令行對變量的"替換"(substitution)處理。 比方說:

    $ A=B $ B=C $ export $A

    上面的命令并未將A輸出為"環境變量",而是將B導出 這是因為在這個命令行中,$A會首先被替換為B,然后在"塞回" 作export的參數。

    要理解這個export,事實上需要從process(進程)的角度來理解才能透徹。 我們將于下一章為大家說明process(進程)的概念,敬請留意。

    ####4. 取消變量(unset) 要取消一個變量,在bash中可使用unset命令來處理:

    unset A

    與export一樣,unset命令行,也同樣會作 變量替換(這其實是shell的功能之一), 因此:

    $ A=B $ B=C $ unset $A

    事實上,所取消的是變量B而不是A。

    此外,變量一旦經過unset取消之后, 其結果是將整個變量拿掉,而不是取消變量的值。

    如下兩行其實是很不一樣的:

    $ A= $ unset A

    第一行只是將變量A設定為"空值"(null value), 但第二行則是讓變量A不存在。 雖然用眼睛來看, 這兩種變量的狀態在如下的命令結果中都是一樣的:

    $ A= $ echo $A$ unset A $ echo $A

    請學員務必能識別null value 與 unset的本質區別, 這在一些進階的變量處理上是很嚴格的。

    比方說:

    $ str= #設為null $ var=${str=expr} #定義var $ echo $var$ echo $str$ unset str #取消str $ var=${str=expr} #定義var $ echo $var expr $ echo $str expr

    聰明的讀者(yes, you!),稍加思考的話, 應該不難發現為何同樣的var=${str=expr} 在str為null與unset之下的不同吧? 若你看不出來,那可能是如下原因之一:

    • 你太笨了
    • 不了解 var=${str=expr} 這個進階處理
    • 對本篇說明還沒有來得及消化吸收
    • 我講得不好

    不知,您選哪個呢?...... ^_^.

    ##shell十三問之6:exec跟source差在哪?

    這次讓我們從CU shell版的一個實例帖子來談起吧: (論壇改版后,原鏈接已經失效)

    例中的提問原文如下:

    帖子提問:

    cd /etc/aa/bb/cc可以執行 但是把這條命令放入shell腳本后,shell腳本不執行! 這是什么原因?

    意思是:運行shell腳本,并沒有移動到/etc/aa/bb/cc目錄。

    我當時如何回答暫時別去深究,先讓我們了解一下進程 (process)的概念好了。

    首先,我們所執行的任何程序,都是父進程(parent process)產生的一個 子進程(child process),子進程在結束后,將返回到父進程去。 此現象在Linux中被稱為fork。

    (為何要稱為fork呢? 嗯,畫一下圖或許比較好理解...^_^)

    當子進程被產生的時候,將會從父進程那里獲得一定的資源分配、及 (更重要的是)繼承父進程的環境。

    讓我們回到上一章所談到的"環境變量"吧: 所謂環境變量其實就是那些會傳給子進程的變量。 簡單而言, "遺傳性"就是區分本地變量與環境變量的決定性指標。 然而,從遺傳的角度來看,我們不難發現環境變量的另一個重要特征: 環境變量只能從父進程到子進程單向傳遞。 換句話說:在子進程中環境如何變更,均不會影響父進程的環境。

    接下來,在讓我們了解一下shell腳本(shell script)的概念. 所謂shell script 講起來很簡單,就是將你平時在shell prompt輸入的多行 command line, 依序輸入到一個文件文件而已。

    再結合以上兩個概念(process + script),那應該不難理解如下的這句話的意思了: 正常來說,當我們執行一個shell script時,其實是先產生一個sub-shell的子進程, 然后sub-shell再去產生命令行的子進程。 然則,那讓我們回到本章開始時,所提到的例子在重新思考:

    帖子提問:

    cd /etc/aa/bb/cc可以執行 但是把這條命令放入shell腳本后,shell腳本不執行! 這是什么原因?

    意思是:運行shell腳本,并沒有移動到/etc/aa/bb/cc目錄。

    我當時的答案是這樣的:

    因為,我們一般跑的shell script是用sub-shell去執行的。 從process的概念來看,是 parent process產生一個child process去執行, 當child結束后,返回parent, 但parent的環境是不會因child的改變而改變的。 所謂的環境變量元數很多,如effective id(euid),variable, working dir等等... 其中的working dir($PWD) 正是樓主的疑問所在: 當用sub-shell來跑script的話,sub-shell的$pwd會因為cd而變更, 但返回primary shell時,$PWD是不會變更的。

    能夠了解問題的原因及其原理是很好的,但是? 如何解決問題,恐怕是我們更應該感興趣的是吧?

    那好,接下來,再讓我們了解一下source命令好了。 當你有了fork的概念之后,要理解soruce就不難:

    所謂source,就是讓script在當前shell內執行、 而不是產生一個sub-shell來執行。 由于所有執行結果均在當前shell內執行、而不是產生一個sub-shell來執行。

    因此, 只要我們原本單獨輸入的script命令行,變成source命令的參數, 就可輕而易舉地解決前面提到的問題了。

    比方說,原本我們是如此執行script的:

    $ ./my_script.sh

    現在改成這樣既可:

    $ source ./my_script.sh

    或者:

    $ . ./my_script.sh

    說到這里,我想,各位有興趣看看/etc底下的眾多設定的文件, 應該不難理解它們被定義后,如何讓其他script讀取并繼承了吧?

    若然,日后,你有機會寫自己的script, 應也不難專門指定一個設定的文件以供不同的script一起"共用"了... ^_^

    okay,到這里,若你搞懂fork與source的不同, 那接下來再接受一個挑戰:

    那exec又與source/fork有何不同呢?

    哦...要了解exec或許較為復雜,尤其是扯上File Decscriptor的話... 不過,簡單來說:

    exec 也是讓script在同一個進程上執行,但是原有進程則被結束了。 簡言之,原有進程能否終止,就是exec與source/fork的最大差異了。

    嗯,光是從理論去理解,或許沒那么好消化, 不如動手"實踐+思考"來得印象深刻哦。

    下面讓我們為兩個簡單的script,分別命名為1.sh以及2.sh

    1.sh

    #!/bin/bash A=B echo "PID for 1.sh before exec/source/fork:$$"export A echo "1.sh: \$A is $A"case $1 inexec)echo "using exec..."exec ./2.sh ;;source)echo "using source...". ./2.sh ;;*)echo "using fork by default..."./2.sh ;; esacecho "PID for 1.sh after exec/source/fork:$$" echo "1.sh: \$A is $A"

    2.sh

    #!/bin/bashecho "PID for 2.sh: $$" echo "2.sh get \$A=$A from 1.sh"A=C export A echo "2.sh: \$A is $A"

    然后分別跑如下參數來觀察結果:

    $ ./1.sh fork $ ./1.sh source $ ./1.sh exec

    好了,別忘了仔細比較輸出結果的不同及背后的原因哦... 若有疑問,歡迎提出來一起討論討論~~~~

    happy scripting! ^_^

    ##shell十三問之7:()與{}差在哪?

    嗯,這次輕松一下,不講太多... ^_^

    先說一下,為何要用()或者{}好了。

    許多時候,我們在shell操作上,需要在 一定的條件下執行多個命令,也就是說, 要么不執行,要么就全執行,而不是每次 依序的判斷是否要執行下一個命令。

    或者,要從一些命令執行的先后次序中得到結果, 如算術運算的2*(3+4)那樣...

    這時候,我們就可以引入"命令群組"(command group) 的概念:將許多命令集中處理。

    在shell command line中,一般人或許不太計較()與 {}這兩對符號的差異,雖然兩者都可以將多個命令當作群組處理, 但若從技術細節上,卻是很不一樣的:

    • () 將command group置于sub-shell(子shell)中去執行,也稱 nested sub-shell。
    • {} 則是在同一個shell內完成,也稱non-named command group。

    若你對上一章的fork與source的概念還記得的話, 那就不難理解兩者的差異了。

    要是在 command group中扯上變量及其他環境的修改, 我們可以根據不同的需求來使用()或{}。 通常而言, 若所作的修改是臨時的,且不想影響原有或以后的設定, 那我們就使用nested sub-shell, 即(); 反之,則用non-named command group, 即{}。

    是的,光從command line來看,() 與 {}差別就講完了,夠輕松吧~~~, ^_^

    然而,這兩個meta用在其他command meta或領域中(如Regular Expression), 還是有很多差別的。 只是,我不打算再去說明了,留給讀者慢慢發掘好了...

    我這里只想補充一個概念,就是function。 所謂function,就是用一個名字去命名一個command group, 然后再調用這個名字去執行command group。

    從non-named command group來推斷, 大概你也可以推測到我要說的是{}了吧?(yes! 你真聰明 ^_^)

    在bash中,function的定義方式有兩種:

    • 方式一:
    function function_name {command1command2command3..... }
    • 方式二:
    function_name () {command1command2command3...... }

    用哪一種方式無所謂, 只是碰到所定義的名稱與現有的命令或者別名沖突的話, 方式二或許會失敗。 但方式二起碼可以少打個function這一串英文字符, 對懶人來說(如我),有何樂而不為呢?...^_^

    function 在一定程度上來說,也可以稱為"函數", 但請不要與傳統編程所使用的"函數"(library)搞混了, 畢竟兩者差異很大。 唯一相同的是,我們都可以隨時用"已定義的名稱"來調用它們...

    若我們在shell操作中,需要不斷地重復某些命令, 我們首先想到的,或許是將命令寫成shell腳本(shell script)。 不過,我們也可以寫成function, 然后在command line中打上function_name就可當一般的shell script使用了。

    若只是你在shell中定義的function, 除了用unset function_name取消外, 一旦你退出shell, function也跟著消失。 然而,在script中使用function卻有許多好處, 除了提高整體script的執行性能外(因為已經載入), 還可以節省許多重復的代碼......

    簡單而言,若你會將多個命令寫成script以供調用的話, 那你可以將function看成script中script。... ^_^

    而且通過上一章節介紹的source命令, 我們可以自行定義許許多多好用的function, 在集中寫在特定文件中, 然后,在其他的script中用source將它們載入,并反復執行。

    若你是RedHat Linux的使用者, 或許,已經猜出 /etc/rc.d/init.d/functions這個文件時啥作用了~~~ ^_^

    okay,說要輕松點的嘛,那這次就暫時寫到這吧。 祝大家學習愉快,^_^

    ##shell十三問之8: $(())與$()還有${}差在哪?

    我們上一章介紹了()與{}的不同, 這次讓我們擴展一下,看看更多的變化: $()與${}又是啥玩意兒呢?

    在bash shell中, $()與``(反引號)都是用來做 命令替換(command substitution)的。

    所謂的命令替換與我們第五章學過的變量替換差不多, 都是用來重組命令行: 完成 `` 或者$()里面的 命令,將其結果替換出來, 再重組命令行。

    例如:

    $ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)

    如此便可方便得到上一個星期天的日期了...^_^

    在操作上, 用$()或``都無所謂, 只是我個人比較喜歡用$(),理由是:

  • ``(反引號)很容易與''(單引號)搞混亂,尤其對初學者來說。 有時在一些奇怪的字形顯示中,兩種符號是一模一樣的(只取兩點)。 當然了有經驗的朋友還是一眼就能分辨兩者。只是,若能更好的避免混亂, 又何樂而不為呢? ^_^

  • 在多次的復合替換中, ``需要額外的轉義(escape, )處理,而$()則比較直觀。 例如,一個錯誤的使用的例子:

  • command1 `command2 `command3` `

    原來的本意是要在command2 `command3` , 先將command3替換出來給command2處理, 然后再將command2的處理結果,給command1來處理。 然而真正的結果在命令行中卻是分成了`command2`與 ``。

    正確的輸入應該如下:

    command1 `command2 \`command3\` `

    要不然換成$()就沒有問題了:

    command1 $(commmand2 $(command3))

    只要你喜歡,做多少層的替換都沒有問題~~~^_^

    不過,$()并不是沒有弊端的... 首先,``基本上可用在所有的unix shell中使用, 若寫成 shell script,其移植性比較高。 而$()并不是每一種shell都能使用,我只能說, 若你用bash2的話,肯定沒問題... ^_^

    接下來,再讓我們看看${}吧...它其實就是用來做 變量替換用的啦。 一般情況下,$var與${var}并沒有啥不一樣。 但是用${}會比較精準的界定變量名稱的范圍, 比方說:

    $ A=B $ echo $AB

    原本是打算先將$A的結果替換出來, 然后在其后補一個字母B; 但命令行上, 真正的結果卻是替換變量名稱為AB的值出來... 若使用${}就沒有問題了:

    $ A=B $ echo ${A}B $ BB

    不過,假如你只看到${}只能用來界定變量名稱的話, 那你就實在太小看bash了。

    為了完整起見,我這里再用一些例子加以說明${}的一些 特異功能: 假設我們定義了一個變量file為:

    file=/dir1/dir2/dir3/my.file.txt

    我們可以用${}分別替換獲得不同的值:

    ####1. shell字符串的非貪婪(最小匹配)左刪除

    ${file#*/} #其值為:dir1/dir2/dir3/my.file.txt

    拿掉第一個/及其左邊的字符串,其結果為: dir1/dir2/dir3/my.file.txt 。

    ${file#*.} #其值為:file.txt

    拿掉第一個.及其左邊的字符串,其結果為: file.txt 。

    ####2. shell字符串的貪婪(最大匹配)左刪除:

    ${file##*/} #其值為:my.file.txt

    拿掉最后一個/及其左邊的字符串,其結果為: my.file.txt

    ${file##*.} #其值為:txt

    拿掉最后一個.及其左邊的字符串,其結果為: txt

    ####3. shell字符串的非貪婪(最小匹配)右刪除:

    ${file%/*} #其值為:/dir1/dir2/dir3

    拿掉最后一個/及其右邊的字符串,其結果為: /dir1/dir2/dir3。

    ${file%.*} #其值為:/dir1/dir2/dir3/my.file

    拿掉最后一個.及其右邊的字符串,其結果為: /dir1/dir2/dir3/my.file。

    ####4. shell字符串的貪婪(最大匹配)右刪除:

    ${file%%/*} #其值為:其值為空。

    拿掉第一個/及其右邊的字符串,其結果為: 空串。

    ${file%%.*} #其值為:/dir1/dir2/dir3/my。

    拿掉第一個.及其右邊的字符串,其結果為: /dir1/dir2/dir3/my。

    Tips:

    記憶方法:

    #是去掉左邊(在鍵盤上#在$的左邊);

    %是去掉右邊(在鍵盤上%在$的右邊);

    單個符號是最小匹配;

    兩個符號是最大匹配;

    ####5. shell字符串取子串:

    ${file:0:5} #提取最左邊的5個字符:/dir1${file:5:5} #提取第5個字符及其右邊的5個字符:/dir2

    shell字符串取子串的格式:${s:pos:length}, 取字符串s的子串:從pos位置開始的字符(包括該字符)的長度為length的的子串; 其中pos為子串的首字符,在s中位置; length為子串的長度;

    Note: 字符串中字符的起始編號為0.

    ####6. shell字符串變量值的替換:

    ${file/dir/path} #將第一個dir替換為path:/path1/dir2/dir3/my.file.txt ${file//dir/path} #將全部的dir替換為path:/path1/path2/path3/my.file.txt

    shell字符串變量值的替換格式:

    • 首次替換: ${s/src_pattern/dst_pattern} 將字符串s中的第一個src_pattern替換為dst_pattern。

    • 全部替換: ${s//src_pattern/dst_pattern} 將字符串s中的所有出現的src_pattern替換為dst_pattern.

    ####7. ${}還可針對變量的不同狀態(沒設定、空值、非空值)進行賦值:

    • ${file-my.file.txt} #如果file沒有設定,則使用 使用my.file.txt作為返回值, 否則返回${file};(空值及非空值時,不作處理。);

    • ${file:-my.file.txt} #如果file沒有設定或者${file}為空值, 均使用my.file.txt作為其返回值,否則,返回${file}.(${file} 為非空值時,不作處理);

    • ${file+my.file.txt} #如果file已設定(為空值或非空值), 則使用my.file.txt作為其返回值,否則不作處理。(未設定時,不作處理);

    • ${file:+my.file.txt} #如果${file}為非空值, 則使用my.file.txt作為其返回值,否則,(未設定或者為空值時)不作處理。

    • ${file=my.file.txt} #如果file為設定,則將file賦值為my.file.txt,同時將${file}作為其返回值;否則,file已設定(為空值或非空值),則返回${file}。

    • ${file:=my.file.txt} #如果file未設定或者${file}為空值, 則my.file.txt作為其返回值, 同時,將${file}賦值為my.file.txt,否則,(非空值時)不作處理。

    • ${file?my.file.txt} #如果file沒有設定,則將my.file.txt輸出至STDERR, 否側, 已設定(空值與非空值時),不作處理。

    • ${file:?my.file.txt} #若果file未設定或者為空值,則將my.file.txt輸出至STDERR,否則, 非空值時,不作任何處理。

    Tips:

    以上的理解在于,你一定要分清楚,unset與null以及non-null這三種狀態的賦值; 一般而言,與null有關,若不帶:, null不受影響; 若帶 :, 則連null值也受影響。

    ####8. 計算shell字符串變量的長度:${#var}

    ${#file} #其值為27, 因為/dir1/dir2/dir3/my.file.txt剛好為27個字符。

    ####9. bash數組(array)的處理方法

    接下來,為大家介紹一下bash的數組(array)的處理方法。 一般而言, A="a b c def" 這樣的變量只是將$A替換為一個字符串, 但是改為 A=(a b c def), 則是將$A定義為數組....

    #####1). 數組替換方法可參考如下方法:

    ${A[@]} #方法一 ${A[*]} #方法二

    以上兩種方法均可以得到:a b c def, 即數組的全部元素。

    #####2). 訪問數組的成員:

    ${A[0]}

    其中,${A[0]}可得到a, 即數組A的第一個元素, 而 ${A[1]}則為數組A的第二元素,依次類推。

    #####3). 數組的length:

    ${#A[@]} #方法一 ${#A[*]} #方法二 ``` 以上兩種方法均可以得到數組的長度: 4, 即數組的所有元素的個數。回憶一下,針對字符串的長度計算,使用`${#str_var}`; 我們同樣可以將該方法應用于數組的成員: ```shell ${#A[0]}

    其中,${#A[0]}可以得到:1,即數組A的第一個元素(a)的長度; 同理,${#A[3]}可以得到: 3, 即數組A的第4個元素(def)的長度。

    #####4). 數組元素的重新賦值:

    A[3]=xyz

    將數組A的第四個元素重新定義為xyz。

    Tips:

    諸如此類的...

    能夠善用bash的$()與${}可以大大提高及 簡化shell在變量上的處理能力哦~~~^_^

    ####10. $(())作用:

    好了,最后為大家介紹$(())的用途吧: $(())是用來作整數運算的

    在bash中, $(())的整數運算符號大致有這些:

    • +- * / #分別為"加、減、乘、除"。
    • % #余數運算,(模數運算)
    • & | ^ ! #分別為"AND、OR、XOR、NOT"運算。

    例如:

    $ a=5; b=7; c=2; $ echo $(( a + b * c )) 19 $ echo $(( (a + b)/c )) 6 $ echo $(( (a * b) % c )) 1

    在$(())中的變量名稱, 可以在其前面加 $符號來替換, 也可以不用,如: $(( $a + $b * $c )) 也可以得到19的結果。

    此外,$(())還可作不同進制(如二進制、八進制、十六進制)的運算, 只是輸出結果均為十進制的。

    echo $(( 16#2a )) #輸出結果為:42,(16進制的2a)

    以一個實用的例子來看看吧 : 假如當前的umask是022,那么新建文件的權限即為:

    $ umask 022 $ echo "obase=8; $(( 8#666 & (8#777 ^ 8#$(umask)) ))" | bc 644

    事實上,單純用(())也可以重定義變量值,或作testing:

    a=5; ((a++)) #可將$a 重定義為6 a=5; ((a--)) #可將$a 重定義為4 a=5; b=7; ((a< b)) #會得到0 (true)返回值。

    常見的用于(())的測試符號有如下這些:

    符號符號名稱
    <小于號
    >大于號
    <=小于或等于
    >=大于或等于
    ==等于
    !=不等于

    Note:

    使用(())作整數測試時, 請不要跟[]的整數測試搞混亂了。

    更多的測試,我們將于第10章為大家介紹。

    怎樣? 好玩吧... ^_^

    okay,這次暫時說這么多...

    上面的介紹,并沒有詳列每一種可用的狀態, 更多的,就請讀者參考手冊文件(man)吧...

    ##shell十三問之9:$@與$*差在哪?

    要說$@與$*之前, 需得先從shell script的positional parameter談起...

    我們都已經知道變量(variable)是如何定義和替換的, 這個不再多講了。

    1. shell script的positional parameter


    但是,我們還需要知道有些變量是shell內定的, 且其名稱是我們不能隨意修改的。 其中,就有positional parameter在內。

    在shell script中,我們可用$0, $1, $2, $3 ... 這樣的變量分別提取命令行中的如下部分:

    script_name parameter1 parameter2 parameter3 ...

    我們很容易就能猜出, $0就是代表 shell script名稱(路徑)本身, 而$1就是其后的第一個參數,如此類推...

    須得留意的是IFS的作用, 也就是IFS被quoting處理后, 那么positional parameter也會改變。

    如下例:

    my.sh p1 "p2 p3" p4

    由于p2與p3之間的空白鍵被soft quoting所關閉了, 因此,my.sh的中$2是"p2 p3",而$3則是p4...

    還記得前兩章,我們提到function時, 我們不是說過,它是script中的script嗎?^_^

    是的,function一樣可以讀取自己的(有別于script的) positional parameter, 唯一例外的是$0而已。

    舉例而言: 假設my.sh里有一個函數(function)叫my_fun, 若在script中跑my_fun fp1 fp2 fp3, 那么,function內的$0就是my.sh,而$1是fp1而不是p1了...

    不如寫個簡單的my.sh script 看看吧:

    #!/bin/bashmy_fun() {echo '$0 inside function is '$0echo '$1 inside function is '$1echo '$2 inside function is '$2 }echo '$0 outside function is '$0 echo '$1 outside function is '$1 echo '$2 outside function is '$2my_fun fp1 "fp2 fp3"

    然后在command line中跑一下 script就知道了:

    chmod 755 my.sh./my.sh p1 "p2 p3" $0 outside function is ./my.sh $1 outside function is p1 $2 outside function is p2 p3 $0 inside function is ./my.sh $1 inside function is fp1 $2 inside function is fp2 fp3

    然而,在使用positional parameter的時候, 我們要注意一些陷阱哦:

    $10不是替換第10個參數, 而是替換第一個參數,然后在補一個0于其后;

    也就是說, my.sh one two three four five six seven eight nine ten 這樣的command line, my.sh里的$10不是ten而是one0 哦...小心小心 要抓到ten的話,有兩種方法:

    • 方法一:使用我們上一章介紹的${}, 也就是用${10}即可。

    • 方法二:就是shift了。

    用通俗的說法來說, 所謂的shift就是取消positional parameter中最左邊的參數($0不受影響)。 其預設值為1,也就是shift 或shift 1 都是取消$1, 而原本的$2則變成$1, $3則變成$2... 那親愛的讀者,你說要shift掉多少個參數, 才可用$1取得到${10} 呢? ^_^

    okay,當我們對positional parameter有了基本的概念之后, 那再讓我們看看其他相關變量吧。

    2. shell script的positional parameter的number


    先是$#, 它可抓出positional parameter的數量。 以前面的my.sh p1 "p2 p3"為例: 由于"p2 p3"之間的IFS是在soft quote中, 因此,$#就可得到的值是2. 但如果p2與p3沒有置于quoting中話, 那$#就可得到3的值了。 同樣的規則,在function中也是一樣。

    因此,我們常在shell script里用如下方法, 測試script是否有讀進參數:

    [ $# = 0 ]

    假如為0, 那就表示script沒有參數,否則就是帶有參數...

    3. shell script中的$@與$*


    接下來就是**$@與$*: 精確來講,兩者只有在soft quote中才有差異, 否則,都表示“全部參數” ($0除外)**。

    若在comamnd line上, 跑my.sh p1 "p2 p3" p4的話, 不管$@還是$*, 都可得到 p1 p2 p3 p4就是了。

    但是,如果置于soft quote中的話:

    • "$@"則可得到 "p1" "p2 p3" "p4" 這三個不同字段(word);
    • "$*"則可得到 "p1 p2 p3 p4" 這一整個單一的字段。

    我們修改一下前面的my.sh,使之內容如下:

    #!/bin/bashmy_fun() {echo "$#" }echo 'the number of parameter in "$@" is ' $(my_fun "$@") echo 'the number of parameter in "$*" is ' $(my_fun "$*")

    然后再執行:

    ./my.sh p1 "p2 p3" p4

    就知道,$@與$*差在哪了... ^_^

    ##shell十三問之10:&& 與 || 差在哪?

    好不容易,進入了兩位數的章節了... 一路走來,很辛苦吧?也很快樂吧? ^_^

    在解答本章題目之前,先讓我們了解一個概念: return value。

    我們在shell下跑的每一個command或function, 在結束的時候都會傳回父進程一個值,稱為 return value。

    在shell command line中可用$?, 這個變量得到最"新"的一個return value, 也就是剛剛結束的那個進程傳回的值。

    Return Value(RV)的取值為0-255之間, 由進程或者script的作者自行定義:

    • 若在script里,用exit RV 來指定其值; 若沒有指定, 在結束時,以最后一個命令的RV,為script的RV值。

    • 若在function里,則用return RV 來代替exit RV即可。

    Return Value的作用:用來判斷進程的退出狀態(exit status). 進程的退出狀態有兩種:

    • 0值為"真"(true)
    • 非0值為"假"(false)

    舉個例子來說明好了: 假設當前目錄內有一個my.file的文件, 而no.file是不存在的:

    $ touch my.file $ ls my.file $ echo $? #first echo 0 $ ls no.file ls: no.file: No such file or directory $ echo $? #second echo 1 $ echo $? #third echo 0

    上例的:

    • 第一個echo是關于ls my.file的RV,可得到0的值,因此為true。
    • 第二個echo是關于ls no.file的RV,得到非0的值,因此為false。
    • 第三個echo是關于echo $?的RV,得到0值, 因此為true。

    請記住: 每一個command在結束時,都會返回return value,不管你跑什么命令... 然而,有一個命令卻是“專門”用來測試某一條而返回return value, 以供true或false的判斷, 它就是test命令。

    若你用的是bash, 請在command line下, 打man test,或者 man bash 來了解這個test的用法。 這是你可用作參考的最精準的文件了,要是聽別人說的,僅作參考就好...

    下面,我只簡單作一些輔助說明,其余的一律以 man為準: 首先,test的表達式,我們稱為expression,其命令格式有兩種:

    test expression

    或者

    [ expression ]

    Note:

    請務必注意 [] 之間的空白鍵!

    用哪一種格式無所謂,都是一樣的效果。 (我個人比較喜歡后者...)

    其次,bash的test目前支持的測試對象只有三種:

    • string:字符串,也就是純文字。
    • integer:整數(0或正整數、不含負數或小數)
    • file: 文件

    請初學者,一定要搞清楚這三者的差異, 因為test所使用的expression是不一樣的。

    以A=123這個變量為例:

    • [ "$A" = 123 ] #是字符串測試,測試$A是不是1、2、3這三個字符。

    • [ "$A" -eq 123 ] #是整數測試,以測試$A是否等于123.

    • [-e "$A" ] #文件測試,測試123這份文件是否存在.

    第三, 當expression測試為“真”時, test就返回0(true)的return value; 否則,返回非0(false).

    若在 expression 之前加一個!(感嘆號),則在expression為假時,return value為0, 否則, return value 為非0值。

    同時,test也允許多重復合測試:

    • expression1 -a expression2 #當兩個expression都為true,返回0,否則,返回非0;
    • expression1 -o expression2 #當兩個expression均為false時,返回非0,否則,返回0;

    例如:

    [ -d "$file" -a -x "$file" ]

    表示當$file是一個目錄,且同時具有x權限時,test才會為true。

    第四,在command line中使用test時,請別忘記命令行的“重組”特性, 也就是在碰到meta時,會先處理meta,在重新組建命令行。 (這個概念在第2章和第4章進行了反復強調)

    比方說, 若test碰到變量或者命令替換時, 若不能滿足 expression的格式時,將會得到語法錯誤的結果。

    舉例來說好了:

    關于[ string1 = string2 ]這個test格式, 在等號兩邊必須要有字符串,其中包括空串(null串,可用soft quote或者hard quote取得)。

    假如$A目前沒有定義,或被定義為空字符串的話, 那如下的用法將會失敗:

    $ unset A $ [ $A = abc ] [: =: unary oprator expected

    這是因為命令行碰到$這個meta時,會替換$A的值, 然后,再重組命令行,那就變成了: [ = abc ], 如此一來,=的左邊就沒有字符串存在了, 因此,造成test的語法錯誤。 但是,下面這個寫法則是成立的。

    $ [ "$A" = abc ] $ echo $? 1

    這是因為命令行重組后的結果為: [ "" = abc ], 由于等號的左邊我們用soft quote得到一個空串, 而讓test的語法得以通過...

    讀者諸君,請務必留意這些細節哦, 因為稍一不慎,將會導致test的結果變了個樣。 若您對test還不是很有經驗的話, 那在使用test時,不妨先采用如下這一個"法則":

    ** 若在test中碰到變量替換,用soft quote是最保險的***。

    若你對quoting不熟的話,請重新溫習第四章的內容吧...^_^

    okay, 關于更多的test的用法,老話一句:請看其man page (man test)吧!^_^

    雖然洋洋灑灑讀了一大堆,或許你還在嘀咕...那...那個return value有啥用?

    問得好: 告訴你:return value的作用可大了, 若你想要你的shell變"聰明"的話,就全靠它了: 有了return value, 我們可以讓shell根據不同的狀態做不同的事情...

    這時候,才讓我來揭曉本章的答案吧~~~~^_^

    && 與 || 都是用來"組建" 多個command line用的;

    • command1 && command2 # command2只有在command1的RV為0(true)的條件下執行。
    • command1 || command2 # command2 只有在command1的RV為非0(false)的條件下執行。

    以例子來說好了:

    $ A=123 $ [ -n "$A" ] && echo "yes! it's true." yes! it's true. $ unset A $ [ -n "$A" ] && echo "yes! it's true." $ [ -n "$A" ] || echo "no, it's Not true." no, it's Not true

    Note:

    [ -n string ]是測試string長度大于0, 則為true。

    上例中,第一個&&命令之所以會執行其右邊的echo命令, 是因為上一個test返回了0的RV值; 但第二個,就不會執行,因為test返回了非0的結果... 同理,||右邊的echo會被執行,卻正是因為左邊的test返回非0所引起的。

    事實上,我們在同一個命令行中,可用多個&& 或 || 來組建呢。

    $ A=123 $ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture." yes! it's true. $ unset A $ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture." no, it's Not true

    怎樣,從這一刻開始,你是否覺得我們的shell是“很聰明”的呢? ^_^

    好了,最后布置一道練習題給大家做做看: 下面的判斷是:當$A被賦值時,在看看其是否小于100,否則輸出too big!

    $ A=123 $ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!' $ too big!

    若我取消A,照理說,應該不會輸出文字啊,(因為第一個條件不成立)。

    $ unset A $ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!' $ too big!

    為何上面的結果也可得到呢? 又如何解決呢?

    Tips:

    修改的方法有很多種, 其中一種方法可以利用第7章中介紹過 command group...

    快告訴我答案,其余免談....

    解決方法1:sub-shell:

    $ unset A $ [ -n "$A" ] && ( [ "$A" -lt 100 ] || echo 'too big!' )

    解決方法二:command group:

    $ unset A $ [ -n "$A" ] && { [ "$A" -lt 100 ] || echo 'too big!'}

    ##shell十三問之11:>與< 差在哪?

    這次的題目,之前我在CU的shell版說明過了: (原帖的連接在論壇改版后,已經失效) 這次我就不重寫了,將帖子的內容“抄”下來就是了...

    1. 文件描述符(fd, File Descriptor)


    談到I/O redirection,不妨先讓我們認識一下File Descriptor(fd,文件描述符)。

    進程的運算,在大部分情況下,都是進行數據(data)的處理, 這些數據從哪里,讀進來?又輸出到哪里呢? 這就是file descriptor(fd)的功用了。

    在shell的進程中,最常使用的fd大概有三個,分別為:

    • 0:standard Input (STDIN)
    • 1: standard output(STDOUT)
    • 2: standard Error output (STDERR)

    在標準情況下,這些fd分別跟如下設備(device)關聯:

    • stdin(0): keyboard
    • stdout(1): monitor
    • stderr(2): monitor

    Tips: linux中的文件描述符(fd)用整數表示。 linux中任何一個進程都默認打開三個文件, 這三個文件對應的文件描述符分別是:0, 1, 2; 即stdin, stdout, stderr.

    我們可以用如下命令測試一下:

    $ mail -s test root this is a test mail。 please skip. ^d (同時按下ctrl 跟d鍵)

    很明顯,mail進程所讀進的數據,就是從 stdin 也就是keyboard讀進的。 不過,不見得每個進程的stdin都跟mail一樣 從keyboard讀進,因為進程的作者可以從文件參數讀進stdin, 如:

    $ cat /etc/passwd

    但,要是cat之后沒有文件參數則如何呢? 哦, 請你自己玩玩看...^_^

    $ cat

    Tips:

    請留意數據輸出到哪里去了, 最后別忘了按ctrl+d(^d), 退出stdin輸入。

    至于stdout與stderr,嗯...等我有空再續吧...^_^ 還是,有哪位前輩來玩接龍呢?

    相信,經過上一個練習后, 你對stdin與stdout應該不難理解了吧? 然后,讓我們看看stderr好了。

    事實上,stderr沒什么難理解的: 說白了就是“錯誤信息”要往哪里輸出而已... 比方說, 若讀進的文件參數不存在的, 那我們在monitor上就看到了:

    $ ls no.such.file ls: no.such.file: No such file or directory

    若同一個命令,同時成生stdout與stderr呢? 那還不簡單,都送到monitor來就好了:

    $ touch my.file $ ls my.file on.such.file ls: no.such.file: No such file or directory my.file

    okay, 至此,關于fd及其名稱、還有相關聯的設備, 相信你已經沒問題了吧?


    2. I/O 重定向(I/O Redirection)


    那好,接下來讓我們看看如何改變這些fd的預設數據通道。

    • 用< 來改變讀進的數據通道(stdin),使之從指定的文件讀進。
    • 用> 來改變輸出的數據通道(stdout,stderr),使之輸出到指定的文件。

    #####2.1 輸入重定向n<(input redirection)

    比方說:

    $ cat < my.file

    就是從my.file讀入數據

    $ mail -s test root < /etc/passwd

    則是從/etc/passwd讀入...

    這樣一來,stdin將不再是從keyboard讀入, 而是從指定的文件讀入了...

    嚴格來說,<符號之前需要指定一個fd的(之前不能有空白),但因為0是<的預設值,因此,<與0<是一樣的*。

    okay,這樣好理解了吧?

    那要是用兩個<,即<<又是啥呢? 這是所謂的here document, 它可以讓我們輸入一段文本, 直到讀到<< 后指定的字符串

    比方說:

    $ cat <<EOF first line here second line here third line here EOF

    這樣的話, cat會讀入3個句子, 而無需從keyboard讀進數據且要等到(ctrl+d, ^d)結束輸入。


    #####2.2 重定向輸出>n(output redirection)

    當你搞懂了0< 原來就是改變stdin的數據輸入通道之后, 相信要理解如下兩個redirection就不難了:

    • 1> #改變stdout的輸出通道;
    • 2> #改變stderr的輸出通道;

    兩者都是將原來輸出到monitor的數據, 重定向輸出到指定的文件了。

    由于1是>的預設值, 因此,1>與>是相同的,都是改變stdout.

    用上次的ls的例子說明一下好了:

    $ ls my.file no.such.file 1>file.out ls: no.such.file: No such file or directory

    這樣monitor的輸出就只剩下stderr的輸出了, 因為stdout重定向輸出到文件file.out去了。

    $ ls my.file no.such.file 2>file.err my.file

    這樣monitor就只剩下了stdout, 因為stderr重定向輸出到文件file.err了。

    $ ls my.file no.such.file 1>file.out 2>file.err

    這樣monitor就啥也沒有了, 因為stdout與stderr都重定向輸出到文件了。

    呵呵,看來要理解>一點也不難啦是不? 沒騙你吧? ^_^ 不過有些地方還是要注意一下的

    $ ls my.file no.such.file 1>file.both 2>file.both

    假如stdout(1)與stderr(2)都同時在寫入file.both的話, 則是采取"覆蓋"的方式:后來寫入覆蓋前面的。

    讓我們假設一個stdout與stderr同時寫入到file.out的情形好了;

    • 首先stdout寫入10個字符
    • 然后stderr寫入6個字符

    那么,這時原本的stdout輸出的10個字符, 將被stderr輸出的6個字符覆蓋掉了。

    那如何解決呢?所謂山不轉路轉,路不轉人轉嘛, 我們可以換一個思維: 將stderr導進stdout 或者將stdout導進到stderr, 而不是大家在搶同一份文件,不就行了。 bingo就是這樣啦:

    • 2>&1 #將stderr并進stdout輸出
    • 1>&2 或者 >&2 #將stdout并進stderr輸出。

    于是,前面的錯誤操作可以改寫為:

    $ ls my.file no.such.file 1>file.both 2>&1 $ ls my.file no.such.file 2>file.both >&2

    這樣,不就皆大歡喜了嗎? ~~~ ^_^

    不過,光解決了同時寫入的問題還不夠, 我們還有其他技巧需要了解的。 故事還沒有結束,別走開廣告后,我們在回來....


    #####2.3 I/O重定向與linux中的/dev/null

    okay,這次不講I/O Redirection, 請佛吧... (有沒有搞錯?網中人是否頭殼燒壞了?...)嘻~~~^_^

    學佛的最高境界,就是"四大皆空"。 至于是空哪四大塊,我也不知,因為我還沒有到那個境界.. 這個“空”字,卻非常值得反復把玩: ---色即是空,空即是色 好了,施主要是能夠領會"空"的禪意,那離修成正果不遠了。

    在linux的文件系統中,有個設備文件: /dev/null. 許多人都問過我,那是什么玩意兒? 我跟你說好了,那就是"空"啦。

    沒錯空空如也的空就是null了... 請問施主是否忽然有所頓悟了呢? 然則恭喜了。

    這個null在 I/O Redirection中可有用的很呢?

    • 將fd 1跟fd 2重定向到/dev/null去,就可忽略stdout, stderr的輸出。
    • 將fd 0重定向到/dev/null,那就是讀進空(nothing).

    比方說,我們在執行一個進程時,會同時輸出到stdout與stderr, 假如你不想看到stderr(也不想存到文件), 那就可以:

    $ ls my.file no.such.file 2>/dev/null my.file

    若要相反:只想看到stderr呢? 還不簡單將stdout,重定向的/dev/null就行:

    $ ls my.file no.such.file >/dev/null ls: no.such.file: No such file or directory

    那接下來,假如單純的只跑進程,而不想看到任何輸出呢? 哦,這里留了一手,上次沒講的法子,專門贈與有緣人... ^_^ 除了用 >/dev/null 2>&1之外,你還可以如此:

    $ ls my.file no.such.file &>/dev/null

    Tips:

    將&>換成>&也行!


    #####2.4 重定向輸出append (>>)

    okay? 請完佛,接下來,再讓我們看看如下情況:

    $ echo "1" > file.out $ cat file.out 1 $ echo "2" > file.out $ cat file.out 2

    看來,我們在重定向stdout或stderr進一個文件時, 似乎永遠只能獲得最后一次的重定向的結果. 那之前的內容呢?

    呵呵,要解決這個問題,很簡單啦,將>換成>> 就好了;

    $ echo "3" >> file.out $ cat file.out 2 3

    如此一來,被重定向的文件的之前的內容并不會丟失, 而新的內容則一直追加在最后面去。so easy?...

    但是,只要你再次使用>來重定向輸出的話, 那么,原來文件的內容被truncated(清洗掉)。 這是,你要如何避免呢? ----備份, yes,我聽到了,不過,還有更好的嗎? 既然與施主這么有緣分,老衲就送你一個錦囊妙法吧:

    $ set -o noclobber $ echo "4" > file.out -bash:file: cannot overwrite existing file.

    那,要如何取消這個限制呢? 哦,將set -o換成 set +o就行了:

    $ set +o noclobber $ echo "5" > file.out $ cat file.out 5

    再問:那有辦法不取消而又“臨時”改寫目標文件嗎? 哦,佛曰:不可告也。 啊,~開玩笑的,開玩笑啦~^_^, 哎,早就料到人心是不足的了

    $ set -o noclobber $ echo "6" >| file.out $ cat file.out 6

    留意到沒有: 在>后面加個|就好, 注意: >與|之間不能有空白哦...


    #####2.5 I/O Redirection的優先級

    呼....(深呼吸吐納一下吧)~~~ ^_^ 再來還有一個難題要你去參透呢:

    $ echo "some text here" >file $ cat < file some text here $cat < file >file.bak $cat < file.bak some text here $cat < file >file

    嗯?注意到沒有? ---怎么最后那個cat命令看到file是空的呢? why? why? why?

    前面提到:$cat < file > file之后, 原本有內容的文件,結果卻被清空了。 要理解這個現象其實不難, 這只是priority的問題而已: ** 在IO Redirection中, stdout與stderr的管道先準備好, 才會從stdin讀入數據。** 也就是說,在上例中,>file會將file清空, 然后才讀入 < file。 但這時候文件的內容已被清空了,因此就變成了讀不進任何數據。

    哦,~原來如此~^_^ 那...如下兩例又如何呢?

    $ cat <> file $ cat < file >>file

    嗯...同學們,這兩個答案就當練習題嘍, 下課前交作業。

    Tips: 我們了解到>file能夠快速把文件file清空; 或者使用:>file同樣可以清空文件, :>file與>file的功能: 若文件file存在,則將file清空; 否則,創建空文件file (等效于touch file); 二者的差別在于>file的方式不一定在所有的shell的都可用。

    exec 5<>file; echo "abcd" >&5; cat <&5 將file文件的輸入、輸出定向到文件描述符5, 從而描述符5可以接管file的輸入輸出; 因此,cat <>file等價于cat < file。

    而cat < file >>file則使file內容成幾何級數增長。

    好了, I/O Redirection也快講完了, sorry,因為我也只知道這么多而已啦~嘻~^_^ 不過,還有一樣東東是一定要講的,各位觀眾(請自行配樂~!#@$%): 就是pipe line也。


    #####2.6 管道(pipe line)

    談到pipe line,我相信不少人都不會陌生: 我們在很多command line上常看到|符號就是pipe line了。

    不過,pipe line究竟是什么東東呢? 別急別急...先查一下英文字典,看看pipe是什么意思? 沒錯他就是“水管”的意思... 那么,你能想象一下水管是怎樣一個根接一根的嗎? 又, 每根水管之間的input跟output又如何呢? 靈光一閃:原來pipe line的I/O跟水管的I/O是一模一樣的: 上一個命令的stdout接到下一個命令的stdin去了 的確如此。不管在command line上使用了多少個pipe line, 前后兩個command的I/O是彼此連接的 (恭喜:你終于開放了 ^_^ )

    不過...然而...但是... ...stderr呢? 好問題不過也容易理解: 若水管漏水怎么辦? 也就是說:在pipe line之間, 前一個命令的stderr是不會接進下一個命令的stdin的, 其輸出,若不用2>file的話,其輸出在monitor上來。 這點請你在pipe line運用上務必要注意的。

    那,或許你有會問: 有辦法將stderr也喂進下一個命令的stdin嗎? (貪得無厭的家伙),方法當然是有的,而且,你早已學習過了。 提示一下就好:**請問你如何將stderr合并進stdout一同輸出呢? 若你答不出來,下課后再來問我...(如果你臉皮足夠厚的話...)

    或許,你仍意猶未盡,或許,你曾經碰到過下面的問題: 在cmd1 | cmd2 | cmd3 | ... 這段pipe line中如何將cmd2的輸出保存到一個文件呢?

    若你寫成cmd1 | cmd2 >file | cmd3的話, 那你肯定會發現cmd3的stdin是空的,(當然了,你都將 水管接到別的水池了) 聰明的你或許會如此解決:

    cmd1 | cmd2 >file; cmd3 < file

    是的,你可以這樣做,但最大的壞處是: file I/O會變雙倍,在command執行的整個過程中, file I/O是最常見的最大效能殺手。 凡是有經驗的shell操作者,都會盡量避免或降低file I/O的頻度。

    那上面問題還有更好的方法嗎? 有的,那就是tee命令了。 所謂的tee命令是在不影響原本I/O的情況下, 將stdout賦值到一個文件中去。 因此,上面的命令行,可以如此執行:

    cmd1 | cmd2 | tee file | cmd3

    在預設上,tee會改寫目標文件, 若你要改為追加內容的話,那可用-a參數選項。

    基本上,pipe line的應用在shell操作上是非常廣泛的。 尤其是在text filtering方面, 如,cat, more, head, tail, wc, expand, tr, grep, sed, awk...等等文字處理工具。 搭配起pipe line 來使用,你會覺得 command line 原來活得如此精彩的。 常讓人有“眾里尋他千百度,驀然回首,那人卻在燈火闌珊處”之感...

    好了,關于I/O Redirection的介紹就到此告一段落。 若日后,有空的話,在為大家介紹其他在shell上好玩的東西。

    ##shell十三問之12:你要if還是case呢?

    還記得我們在第10章所介紹的return value嗎?

    是的,接下來的介紹的內容與之有關, 若你的記憶也被假期所抵消的話, 那建議您還是回去溫習溫習再回來...

    若你記得return value,我想你也應該記得了 && 與 || 什么意思吧? 用這兩個符號再搭配 command group的話, 我們可讓shell script變得更加聰明哦。 比方說:

    cmd1 && {cmd2cmd3; } || {cmd4cmd5 }

    意思是說: 若 cmd1的return value為true的話, 然后執行cmd2與cmd3, 否則執行cmd4與cmd5.

    事實上, 我們在寫shell script的時候, 經常需要用到這樣、那樣的條件 以作出不同的處理動作。 用&&與||的確可以達成條件執行的結果, 然而,從“人類語言”上來理解, 卻不是那么直觀。 更多時候,我們還是喜歡用if...then...else... 這樣的的keyword來表達條件執行。

    在bash shell中,我們可以如此修改上一段代碼:

    if cmd1 thencmd2cmd3 elsecmd4cmd5 fi

    這也是我們在shell script中最常用的if判斷式: 只要if后面的command line返回true的return value (我們常用test命令返回的return value), 然則就執行then后面的命令,否則,執行else之后的命令, fi則是用來結束判斷式的keyword。

    在if的判斷式中,else部分可以不用,但then是必需的。 (若then后不想跑任何command,可用:這個null command代替)。 當然,then或else后面,也可以再使用更進一層的條件判斷式, 這在shell script的設計上很常見。 若有多項條件需要"依序"進行判斷的話, 那我們則可使用elif這樣的keyword:

    if cmd1; thencmd2; elif cmd3; thencmd4 elsecmd5 fi

    意思是說: 若cmd1為true,然則執行cmd2; 否則在測試cmd3,若為true則執行cmd4; 倘若cmd1與cmd3均不成立,那就執行cmd5。

    if判斷式的例子很常見,你可從很多shell script中 看得到,我這里不再舉例子了...

    接下來為要為大家介紹的是case判斷式。 雖然if判斷式已可應付大部分的條件執行了, 然而,在某些場合中,卻不夠靈活, 尤其是在string式樣的判斷上,比方如下:

    QQ() {echo -n "Do you want to continue? (Yes/No): "read YNif [ "$YN" = Y -o "$YN" = y -o "$YN" = "Yes" -o "$YN" = "yes" -o "$YN" = YES]thenQQelseexit 0fi }QQ

    從例中,我們看得出來, 最麻煩的部分是在判斷YN的值可能有好幾種樣式。

    聰明的你或許會如此修改:

    QQ() {echo -n "Do you want to continue? (Yes/No): "read YNif echo "$YN" | grep -q '^[Yy]\([Ee][Ss]\)*$'thenQQelseexit 0fi }QQ

    也就是用Regular Expression來簡化代碼。 (我們有機會,再來介紹RE) 只是...是否有其他更方便的方法呢? 有的,就是用case判斷式即可:

    QQ() {echo -n "Do you want to continue? (Yes/No): "read YNcase "$YN" in[Yy]|[Yy][Ee][Ss])QQ;;*)exit 0;;esac }QQ

    我們常用的case的判斷式來判斷某一變量 在不同的值(通常是string)時,作出不同的處理, 比方說, 判斷script參數,以執行不同的命令

    若你有興趣,且用linux系統的話, 不妨挖一挖/etc/init.d/*中的那堆script中的case用法. 如下就是一例:

    case "$1" instart)start;;stop)stop;;status)rhstatus;;restart|reload)restart;;condrestart)[ -f /var/lock/subsys/syslog ] && restart || :;;*)echo $"Usage: $0 {start|stop|status|restart|condrestart}"exit 1 esac

    (若你對 postional parameter的印象已經模糊了,請重看第9章吧。)

    okay,是十三問還剩一問而已,過幾天再來搞定之...^_^

    ##shell十三問之13: for what? while與until差在哪?

    終于,來到了shell十三問的最后一問了... 長長吐一口氣~~~~

    最后要介紹的是shell script設計中常見的循環(loop). 所謂的loop就是script中的一段在一定條件下反復執行的代碼。

    bash shell中常用的loop有如下三種:

    • for
    • while
    • until

    ###1. for loop

    for loop 是從一個清單列表中讀進變量的值, 并依次的循環執行do到done之間的命令行。 例:

    for var in one two three four five doecho -----------------echo '$var is '$varecho done

    上例的執行結果將會是:

  • for會定義一個叫var的變量,其值依次是one two three four five。
  • 因為有5個變量值,因此,do與done之間的命令行會被循環執行5次。
  • 每次循環均用echo產生3個句子。而第二行中不在hard quote之內的$var會被替換。
  • 當最后一個變量值處理完畢,循環結束。
  • 我們不難看出,在for loop中,變量值的多寡,決定循環的次數。 然而,變量在循環中是否使用則不一定,得視設計需求而定。 倘若for loop沒有使用in這個keyword來制變量清單的話,其值將從 $@(或$*)中繼承:

    for var; do...... done

    Tips:

    若你忘記了`positional parameter, 請溫習第9章...

    for loop用于處理“清單”(list)項目非常方便, 其清單除了明確指定或從postional parameter取得之外, 也可以從變量替換或者命令替換取得... (再一次提醒:別忘了命令行的“重組”特性) 然而,對于一些“累計變化”的項目(整數的加減),for也能處理:

    for ((i = 1; i <= 10; i++)) doecho "num is $i" done

    ###2. while loop

    除了for loop, 上面的例子, 我們也可改用while loop來做到:

    num=1 while [ "$num" -le 10 ]; doecho "num is $num"num=$(($num + 1)) done

    while loop的原理與for loop稍有不同: 它不是逐次處理清單中的變量值, 而是取決于while 后面的命令行的return value:

    • 若為true, 則執行do與done之間的命令, 然后重新判斷while后的return value。
    • 若為false,則不再執行do與done之間的命令而結束循環。

    分析上例:

  • 在while之前,定義變量num=1.
  • 然后測試(test)$num是否小于或等于10.
  • 結果為true,于是執行echo并將num的值加1.
  • 再作第二輪測試,此時num的值為1+1=2,依然小于或等于10,因此,為true,循環繼續。
  • 直到num為10+1=11時,測試才會失敗...于是結束循環。
  • 我們不難發現: 若while的測試結果永遠為true的話,那循環將一直永久執行下去

    while:; doecho looping... done

    上面的**:是bash的null command,不做任何動作, 除了返回true的return value**。 因此這個循環不會結束,稱作死循環。

    死循環的產生有可能是故意設計的(如跑daemon), 也可能是設計的錯誤。

    若要結束死循環,可通過signal來終止(如按下ctrl-c). (關于process與signal,等日后有機會再補充,十三問略過。)

    ####3.until loop

    一旦你能夠理解while loop的話,那就能理解until loop: **與while相反, until是在return value 為false時進入循環,否則,結束。 因此,前面的例子,我們也可以輕松的用until來寫:

    num=1 until [ ! "$num" -le 10 ]; doecho "num is $num"num=$(($num + 1)) done

    或者:

    num=1until [ "$num" -gt 10 ]; doecho "num is $num"num=$(($num + 1)) done

    okay, 關于bash的三個常用的loop暫時介紹到這里。

    ###4. shell loop中的break與continue

    在結束本章之前,再跟大家補充兩個loop有關的命令:

    • break
    • continue 這兩個命令常用在復合式循環里, 也就是do ... done之間又有更進一層的loop, 當然,用在單一循環中也未嘗不可啦... ^_^

    break用來中斷循環,也就是強迫結束循環。 若break后面指定一個數值n的話,則從里向外中斷第n個循環, 預設值為 break 1,也就是中斷當前循環。 在使用break時,需要注意的是,它與return及exit是不同的:

    • break是結束loop;
    • return是結束function;
    • exit是結束script/shell;

    而continue則與break相反:強迫進入下一次循環動作.

    若你理解不來的話,那你可簡單的看成: 在continue在done之間的句子略過而返回到循環的頂端...

    與break相同的是:continue后面也可以指定一個數值n, 以決定繼續哪一層(從里往外計算)的循環, 預設值為 continue 1,也就是繼續當前的循環。

    在shell script設計中,若能善用loop, 將能大幅度提高script在復雜條件下的處理能力。 請多加練習吧...


    shell是十三問的總結語


    好了,該是到了結束的時候了。 婆婆媽媽地跟大家啰嗦了一堆shell的基礎概念。

    目的不是要告訴大家“答案”,而是要帶給大家“啟發”...

    在日后的關于shell的討論中,我或許經常用"連接"的方式 指引十三問中的內容。

    以便我們在進行技術探討時,彼此能有一些討論的基礎, 而不至于各說各話、徒費時力。

    但更希望十三問能帶給你更多的思考與樂趣, 至為重要的是通過實踐來加深理解。

    是的,我很重視實踐獨立思考這兩項學習要素。

    若你能夠掌握其中的真諦,那請容我說聲: 恭喜十三問你沒白看了 ^_^

    p.s. 至于補充問題部分,我暫時不寫了。 而是希望:

  • 大家補充題目。
  • 一起來寫心得。
  • Good luck and happy studing!


    ##shell十三問原作者**網中人**簽名中的bash的fork bomb

    最后,Markdown整理者補上本書的原作者網中人的個性簽名:

    ** 君子博學而日叁省乎己,則知明而行無過矣。**

    一個能讓系統shell崩潰的shell 片段:

    :() { :|:& }; : # <--- 這個別亂跑!好奇會死人的! echo '十人|日一|十十o' | sed 's/.../&\n/g' # <--- 跟你講就不聽,再跑這個就好了...

    原來是一個bash的fork炸彈:ref:http://en.wikipedia.org/wiki/Fork_bomb

    :() {:|:& } :

    代碼分析:

    (即除最后一行外)

    定義了一個 shell 函數,函數名是:,

    而這個函數體執行一個后臺命令:|:

    即冒號命令(或函數,下文會解釋)的輸出 通過管道再傳給冒號命令做輸入

    最后一行執行“:”命令

    在各種shell中運行結果分析:

    這個代碼只有在 bash 中執行才會出現不斷創建進程而耗盡系統資源的嚴重后果;

    在 ksh (Korn shell), sh (Bourne shell)中并不會出現,

    在 ksh88 和傳統 unix Bourne shell 中冒號不能做函數名,

    即便是在 unix-center freebsd 系統中的 sh 和 pdksh(ksh93 手邊沒有,沒試)中冒號可以做函數名,但還是不會出現那個效果。

    原因是 sh、ksh 中內置命令的優先級高于函數,所以執行“:”, 總是執行內置命令“:”而不是剛才定義的那個恐怖函數。

    但是在 bash 中就不一樣,bash 中函數的優先級高于內置命令, 所以執行“:”結果會導致不斷的遞歸,而其中有管道操作, 這就需要創建兩個子進程來實現,這樣就會不斷的創建進程而導致資源耗盡。

    眾所周知,bash是一款極其強大的shell,提供了強大的交互與編程功能。

    這樣的一款shell中自然不會缺少“函數”這個元素來幫助程序進行模塊化的高效開發與管理。 于是產生了由于其特殊的特性,bash擁有了fork炸彈。

    Jaromil在2002年設計了最為精簡的一個fork炸彈的實現。

    所謂fork炸彈是一種惡意程序,它的內部是一個不斷在fork進程的無限循環.

    fork炸彈并不需要有特別的權限即可對系統造成破壞。

    fork炸彈實質是一個簡單的遞歸程序。

    由于程序是遞歸的,如果沒有任何限制,

    這會導致這個簡單的程序迅速耗盡系統里面的所有資源.

    ##shell十三問之14: [^ ] 跟[! ]差在哪? (wildcard)

    這個題目說穿了, 就是要探討Wildcard與Regular Expression的差別的。 這也是很多初學shell的朋友很容易混淆的地方。

    首先,讓我們回到十三問之第2問, 再一次將我們提到的command line format 溫習一次:

    command_name options arguments

    同時,也再來理解一下,我在第5章所提到的變量替換的特性:

    先替換,再重組 command line!

    有了這個兩個基礎后,再讓我們來看Wildcard是什么回事吧。

    Part-I Wildcard (通配符)


    首先,

    `Wildcard` 也是屬于 `command line` 的處理工序,作用于 `arguments` 里的 `path` 之上。

    沒錯,它不用在command_name,也不用在options上。 而且,若argument不是path的話,那也與wildcard無關。

    換句更為精確的定義來講,

    `wildcard`是一種命令行的路徑擴展(path expansion)功能。

    提到這個擴展,那就不要忘了 command line的“重組”特性了!

    是的,這與變量替換(variable subtitution)及 命令替換(command substitution)的重組特性是一樣的。

    也就是在wildcard進行擴展后, 命令行會先完成重組,才會交給shell來處理。

    了解了wildcard的擴展與重組特性后, 接下來,讓我們了解一些常見的wildcard吧。

    wildcard功能
    *匹配0個或多個字符
    ?匹配任意單一字符
    [list]匹配list中任意單一字符
    [!list]匹配不在list中任意單一字符
    {string1,string2,...}匹配string1或者stsring2或者(...)中其一字符串

    Note: list 中可以指定單個字符,如abcd, 也可以指定ASCII字符的起止范圍,如 a-d。 即[abcd] 與 [a-d] 是等價的,稱為一個自定義的字符類。

    例如:

    a*b # a 與 b 之間可以有任意個字符(0個或多個),如aabcb, axyzb, a012b,ab等。 a?b # a 與 b 之間只能有一個字符,但該字符可以任意字符,如 aab, abb, acb, azb等。 a[xyz]b # a 與 b 之間只能有一個字符,但這個字符只能是x或者y或者z,如:axb, ayb, azb這三個。 a[!0-9]b# a 與 b 之間只能有一個字符,但這個字符不能是阿拉伯數字,如aab,ayb,a-b等。 a{abc,xyz,123}b # a 與 b之間只能是abc或者xyz或者123這三個字串之一,擴展后是aabcb,axyzb,a123b。
  • [! ] 中的! 只有放在第一位時,才有取反的功效。 eg: [!a]* 表示當前目錄下不以a開頭的路徑名稱; /tmp/[a\!]*表示/tmp目錄下所有以a 或者 ! 開頭的路徑名稱;

    思考:為何!前面要加\呢?提示是十三問之4.

  • [ - ]中-左右兩邊均有字符時,才表示一個范圍,否則,僅作-(減號)字符來處理。 舉例: /tmp/*[-z]/[a-zA-Z]* 表示/tmp 目錄下所有以z或者-結尾的子目錄中, 以英文字母(不分大小寫)開頭的目錄名稱。

  • 以*或?開頭的wildcard不能匹配隱藏文件(即以.開頭的文件名)。 eg: *.txt并不能匹配.txt但能匹配1.txt這樣的路徑名。 但1*txt及1?txt均可匹配1.txt這樣的路徑名。

  • 基本上,要掌握wildcard并不難, 只要多加練習,再勤于思考,就能靈活運用了。

    再次提醒:

    別忘了wildcard的"擴展" + "重組" 這個重要特性,而且只作用在 argument的path上。

    比方說, 假如當前目錄下有: a.txt b.txt c.txt 1.txt 2.txt 3.txt 這幾個文件。

    當我們在命令行中執行ls -l [0-9].txt的命令行時, 因為wildcard處于argument的位置上,

    于是根據匹配的路徑,擴展為: 1.txt 2.txt 3.txt, 在重組出ls -l 1.txt 2.txt 3.txt 這樣的命令行。

    因此,你在命令行上敲 ls -l [0-9].txt 與 ls -l 1.txt 2.txt 3.txt 輸出的結果是一樣, 原因就是在于此。

    ##shell十三問之15: [^ ] 跟[! ]差在哪? (RE: Regular Expression)

    Part-II Regular Expression (正則表達式)


    接下來的Regular Expression(RE) 可是個大題目,要講的很多。 我這里當然不可能講得很全。 只希望能帶給大家一個基本的入門概念,就很足夠了...

    先來考一下英文好了:What is expression? 簡單來說,就是"表達",也就是人們在溝通的時候所要陳述的內容。

    然而,生活中,表達方要清楚的將意思描述清楚, 而讓接收方完整無誤地領會,可不是件容易的事情。

    因而才會出現那么多的"誤會", 真可嘆句"表達不易"啊......

    同樣的情形也發生在計算機的數據處理過程中, 尤其是當我們在描述一段"文字內容"的時候.... 那么,我們不禁要問: 有何方法可以讓大家的誤會降至最低程度, 而讓表達的精確度達到最高程度呢? 答案就是"標準化"了, 也就是我們這里要談的Regular Expression啦...^_^

    然而,在進入RE介紹之前,不妨先讓我們溫習一下shell十三問之第4問, 那就是關于quoting的部分。

    關鍵是要能夠區分 shell command line上的meta與literal的這兩種不同的字符類型

    然后,我這里也跟你講: RE 表達式里字符也分meta與literal這兩種

    呵,不知親愛的讀者是否被我搞混亂了呢?... ^_^

    這也難怪啦,因為這的確是最容易混淆的地方, 剛學RE的朋友很多時候,都死在這里! 因此,請特別小心理解哦...

    簡單而言,除非你將RE寫在特定程序使用的腳本里, 否則,我們的RE也是通過 command line輸入的。 然而, 不少RE所使用的meta字符,跟shell 的meta字符是沖突的

    比方說, *這個字符,在RE里是一個modifier(修飾符);而在command line上,確是wildcard(通配符)

    那么,我們該如何解決這樣的沖突呢? 關鍵就是看你對shell十三問的第4問中所提的quoting是否足夠理解了!

    若你明白到 shell quoting 就是用來在command line上關閉shell meta這一基本原理, 那你就能很輕松的解決 RE meta與shell meta的沖突問題了: 用shell quoting 關閉掉shell meta就是了。 就這么簡單... ^_^

    再以剛提到*字符為例, 若在command line的path中沒有quoting處理的話, 如abc* 就會被作為wildcard expression來擴充及重組了。 若將其置于quoting中,即"abc*",則可以避免wildcard expand的處理。

    好了,說了大半天,還沒有進入正式的RE介紹呢.... 大家別急,因為我的教學風格就是要先建立基礎,循序漸進的... ^_^ 因此, 我這里還要再啰嗦一個觀念,才會到RE的說明啦...(哈...別打我...)

    當我們在談到RE時,千萬別跟wildcard搞混在一起! 尤其是

    在command line的位置里,wildcard只作用于argument的path上; 而RE卻只用于"字符串處理" 的程序中,這與路徑名一點關系也沒有。

    Tips: RE 所處理的字符串,通常是指純文本或通過stdin讀進的內容。

    okay,夠了夠了,我已看到一堆人開始出現不耐煩的樣子了... ^_^ 現在,就讓我們登堂入室,揭開RE的神秘面紗吧, 這樣可以放過我了吧? 哈哈...

    在RE的表達式里,主要分為兩種字符:literal與meta。 所謂literal就是在RE里不具有特殊功能的字符,如abc,123等; 而meta,在RE里具有特殊的功能。 要關閉之,需要在meta之前使用escape()轉義字符。

    然而,在介紹meta之前,先讓我們來認識一下字符組合(character set)會更好些。

    一、所謂的char set就是將多個連續的字符作為一個集合。 例如:

    char set意義
    abc表示abc三個連續的字符,但彼此獨立而非集合。(可簡單視為三個char set)
    (abc)表示abc這三個連續字符的集合。(可簡單視為一個char set)
    abc|xyz表示abc或xyz這連個char set之一
    [abc]表示單一字符,可為a或b或c;與wildcard的[abc]原理相同,稱之為字符類。
    [^abc]表示單一字符,不為a或b或c即可。(與wildcard [!abc]原理相同)
    .表示任意單個字符,(與wildcard的?原理相同)

    note: abc|xyz 表示abc或xyz這連個char set之一

    在認識了RE的char set這個概念之后,然后,在讓我們多認識幾個RE中常見的meta字符:

    二、 錨點(anchor): 用以標識RE在句子中的位置所在。 常見的有:

    錨點說明
    ^表示句首。如,^abc表示以abc開頭的句子。
    $表示句尾。如,abc$表示以abc結尾的句子。
    <表示詞首。如,<abc表示以abc開頭的詞。
    >表示詞尾。如,abc>表示以abc結尾的詞。

    三、 修飾符(modifier):獨立表示時本身不具意義,專門用以修飾前一個char set出現的次數。 常見的有:

    modifier說明
    *表示前一個char set出現0次或多次,即任意次。如ab*c表示a與c之間可以有0個或多個b。
    ?表示前一個char set出現0次或1次,即至多出現1次。如ab?c 表示a與c之間可以有0個或1個b。
    +表示前一個char set出現1次或多次,即至少出現1次。如ab+c 表示a與c之間可以有1個或多個b。
    {n}表示前一個char set出現n次。如ab{n}c 表示a與c之間可以有n個b。
    {n, }表示前一個char set至少出現n次。如ab{n}c 表示a與c之間至少有n個b。
    {n, m}表示前一個char set至少出現n次,至多出現m次。如ab{n,m}c 表示a與c之間至少有n個b,至多有m個b。

    然而,當我們在識別modifier時,卻很容易忽略"邊界(boundary)字符"的重要性。

    以ab{3,5}c為例,這里的a與c就是邊界字符了。 若沒有邊界字符的幫忙,我們很容易做出錯誤的解讀。 比方說: 我們用ab{3,5}這個RE(少了c這個邊界字符) 可以抓到"abbbbbbbbbb"(a后面有10個b)的字符串嗎? 從剛才的modifier的說明,我們一般認為,我們要的b是3到5個, 若超出了此范圍,就不是我們所要表達的。 因此,我們或許會很輕率地認為這個RE抓不到結果(上述"abbbbbbbbbb"字符串)。

    然而,答案卻是可以的!為什么呢? 讓我們重新解讀ab{3,5}這個RE看看: 我們要表達的是a后接3到5個b即可,但3到5個b后面,我們卻沒有規定什么, 因此,在RE后面可以是任意的字符串,當然包括b也可以啦!(明白了嗎?)

    同樣,我們用b{3,5}c也同樣可以抓到"abbbbbbbbbbc" 這樣的字符串。

    但當我們用ab{3,5}c這樣的RE時, 由于同時有a與c這連個邊界字符,就截然不同了!

    有空在思考一下,為何我們用下面這些RE都抓到abc這樣的字符串呢?

    x* ax*, abx*, ax*b abcx*, abx*c, ax*bc bx*c, bcx*, x*bc

    但, 若我們在這些RE前后分別加^與$這樣的anchor,那又如何呢?

    剛學RE時,只要能掌握上面這些基本的meta的大概就可以入門了。 一如前述,RE是一種規范化的文字表達式, 主要用于某些文字處理工具之間,如: grep, perl, vi,awk,sed,等等, 常用于表示一段連續的字符串,查找和替換。

    然而每種工具對RE表達式的具體解讀或有一些細微差別, 不過節本原理還是一致的。 只要掌握RE的基本原理,那就一理通百理了, 只是在實踐時,稍加變通即可。

    比方以grep來說, 在Linux上,你可以找到grep,egrep,fgrep這些程序, 其差異大致如下:

    grep: 傳統的grep程序,在沒有任何選項(options)的情況下,只輸出符合RE字串的句子, 其常見的選項如下:

    選項 (option)用途
    -v反模式, 只輸出“不含”RE的字符串的行。
    -r 遞歸模式,可同時處理所有層級的子目錄里的文件
    -q靜默模式,不輸出任何結果(stderr 除外,常用于獲取return value,符合為true,否則,為false.
    -i忽略大小寫
    -w整詞匹配,類似 <RE>
    -n同時輸出行號
    -l輸出匹配RE的文件名
    -o只輸出匹配RE的字符串。(gna新版獨有,不見得所有版本支持)
    -E切換為egrep

    egrep:為grep的擴充版本,改良了許多傳統grep不能或者不便的操作,

    • grep下不支持?與+這兩種meta,但egrep支持;
    • grep 不支持a|b或(abc|xyz)這類“或一”的匹配,但egrep支持;
    • grep 在處理{n,m}時,需要\{ 與 \}處理,但egrep不需。

    等諸如此類的。我個人建議能用egrep就不用grep啦...^_^

    fgrep: 不作RE處理,表達式僅作一般的字符串處理,所有的meta均市區功能。

    好了,關于RE的入門,我們暫時就介紹到這里。 雖然有點亂,且有些觀念也不恨精確, 不過,姑且算是對大家的一個交差吧...^_^ 若這兩天有時間的話,我在舉些范例來分析一下,以幫助大家更好的理解。 假如更有可能的話,也順道為大家介紹一下sed這個工具。


    Part-III eval


    講到command line的重組特性, 真的需要我們好好的加以解釋的。

    如此便能抽絲剝繭的一層層的將整個command line分析的 一清二楚,而不至于含糊。

    假如這個重組的特性理解了,那我們介紹一個好玩的命令:eval.

    我們在變量替換的過程中,常會碰到所謂的復式變量的問題: 如:

    a=1 A1=abc

    我們都知道echo $A1就可以得到abc的結果。 然而,我們能否用$A$a來取代$A1,而同一樣替換為abc呢?

    這個問題我們可用很輕松的用eval來解決:

    eval echo \$A$a

    說穿了,eval 只不過是在命令行完成替換重組后, 在來一次替換重組罷了... 就是這么簡單啦~~~ ^_^

    ##shell十三問之16:學習總結與原帖目錄

    本人(markdown譯者)是解決工作中shell腳本的一個問題, 偶爾的一次機會遇到了CU論壇中這樣一個神貼:shell十三問.

    shell十三問是CU的shell版的臺灣的網中人是2003年用繁體發布的。 第一次讀到shell十三問,由于是繁體,第一感覺有點抵觸, 但是還是耐著性子讀完了一貼,沒想到竟然讀懂了, 而且還被網中人的幽默的寫作風格,獨到的思維方式, 循序漸進的認識事物的過程所折服。

    盡管帖子是10多年前寫的,今天看來也幾乎沒有一點過時的感覺。 從這個方面來說,shell十三問應該shell的(思想)精華本質所在, 就像武功的內功心法,可能我說的點過, 但是我曾經看過一本shell腳本學習指南,看完后的感覺,還是有感念很朦朧, 而shell十三問是我最容易理解和接受的,這也是我整理的Markdown版本初衷。 為什么不讓好東西讓更多的人熟知呢,恰好年前項目管理開始遷移到git上, 在git上認識一個好東西Markdown,用它可以很簡單地整理出條例清晰篇章。 在年假的時候,覺得這個假期該做點什么, 畢竟馬總都說了,改變世界,不如改變自己。

    本人整理的 [簡體中文Markdown版本的shell十三問][shell-markdown] 的鏈接地址: https://github.com/wzb56/13_questions_of_shell

    網中人的CU原帖shell十三問地址:http://bbs.chinaunix.net/thread-218853-1-1.html

    我簡單將原文整理如下:

    我在CU的日子并不長,有幸在shell版上與大家結緣。 除了跟前輩學習到不少技巧之外,也常看到不少朋友的問題。 然而,在眾多問題中,我發現許多瓶頸都源于shell的基礎而已。 每次要解說,卻總有千言萬語不知從何而起之感......

    這次,我不是來回答,而是準備了關于shell基礎的十三個問題要問大家。 希望的shell的學習者們能夠通過尋找答案的過程,好好的將shell基礎打扎實一點。

    當然了,這些問題我也會逐一解說一遍。 只是,我不敢保證什么時候能夠完成這趟任務。

    除了時間關系外,個人功力實在有限,很怕匆忙間誤導觀眾就糟糕了。 若能拋磚引玉,誘得,其他前輩出馬補充,那才是功德一件。

    ###shell十三問:

  • 為何叫做 shell?

  • shell prompt(PS1) 與 Carriage Return(CR) 的關系? (2008-10-30 02:05 最后更新)

  • 別人 echo、你也 echo ,是問 echo 知多少?( 2008-10-30 02:08 最后更新)

  • " "(雙引號) 與 ' '(單引號)差在哪? (2008-10-30 02:07 最后更新)

  • var=value 在export前后差在哪? (2008-10-30 02:12 最后更新)

  • exec 跟 source 差在哪? (2008-10-30 02:17 最后更新)

  • ( ) 與 { } 差在哪?

  • $(( )) 與 $( ) 還有${ } 差在哪? (2008-10-30 02:20 最后更新)

  • $@ 與 $* 差在哪?

  • && 與 || 差在哪? (2008-10-30 02:21 最后更新)

  • > 與 < 差在哪? (2008-10-30 02:24 最后更新)

  • [你要 if 還是 case 呢?] 12 (2008-10-30 02:25最后更新)

  • for what? while 與 until 差在哪? (2008-10-30 02:26最后更新)

  • [^ ] 跟 [! ] 差在哪?

  • Part-I: Wildcard (2008-10-30 02:25 最後更新)

  • Part-II Regular Expression (2008-10-30 02:26 最后更新)


  • 說明:

  • 歡迎大家補充/擴充問題。

  • 我接觸電腦的中文名稱時是在臺灣,因此一些術語或與大陸不同,請自行轉換。

  • 我會不定時"逐題"說明(以 Linux 上的 bash 為環境) 同時,也會在任何時候進行無預警的修改。請讀者自行留意。

  • 本人于本系列所發表的任文章均可自由以電子格式(非印刷)引用、修改、轉載, 且不必注明出處(若能注明 CU 更佳)。當然,若有錯漏或不當結果,本人也不負任何責任。

  • 若有人愿意整理成冊且付印者,本人僅保留著作權,版權收益之 30% 須捐贈于 CU 論壇管理者,剩余不究。


  • 建議參考談論:

  • shaoping0330 兄關于變量替換的補充:(鏈接在改版后已經失效)

  • shaoping0330 兄關于 RE 的說明:

  • 關于 nested subshell 的討論:(鏈接在改版后已經失效)

  • 關于 IFS 的討論:


    • 感謝 lkydeer 兄整理 word/pdf 版本方便大家參考:

    轉載于:https://my.oschina.net/michao/blog/758884

    總結

    以上是生活随笔為你收集整理的shell十三问--shell教程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    九九免费精品视频在线观看 | 国产传媒中文字幕 | 午夜性色 | av在线免费播放网站 | 在线观看中文字幕一区 | 国产精品不卡在线 | 欧美精品一区二区免费 | 天天草综合 | 国产中文字幕视频在线观看 | 欧美日韩在线播放一区 | 国产在线视频导航 | 日韩av电影中文字幕 | 亚洲毛片一区二区三区 | av免费在线网| 狠狠操.com| 中文字幕在线观看完整 | 国产看片 色 | 最新av在线网站 | 国产精品 日本 | 亚洲国产精品久久久久久 | 日韩在线第一区 | 国产亚洲人成网站在线观看 | 九九九热精品免费视频观看网站 | 国产91精品一区二区 | 99久久99视频只有精品 | 亚洲粉嫩av | 免费看片网站91 | 成人久久18免费 | 高清不卡毛片 | 亚洲精品一区二区在线观看 | 在线不卡视频 | 9在线观看免费高清完整版在线观看明 | 久久伦理| 九色91在线视频 | 国产精品久久久久久久7电影 | 日韩一区精品 | 欧美日韩国语 | 懂色av一区二区三区蜜臀 | 五月天综合婷婷 | 精品一区二区在线免费观看 | 天天射成人 | 亚洲第一中文字幕 | 国产精品一区二区在线观看免费 | 亚洲理论视频 | 久久国产乱 | 97视频免费 | 久久伦理电影网 | 99在线观看免费视频精品观看 | 国产主播大尺度精品福利免费 | 一二三区高清 | 亚洲精品在线免费播放 | 91在线播放国产 | 国产精品乱码久久久 | 激情在线免费视频 | 国产精品福利在线播放 | 99在线视频网站 | www.久久久.com | 99高清视频有精品视频 | 亚洲国产成人精品电影在线观看 | www..com毛片| 激情亚洲综合在线 | 亚洲精品国内 | 亚洲精品国产视频 | 免费看黄在线观看 | 久久综合精品一区 | 91免费国产在线观看 | 999国内精品永久免费视频 | 国产99在线播放 | 国产韩国精品一区二区三区 | 日韩在线观看视频在线 | 国产精品久久久久久久99 | 午夜精品一区二区三区在线观看 | 麻豆观看 | 亚洲精选久久 | 91精品一区国产高清在线gif | 国产自在线观看 | 99精品免费久久久久久久久日本 | 激情五月婷婷综合 | 日韩免费在线一区 | 国产在线视频一区二区 | 午夜国产影院 | 狠狠狠色丁香婷婷综合激情 | 911久久香蕉国产线看观看 | 99精品视频在线 | 97超碰人人澡人人 | 97免费视频在线 | 午夜精品一区二区三区免费 | 夜夜天天干 | 精品字幕在线 | 九九久久精品 | 欧美日韩aa | 国产午夜影院 | 久久久久久久久久久久久久av | 亚洲在线不卡 | 一区二区电影网 | 天天干天天干天天色 | 伊人亚洲综合网 | 91精品网站在线观看 | 欧美一二三视频 | 日韩视频图片 | 在线视频日韩一区 | 69xxxx欧美| 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 午夜精品中文字幕 | 久久精品综合视频 | 国色综合 | 九九热1 | 天天干天天干天天射 | va视频在线| 久久久久国产成人精品亚洲午夜 | 99热最新在线 | 日本不卡123区 | 色在线免费观看 | 中文av在线播放 | 婷婷伊人综合 | 能在线观看的日韩av | 免费性网站 | 国产小视频91 | 成全免费观看视频 | 欧美日韩精品在线 | 肉色欧美久久久久久久免费看 | 99在线观看免费视频精品观看 | 欧美高清成人 | 亚洲综合成人在线 | www.久久视频| 国产免费人成xvideos视频 | 99精品国产一区二区三区麻豆 | 亚洲最新av在线网站 | 国产在线日韩 | 日日夜夜噜 | 天天爽网站 | 国产在线观看黄 | 国产在线国产 | 亚洲国产99 | 久久人人爽人人爽人人片av免费 | 五月视频 | 久久综合欧美精品亚洲一区 | 开心色婷婷| av激情五月| 免费的国产精品 | 日韩一区二区三区高清免费看看 | .精品久久久麻豆国产精品 亚洲va欧美 | 91色综合 | 久久99深爱久久99精品 | 久草精品视频在线观看 | www.久久久久| av在线免费观看网站 | 亚洲一级久久 | 久久久久国产视频 | 人人爱人人添 | 日日夜夜网站 | 精品欧美乱码久久久久久 | 国产精品一区二区久久精品爱涩 | 国产日本在线观看 | 欧美 激情 国产 91 在线 | 成年免费在线视频 | 黄视频网站大全 | 午夜成人影视 | 亚洲精品在线观看不卡 | 久久草在线视频国产 | 国产精品第一视频 | 四虎国产精品成人免费4hu | 国产一级片观看 | 久久久一本精品99久久精品 | 午夜国产福利视频 | 国产一级小视频 | 99精品视频在线观看 | 亚洲精品视频免费看 | www.五月天激情 | 日韩v在线91成人自拍 | 在线中文字幕视频 | 男女视频久久久 | 国产xxxx性hd极品 | 日韩精品视频第一页 | 91丨九色丨国产在线观看 | 国产日韩精品一区二区三区在线 | 欧美国产日韩中文 | 国产成人免费在线观看 | 激情五月婷婷网 | 久久99精品久久久久久久久久久久 | 天天干夜夜爱 | 国内精品久久久久久久久 | 亚洲乱码国产乱码精品天美传媒 | 亚洲综合色站 | a久久久久久 | 国产精品一区二区免费在线观看 | 波多野结衣一区二区三区中文字幕 | 久久久久久久免费 | 久久成年人视频 | 欧美中文字幕第一页 | 成人黄色在线视频 | 成年人国产在线观看 | 丁香综合av | 天天插天天干天天操 | 黄色片网站免费 | 日韩最新中文字幕 | 99精品国产高清在线观看 | 国产成人福利在线观看 | 人人爽人人爽 | 国产在线视频在线观看 | 国产精品免费观看网站 | www.av免费| 午夜久久 | 国产成人精品一区二区三区 | 亚洲最新在线 | 开心激情五月婷婷 | 亚洲成人资源网 | 亚洲午夜精 | 久久免费福利视频 | 免费在线色 | 亚洲小视频在线 | 亚洲欧美精品在线 | 狠狠狠狠狠狠干 | 亚洲精品日韩在线观看 | 欧美做受69 | 日韩av一区二区三区在线观看 | 日本精品一区二区在线观看 | 99久久精品无码一区二区毛片 | 狠狠色伊人亚洲综合成人 | 国产精品成人久久久久久久 | 亚洲高清在线精品 | 91亚洲精品久久久久图片蜜桃 | 国产精品一区二区三区久久久 | 在线免费观看成人 | 欧美一级视频免费看 | 久久久国产电影 | 久久视频二区 | 99视频免费看 | 中文字幕在线观看完整版 | 久久成人在线视频 | 一级α片 | 国产69精品久久久久99 | 四虎影视av | 夜色资源网 | 欧美日韩在线视频观看 | 国产福利在线 | 日韩国产精品久久久久久亚洲 | 久久99精品国产麻豆宅宅 | 久久国产福利 | www免费网站在线观看 | 五月婷婷狠狠 | 日韩在线观看视频网站 | 91爱爱视频 | 亚州精品天堂中文字幕 | 久草视频一区 | 国产福利精品在线观看 | 日韩免费一区二区 | 视频国产一区二区三区 | 香蕉在线观看视频 | 国内一区二区视频 | 亚洲精品综合一二三区在线观看 | 国产在线观看免费观看 | 黄色不卡av | 久久国产电影 | 天天射天天射 | 国产精品伦一区二区三区视频 | 精品国产乱码久久 | 97精品免费视频 | 日日爽| 日本99干网 | 欧美一级激情 | 国产欧美精品一区二区三区四区 | 亚洲在线精品视频 | 中文字幕在线一区二区三区 | 国产永久免费观看 | 91大神免费视频 | 精品专区一区二区 | 国产精品毛片一区二区 | 在线观看日韩中文字幕 | 香蕉在线观看 | 久久综合五月 | 久久综合免费视频影院 | 成人精品一区二区三区电影免费 | 国产黄色大片免费看 | 成人免费看视频 | 日产乱码一二三区别免费 | 日韩欧美一区二区在线观看 | 日韩视频中文字幕在线观看 | 天天干,天天干 | 99久高清在线观看视频99精品热在线观看视频 | 91在线视频 | 日韩欧美综合 | 中午字幕在线观看 | wwwav视频 | 性色av一区二区三区在线观看 | 伊人伊成久久人综合网小说 | 伊人一级 | 天天干天天射天天操 | 免费a v在线 | 在线影院 国内精品 | 色婷婷亚洲综合 | 国产精品观看在线亚洲人成网 | 99久久精品无码一区二区毛片 | 欧美一级黄色片 | 奇米影视在线99精品 | 在线视频亚洲 | 91在线91拍拍在线91 | 91精品国产91久久久久福利 | 免费黄色激情视频 | 成人一级在线观看 | 在线国产一区 | 伊人影院得得 | 久久久久久久久久免费 | 欧美精品久久久久久久久久 | 很黄很色很污的网站 | 国产精品久久久久久高潮 | 最近久乱中文字幕 | 日本爱爱免费视频 | 色综合久久五月 | 久久久久久久久久久影视 | 精品久久久久久久久久久久久久久久 | 国产无套精品久久久久久 | 色视频国产直接看 | 黄色毛片视频免费观看中文 | 色开心| 成人香蕉视频 | 国内精品毛片 | 一区二区视频在线免费观看 | 精品免费视频 | 日韩免费高清在线观看 | 在线亚洲欧美日韩 | 91成人欧美 | 一区二区三区电影大全 | 国产999精品久久久影片官网 | 亚洲午夜精品久久久 | 国产一级免费观看 | 国产精品爽爽久久久久久蜜臀 | 操久久免费视频 | 日韩欧美视频免费在线观看 | 色a资源在线 | 久久国产视频网站 | 亚洲精品国内 | 成年人免费电影在线观看 | 国产又粗又猛又爽又黄的视频先 | 中文字幕高清在线播放 | 超碰人人干人人 | 亚洲精品网站在线 | 99色在线视频 | 亚洲成人资源在线 | 亚洲黄色一级视频 | 97看片网 | www.夜夜爱| 久久免费精品 | 狠狠色丁香久久婷婷综合_中 | 国产色久| 精品久久网 | 久久av电影 | 国产精品久久久久久久久久直播 | 亚洲精品视 | 97成人在线 | 东方av在 | 亚洲夜夜爽| 国产综合小视频 | 国产亚洲精品久久久久久久久久 | 91免费在线 | av免费在线观看1 | 一级黄色免费 | 久久久国产网站 | 亚洲精品免费在线 | 国产一级视频在线免费观看 | 久久久综合电影 | 国产精品乱码久久久久 | 久久久久久久久久免费 | 国产又粗又硬又长又爽的视频 | 婷婷激情小说网 | 美女露久久 | 丁香花在线观看视频在线 | 在线免费观看国产精品 | 在线观看 国产 | 日韩久久视频 | 国产精品视频在线看 | 96视频免费在线观看 | 97免费在线观看视频 | 综合久久一本 | 国产精品久久久久免费观看 | 中文超碰字幕 | 午夜性盈盈| 成年人免费在线 | 91日韩在线视频 | 久久爱992xxoo | 国产一区二三区好的 | 91中文在线观看 | 人人干天天干 | 亚洲欧洲久久久 | 992tv在线观看网站 | 在线观看免费国产小视频 | 国产精品美女久久久久久久久久久 | 国产在线播放一区二区三区 | 免费高清在线视频一区· | 久久成人资源 | 成年人视频免费在线播放 | 免费a级毛片在线看 | 国产福利小视频在线 | 日韩在观看线 | 91精品视频免费观看 | 伊人网综合在线观看 | 婷婷丁香在线 | 国产免费视频在线 | 国产精品18久久久久久久网站 | 国产欧美在线一区二区三区 | 欧美久久久久久久久中文字幕 | 69亚洲视频| 国产精品电影一区 | 大荫蒂欧美视频另类xxxx | 天天综合在线观看 | 97av精品| 四虎在线免费视频 | 色国产精品一区在线观看 | 精品在线视频一区 | 午夜av剧场| 国产九九九视频 | 亚洲 欧美变态 另类 综合 | 日韩精品专区在线影院重磅 | 成人久久18免费网站图片 | 中文字幕丰满人伦在线 | 黄色大片视频网站 | av高清一区二区三区 | 婷婷五情天综123 | 国产精品国产自产拍高清av | 婷婷丁香狠狠爱 | 亚洲国产视频在线 | 国产麻豆精品传媒av国产下载 | 国产一区自拍视频 | 欧美日韩另类在线 | 丝袜美女视频网站 | 久久综合色婷婷 | 欧美大片aaa | 亚洲精品综合在线观看 | 夜夜骑日日操 | 一区二区视频免费在线观看 | 麻豆首页 | av一区在线播放 | 日本特黄一级片 | 伊人伊成久久人综合网小说 | 日韩激情网 | 国产精品一区二区久久 | 国产在线视频一区二区三区 | 五月婷久久 | 亚洲成色| 最新日韩中文字幕 | 99国产成+人+综合+亚洲 欧美 | 久久久91精品国产 | 99精品免费观看 | 久久精品看 | www.777奇米| 99视频精品 | 天天操夜夜拍 | 日韩在线观看网站 | 天天做天天干 | 国产在线观看你懂得 | 人人爱人人射 | 日韩精选在线观看 | 五月开心六月伊人色婷婷 | 亚洲精品xx| 天天弄天天操 | 亚洲精区二区三区四区麻豆 | 色av网站| 亚洲天天在线日亚洲洲精 | 日韩精品在线视频免费观看 | 久久视频在线视频 | 精品嫩模福利一区二区蜜臀 | 国产精品乱码久久久久 | 色婷av | 亚洲视频在线播放 | www日日 | 久草在线免费资源 | 91精品国产九九九久久久亚洲 | 最新av网址大全 | 国产成人三级在线播放 | 亚洲爱视频 | 亚洲四虎 | 偷拍久久久 | 免费在线色 | 99在线视频免费观看 | 久久在线视频在线 | 久久九九久久九九 | 国产一级特黄电影 | 亚洲最大激情中文字幕 | 在线观看一区 | 黄污在线观看 | a在线播放| 日韩精品三区四区 | 91成人免费电影 | www.香蕉视频 | 在线视频精品 | 91在线免费看片 | 久久经典视频 | 狠狠色狠狠色 | 久久精品美女视频 | 欧美日韩一区二区三区免费视频 | 日日干天天射 | 最近中文字幕国语免费av | 日韩免费区 | 亚洲精品中文在线资源 | 日韩在线观看你懂的 | 久久精品中文视频 | 亚洲黄色小说网址 | 天天干夜夜爽 | av福利电影| 国产打女人屁股调教97 | 中文字幕在线看视频 | 最近日本韩国中文字幕 | 亚洲视频免费在线观看 | 久久精品看 | 狠狠操综合 | 人人爱在线视频 | 久久91网 | 九草在线视频 | 国产精美视频 | 久久99热久久99精品 | 最近日本韩国中文字幕 | 久久久夜色 | 成人av资源网 | 亚洲一级电影在线观看 | 天堂av网站| 福利视频导航网址 | 免费看成人 | 亚洲一区不卡视频 | 国产一级精品在线观看 | 97超碰国产在线 | 欧美亚洲一区二区在线 | 九色91福利| 天天操天天操天天 | 久草在线视频在线观看 | 中文字幕在线观看视频免费 | 国产黄在线 | 手机在线看永久av片免费 | 天天天色 | 日日夜夜综合 | 欧美先锋影音 | 在线只有精品 | 狠狠色狠狠色合久久伊人 | 国产人成在线观看 | 成人国产精品久久久 | 综合在线色| 亚洲精品合集 | 激情综合亚洲 | 九热在线 | 日韩在线观看网址 | 亚洲精品视频在线看 | 国产一级在线看 | 波多野结衣一区二区 | 国产精品久久久久久久99 | 91尤物国产尤物福利在线播放 | 婷婷激情综合五月天 | 国产美女黄网站免费 | 久久免费精品视频 | 热re99久久精品国产99热 | 夜夜操综合网 | 日韩精品在线一区 | 丁香六月婷 | 国产成人av电影 | 免费看片网址 | 五月色婷 | 国产在线观看免费观看 | 国产精品高清免费在线观看 | 国产精品久久久精品 | 久久黄色网址 | 天天做天天爱天天综合网 | 国产成人免费观看久久久 | 亚洲伊人成综合网 | 欧美亚洲成人xxx | 国产91精品一区二区麻豆网站 | 国产精品原创av片国产免费 | 人人澡超碰碰 | 草久电影 | 91视频高清免费 | 国产最新在线观看 | 国产黄色精品 | 狠狠狠狠狠狠狠 | 99久久久久久国产精品 | 狠狠的操你 | 国产一区二区午夜 | 激情欧美一区二区三区 | 四虎影视精品 | 91高清免费看 | 伊人久久国产 | 国产视频在线观看一区 | 久久久视频在线 | 久久精品一区二区 | 亚洲在线免费视频 | 激情xxxx| 日韩免费在线视频 | 久久精品女人毛片国产 | 天天色天天操综合 | 91精品久久久久久综合乱菊 | 国产精品99久久久久久久久久久久 | 亚洲国产69 | 97国产电影 | 国产区av在线 | 91免费在线 | 久久男人影院 | 国产精品区一区 | 天天操天天能 | 亚洲九九影院 | 成人一区二区三区在线观看 | 最新av在线免费观看 | 999久久国精品免费观看网站 | 天天草夜夜 | 女人高潮一级片 | 奇米7777狠狠狠琪琪视频 | 伊人成人激情 | 337p日本欧洲亚洲大胆裸体艺术 | 狠狠gao| 西西444www高清大胆 | 久人人 | 天天综合久久 | 国产精品 日本 | 人人要人人澡人人爽人人dvd | 欧美精品在线观看一区 | 日韩欧美在线第一页 | 亚洲aⅴ一区二区三区 | 国产精品12345| 亚洲区视频在线观看 | 久久国产精品视频 | 国产91在线 | 美洲 | 五月天婷婷视频 | 久久国产精品久久精品国产演员表 | av免费电影在线 | 国产中文自拍 | 国产在线观看高清视频 | 欧美日韩免费看 | 99国产免费网址 | 九九久久影视 | 国产精品久久久久久久久久新婚 | 成人蜜桃视频 | 超碰人人做 | 99精品视频在线播放免费 | 97精品伊人 | 日韩欧美视频 | 日韩在线观看三区 | 99精品久久99久久久久 | 中文字幕观看av | 日日碰夜夜爽 | 99久e精品热线免费 99国产精品久久久久久久久久 | 999色视频 | 国产精品国产三级国产aⅴ入口 | 一区二区三区三区在线 | 亚洲国产中文字幕在线 | 国产亚洲视频在线观看 | 国产黄色av网站 | 亚洲视频在线看 | 国产精品久久二区 | 六月久久婷婷 | 99热最新在线 | 国色天香av| 蜜臀av性久久久久蜜臀aⅴ流畅 | 成人黄色影片在线 | 国产视频在线免费观看 | 激情影院在线 | 欧美成人精品欧美一级乱黄 | 免费视频18 | 成人在线观看日韩 | 国产精品永久免费视频 | 99热在线精品观看 | 91久久久久久国产精品 | a黄色一级| av成人免费网站 | 91视频在线免费看 | 亚洲少妇久久 | 又黄又刺激 | 天天干,天天射,天天操,天天摸 | 91精品视频免费看 | 黄色视屏免费在线观看 | 色网站中文字幕 | 国产精品成人一区二区 | 99视频国产精品免费观看 | 91精品国产乱码久久桃 | 日韩av在线高清 | 久久99欧美 | 国产aa精品 | 国产视频黄 | 成人免费ⅴa| 一级欧美一级日韩 | 久久高清| 免费观看全黄做爰大片国产 | 91精品福利在线 | www.久久久.cum| 最近更新中文字幕 | 天天干天天操天天拍 | 人人人爽| 午夜视频在线观看网站 | 在线免费av观看 | 久久在线观看视频 | 亚州av免费 | 91在线精品秘密一区二区 | 久久成人免费电影 | 青春草视频在线播放 | 国产精品久久久久久久久蜜臀 | 国产人成精品一区二区三 | 天天色综合三 | 成年人国产在线观看 | 国产精品免费观看国产网曝瓜 | 日韩精品在线播放 | 天天操天 | 天天天射 | 深夜男人影院 | 激情视频91 | 日本精品久久久久中文字幕5 | 久久手机视频 | av日韩在线网站 | 日韩在线观看视频中文字幕 | 天天干天天操天天射 | 又黄又爽免费视频 | 成人宗合网 | 日韩69av| 国产玖玖在线 | 国产精品一区二区av | 国产成人三级三级三级97 | 日韩免费一区二区在线观看 | 日韩午夜视频在线观看 | 99热这里只有精品1 av中文字幕日韩 | av导航福利| 亚洲乱码久久 | 亚洲首页 | a久久免费视频 | 国产亚洲精品久久久久久 | 中文字幕在线看视频 | 国产字幕在线播放 | 国产99久久99热这里精品5 | 97看片 | 99亚洲天堂| 精品国产一区二区三区在线 | 国产在线超碰 | 亚洲色图 校园春色 | 国产黄色精品网站 | 99久久er热在这里只有精品15 | 精品自拍网 | 久草热视频 | 91麻豆看国产在线紧急地址 | 中文字幕亚洲精品日韩 | 色欧美88888久久久久久影院 | 99视频99 | 天天弄天天操 | 国产一级a毛片视频爆浆 | 久久首页 | 亚洲精品免费在线视频 | 一二三精品视频 | 中文字幕在线久一本久 | av国产在线观看 | 色婷婷激情 | 九色精品免费永久在线 | 99免费在线视频观看 | 五月婷丁香网 | 国产在线视频一区 | 国产馆在线播放 | 国产123av| .精品久久久麻豆国产精品 亚洲va欧美 | 久久久综合香蕉尹人综合网 | 国产精品二区在线观看 | 91成年视频 | 中文字幕av在线不卡 | 国内精品视频一区二区三区八戒 | 成人亚洲精品国产www | 欧美日韩高清一区二区 | 伊人五月| 免费福利在线播放 | 99久久久久久久久 | 国产又粗又猛又爽又黄的视频免费 | 亚洲经典视频 | 国产精品美女久久久久aⅴ 干干夜夜 | 国产香蕉97碰碰久久人人 | 91综合久久一区二区 | www亚洲精品| 97成人精品视频在线观看 | 中文字幕av最新更新 | 黄色软件网站在线观看 | 免费瑟瑟网站 | 日韩在线视频播放 | 国产中文字幕视频在线 | 国产在线毛片 | 91av视频播放 | 国产在线91在线电影 | 国产91免费观看 | 99久久久久久久久 | 亚洲视频高清 | 九九九九九精品 | 成人午夜精品 | 国产一级精品绿帽视频 | 日韩免费播放 | 国产剧情在线一区 | 超碰成人网 | 国产精品9999久久久久仙踪林 | 日韩免费在线观看网站 | 欧美午夜久久久 | 国产福利网站 | 日本精品中文字幕 | 日批网站免费观看 | 国产麻豆果冻传媒在线观看 | 天天操天天草 | 成年人免费观看在线视频 | 综合天堂av久久久久久久 | 成人在线观看网址 | 天天久久综合 | 日本三级全黄少妇三2023 | 久久成人精品视频 | 久久婷婷精品 | 麻豆精品视频在线 | 9在线观看免费高清完整版在线观看明 | 国产区久久 | 久久久久伦理电影 | 蜜桃视频成人在线观看 | 久草视频在线观 | 国产成人av一区二区三区在线观看 | 国产一区二区三区 在线 | 日本三级人妇 | 免费黄色av电影 | 99爱精品视频 | 九九免费在线看完整版 | 激情综合啪啪 | 亚洲精品国产精品国产 | 久操97 | 97超碰色| 麻豆91视频 | 久久久电影网站 | 国产麻豆精品一区 | 91精品国产三级a在线观看 | 99精品国产99久久久久久97 | 婷婷综合伊人 | 天天人人综合 | 精品国产欧美 | 视频一区久久 | 国产成人一区二区在线观看 | 亚洲高清在线视频 | 国产精品 亚洲精品 | 西西www4444大胆视频 | 国产青春久久久国产毛片 | 成人a在线观看 | 天天干天天操天天爱 | 久久视| av在线看网站 | 夜夜视频资源 | 日韩中文字幕一区 | 成人在线观看影院 | 黄色亚洲精品 | 成人cosplay福利网站 | 天堂激情网 | 国产一区二区不卡视频 | 国产成人精品一区二区三区福利 | 在线影视 一区 二区 三区 | 国产高清精品在线 | 在线观看视频一区二区三区 | 日韩黄色在线观看 | 久久免费99精品久久久久久 | 日本性xxx | 中文字幕高清av | 亚洲va天堂va欧美ⅴa在线 | a级片久久久 | 久久国产一区 | 51久久成人国产精品麻豆 | 国产精品久久久久久久久久ktv | 996久久国产精品线观看 | 日日夜夜天天久久 | 午夜精品99久久免费 | 亚洲欧美日韩国产 | 亚洲婷婷丁香 | 日本成人免费在线观看 | 国产黄色片一级 | 日韩精品一区二区三区免费观看 | 成人在线视频免费看 | 免费看av片网站 | 日韩专区中文字幕 | 操操操日日日 | 五月婷婷丁香在线观看 | 91在线视频观看 | 亚洲最新av在线网站 | 日韩一区二区三区高清在线观看 | 久久国产精品久久久 | 中文字幕在线播放av | 欧美一级爽 | 国产人在线成免费视频 | 国产v欧美 | 亚洲国产精品人久久电影 | 精品国产自在精品国产精野外直播 | 在线看片日韩 | 婷婷丁香激情网 | 精品福利国产 | 国产.精品.日韩.另类.中文.在线.播放 | 成人国产精品久久久春色 | 国产午夜精品久久久久久久久久 | 一级免费黄视频 | 2023年中文无字幕文字 | 久久情侣偷拍 | 中国一区二区视频 | 日韩av片免费在线观看 | 日韩 精品 一区 国产 麻豆 | 91网址在线看 | 久热久草在线 | 成人免费在线视频观看 | 免费观看的av网站 | 精品五月天| 久久综合久久综合久久综合 | 丰满少妇一级片 | 亚洲综合欧美日韩狠狠色 | 精品久久久网 | 8x成人免费视频 | 国产一区黄色 | 久久国产精品免费一区二区三区 | 国产成人精品久久亚洲高清不卡 | 日韩av中文字幕在线免费观看 | 911久久香蕉国产线看观看 | 国产精品免费一区二区三区 | 九色视频网址 | 日本中文字幕在线电影 | 国产高清专区 | 久久久五月天 | 黄色av一区二区 | 久久国产高清视频 | 国产精品9999久久久久仙踪林 | 国产高清日韩欧美 | 精品国产中文字幕 | 久久精品亚洲一区二区三区观看模式 | 精品亚洲欧美无人区乱码 | 亚洲欧洲一级 | 日韩草比 | 日批视频在线观看免费 | 91亚洲精品国产 | 日韩av黄| 亚洲欧洲国产日韩精品 | 日韩在线观看网址 | 五月综合在线观看 | 精品少妇一区二区三区在线 | 日日干日日 | 青青河边草免费观看完整版高清 | 久久国产综合视频 | 日韩免费视频 | 婷婷视频在线播放 | 成人av在线直播 | 国产精品一区二区在线免费观看 | aaa日本高清在线播放免费观看 | 中文字幕中文字幕 | 四虎国产精品免费观看视频优播 | 亚洲成人中文在线 | 久99热| 人人射人人爽 | 麻豆视频免费 | 一区二区三区视频在线 | 天天综合色天天综合 | 久久久精品免费观看 | 欧美国产视频在线 | 欧美成人精品欧美一级乱黄 | 婷婷色站 | 亚洲精品九九 | 国产91综合一区在线观看 | 久久avav| 久久视频在线 | 日韩啪视频 | 国产亚洲在 | 久久99偷拍视频 | 国产精品精品国产 | www日韩精品 | 国产在线观看午夜 | 欧美资源| 五月婷婷在线观看视频 | 国产黄色精品 | 久久久久国产精品一区二区 | 色偷偷88888欧美精品久久 | 欧美精品免费视频 | 日韩精品中文字幕av | 日韩电影精品一区 | 99久久精品无免国产免费 | 精品产品国产在线不卡 | 超碰97久久 | 免费瑟瑟网站 | 毛片在线播放网址 | 91超在线 | 日韩高清在线一区二区三区 | 91视频免费网站 | 一区二区丝袜 | 国产成人精品一区二区三区在线 | 日日躁你夜夜躁你av蜜 | 成人免费网站在线观看 | 亚洲日本va在线观看 | 亚洲国产精品成人va在线观看 | 99色资源 | 亚洲在线网址 | 亚洲黄色在线 | 999久久久久久久久久久 | 人人干天天干 | 香蕉视频导航 | 色综合天天综合 | 国产精品青草综合久久久久99 | 欧美日韩视频网站 | 欧美精品乱码久久久久久按摩 | 亚洲精品综合一二三区在线观看 | 91视频免费看片 | 狠狠操导航 | 在线不卡中文字幕播放 | 狠狠躁夜夜a产精品视频 | 在线视频 国产 日韩 | 国产精品v欧美精品 | 911av视频 | 99精品欧美一区二区蜜桃免费 | 天天插综合 | 免费看片日韩 | 美女国产 | 99人久久精品视频最新地址 | 麻豆va一区二区三区久久浪 |