Shell


操作範例

  1. cd /etc
  2. ls -l 看看有沒有一個 passwd 檔?
  3. 用 less 檢視一下 passwd 的內容, 找到關於你自己的資訊。
  4. grep $USER passwd 咦, 只印出關於自己的資訊! 為什麼?
  5. ls -l $USER 錯誤訊息說什麼?
  6. ls -l $HOME 咦, 印出自己家裡的檔案?
  7. set -x 再重複先前三個指令。
  8. 我們輸入的命令列, shell 會先對它做各種處理與代換 (substitution/ expansion) 用 set -x 這個 內建指令 (builtin command) 可以令 shell 在執行命令之前, 印出它最終代換的結果, 也就是 外部指令 (external command) 真正看到的命令列。 如果要取消, 可以這樣: set +x
  9. ll -d $HOME/.[a-z]* 這句話裡面發生了三個代換: alias、 environment variable、 file name glob。
            使用者下的命令:         ll $HOME/.[a-z]*
                                        ||
                                        ||          (經過 shell 處理)
                                       \  /
                                        \/
            最後把 ls           ls -l --color=auto /home/ckhung/.bash_history
            叫起來之前變成:                        /home/ckhung/.bashrc ...
    
  10. File name glob 的範例: 先 cd /usr/bin, 然後:
            echo z*                 所有 z 開? ?的檔案
            echo l*s                所有以 l 開? ?, 以 s 結尾的檔案
            echo *zip*              所有名稱中間有 zip 的檔案
            echo ??zip              所有名稱長度為 5, 且以 zip 結尾的檔案
            echo [A-Z]*             所有以大寫字母開? ?的檔案
            echo *[0-9]*            所有名稱當中至少有一個數字的檔案
        
    

    請注意: 這些代換都是 shell 在做的。 echo 這個命令在被呼叫起來之前, 這些特殊符號早就已經被 shell 展開成檔名了。 藉由 file name glob 的功能, 可以在沒有 ls 可以用的情況下以 echo 達到簡單的 ls 功能。 又, 這裡用到的符號, 跟 regular expressions 的意義不一樣。 (dos/windows 使用者: dos 的 command.com 也是一個 shell, 但它並不支援 file name glob, 所以一個應用程式想要認得命令列上的 * 與 ? 要靠它自己處理。)
  11. echo $HOME 印出 環境變數 (environment variable) HOME 的值。 這是你的家目錄。 其他與目錄有關的環境變數: PATH 命令的搜尋路徑; MANPATH 手冊的搜尋路徑。 LANG 則是系統顯示訊息時要採用哪種 (自然) 語言 -- 中文或英文或日文之類的。
  12. 許多程式共同使用的環境變數: $EDITOR$VISUAL 你要用那個文字檔編輯器; $PAGER 你要用那個 "分頁器" (文字檔閱覽器) 來看輸出.
  13. 例: 在 tcsh 下 setenv PAGER head 或在 bash 下 export PAGER=head 之後再 man 1 echo Q: 如果把 PAGER 設定成 cat, 從此以後使用 man 會怎麼樣? 有沒有辦法在不把 PAGER 改回來的情況下仍舊可以讀手冊?
  14. 改變 shell 的提示符號: (bash 適用) export PS1='\t \[\033[4;35m\]\h:\w\$\[\033[0m\] ' 或更簡短的 export PS1='\t \e[4;35m\h:\w\$\e[m ' 詳見 bash(1) 裡面的 PROMPTING 以及 這篇。 可以把這句話放在 ~/.bashrc 裡面, 這樣每次開一個新的 bash, 都會執行這句。
  15. 看看有那些環境變數: env
  16. Q: 你的環境下有幾個環境變數? Q: 有一個環境變數記載著這部機器的名稱 (例如 penguin, 或 mail, 或 digital), 是那一個?
  17. alias lltype ll 可以看到 ll 其實是 "ls -l" 的 別名 alias。 (其實概念比較像是 「簡寫」)
  18. 命令別名範例: 先看看危險的命令 mv 的手冊, 那一個選項可以讓系統在面臨有覆蓋舊檔危險時警告你? 現在用 alias 看看目前有那些命令別名. 然後加入兩個別名 (bash 版):
            alias mv='echo use mov instead'
            alias mov='/bin/mv -i'
    

    (如果是 tcsh, 則把 = 改成空格.) 再用 alias 檢查成果. 最後試著用 mv 與 mov 更改檔名. 又, 此時下 which mvwhich mov 得到什麼?
  19. alias 的應用實例: alias pgrep='/bin/grep -P --color=auto' 從此以後, pgrep 指令裡面可以用 PCRE 而不必另學一套 grep 自己的 regular expression。
  20. 如果你的 /etc/mailcap 設定正確的話, 文字瀏覽器 lynx 也可以用來選擇性地看圖形. 但必須把 lynx 的 -image_links 打開, 才看得到圖形的超連結; 而這只能在命令列上打開, 無法在 .lynxrc 當中設定. 所以只好在 .bashrc 當中加入 alias lynx='/usr/bin/lynx -image_links'
  21. Q: dir 命令作什麼用的? 如果不用任何代換而想造成相同的效果, 要如何下命令?
  22. 消除命令別名範例: unalias mov
  23. 題外話:
    1. 看看一個二進位檔案裡面有沒有一些可以印出來看的字串: strings /usr/bin/man
    2. 看看 ckhung 這個使用者發呆發了多久: finger ckhung 修改自己的資料, 讓別人 finger 你時看到你的姓名電話: chfn
  24. [圖解 '命令結果代換'] 命令結果代換 command substitution (舊稱 Back quote) 的範例
    1. grep $(whoami) /etc/passwd 與先前的 grep $USER /etc/passwd 有相同效果.
    2. 主機名稱到底是在哪裡設定的? grep $HOSTNAME $(find /etc -type f)
    3. 看看 man 這個命令的可執行檔中, 有那些可以印出來看的字串: strings $(which man)
    4. 找出 man 命令的可執行檔中, 看起來像是環境變數的字串: strings $(which man) | grep '^[A-Z]\+$'
    5. 用 finger 查看目前在線上的每個使用者: finger $(who -q | grep -v '^#' ) 如果覺得太複雜, 可以分成兩步來做: who -q | grep -v '^#' > a; finger $(cat a)
    6. 也請參考 「組合的力量」 當中更多的例子。
  25. [用 xargs 取代 '命令結果代換'] xargs 命令可以把本來從 stdin 輸入的資料, 轉成後面那個命令的命令列參數. 這可以在不支援 back quote substitution 的 shell 下做到 back quote substitution 的功能. 上述兩例可以改成: which man | xargs strings | grep '^[A-Z]\+$' 以及 who -q | grep -v '^#' | xargs finger
  26. 注: strings $(which man) 古代的寫法是 strings `which man` 不過這種寫法無法層層相疊, 所以建議還是用前一種寫法。
  27. 我個人認為 pipe 與 back quote substitution 是所有命令列觀念當中, 最重要的兩項. 學會靈活使用這兩項來組合其他指令, 就可以用有限的指令產生出許多神奇的效果. 如果再配合上 regular expression, 就可以算是半個 unix 高手了.
  28. 歷史代換: 先用 history 看一下你先前曾經下過那些命令.
            !!                      前一個命令再執行一次
            !-5                     倒數第五個命令再執行一次
            !58                     把第 58 個命令再執行一次
            !wh                     把最近一次下過, 以 wh 開頭的命令再執行一次
        
    

  29. 歷史代換也可以嵌在其他命令或代換裡面: finger $(!wh) 例: 從 who -q 開始, 逐次使用 !! 於下一命令中, 終至將上面命令結果代換的例子寫出.
  30. 避免代換的字元: ' ... ' 避免掉幾乎所有的代換; " .... " 避免掉多數代換, 但是裡面變數代換照常進行; \ 避免掉它後面那個字元的特殊意義。 請看看這幾個指令的差別:
    	echo $HOME
    	echo "$HOME"
    	echo '$HOME'
    	echo \$HOME
    	
  31. 作業: 運用 file name glob 與避免代換的字元等等功能, 把自己家裡亂七八糟的檔案清除乾淨.
  32. 作業: 自己查手冊, 看看下面這兩個命令有什麼用處:
            find ~ -name '*.[ch]' | zip -@ ~/backup.zip
            zip ~/backup.zip $(find ~ -name '*.[ch]' )
        
    

    如何把上述命令設定成 alias, 讓你將來 login 到你的帳號以後, 隨時都可以下 "savework" 命令, 而達到備份重要資料的效果? (提示: 可能需要用到 " ... " 避免 shell 在你下 alias 命令時就急著想要代換.)
  33. 要取消「印出 shell expansion 處理完的命令列字串」這個效果, 可以在 bash 視窗內下 set +x 在 tcsh 視窗內下 unset echo
  34. bash 提供一些 快捷鍵
  35. 如果你發現 shell 變得很難用 -- 例如沒有快捷鍵、 檔案名稱快打、 ... 等等功能, 有可能是因為你用的是 /bin/sh 而不是 /bin/bash 。 用 ps 指令檢查看看目前正在使用哪個 shell, 也用 grep $USER /etc/passwd 檢查看看系統指定什麼 shell 給你用。
  36. 要改變你所使用的 shell 可以用 chsh 但是並不是系統內有安裝的 shells 都可以用, 只有記載在 /etc/shells 內的才可以用. 如果你喜愛的 shell (例如是 pdmenu) 並未在允許的 shells 之中, 可以在 .login 或 .profile 當中寫 exec /usr/bin/pdmenu
  37. 簡單的迴圈:for i in guava pineapple cherry orange ; do echo $i ; done
  38. 例如要將一片 12 軌的音樂 cd 上所有的歌曲全部變成 mp3, 可以這樣下: for i in 1 2 3 4 5 6 7 8 9 10 11 12 ; do cdda2wav -D /dev/cdrom -x -q -t $i+$i - | lame -h -S - $i.mp3 ; done 至於它到底有幾軌, 如何得知呢? cdda2wav -D /dev/cdrom -J -v all
  39. 一口氣把家目錄底下 (含子目錄, 孫目錄, ...) 所有 .doc 檔轉換成 .html 檔: for f in $(find ~ -iname '*.doc' | sed 's/\.doc//' ) ; do echo "converting $f.doc ..."; wvHtml $f.doc $(echo $f | sed 's#.*/##g').htm ; done
  40. 如何一次產生很多帳號? 例如要從這樣一個學生名單檔案 grade.txt 一口氣建立很多 unix 帳號, 可以這樣下: for user in $(perl -ne 'print "$1\n" if /^(\w+):/' grade.txt) do ; useradd -g users $user ; done

名詞解釋

  1. substitution: 代換. shell 在把控制權暫時轉交給一個命令之前, 會事先處理命令列, 也就是說, 每個命令見到的選項與參數與使用者打進去的可能有所出入. 有許多類不同的代換, 分別由不同的特殊字元表示.
  2. file name glob (wildcard): 用 ? * [] 等等特殊字元表示一次要取得好幾個檔案名稱當做命令的參數.
  3. environment variable: 環境變數. 用來修改許多程式的行為的機制, 讓每個使用者可以依照自己的喜好裝飾/調整工作環境.
  4. (command) alias: (命令的) 別名. 如果你有一個常用的命令 (如 mv), 固定要用某些選項 (如 -i), 可以定義別名, 用來縮短命令; 又或者你已經習慣其他作業系統的某些命令 (如 dir), 可以替 UNIX 下的命令 (如 ls) 取一個與該常用命令相同的別名.
  5. command substitution: 命令結果代換, 或稱為 back quote substitution: 把前一個命令的結果放在參數列, 當做下一個命令的參數. xargs 可以在不支援 command substitution 的 shell 下做到相同的效果.
  6. history substitution: 歷史代換. 把過去下過的命令叫出來重複使用.
  7. shell initialization: 有些常用設定 (例如自己設的 alias 與環境變數), 甚至是其他任何命令, 你希望每次 login 之後就自動執行, 可以放在特殊設定檔內. 對 bash 用 ~/.profile 對 tcsh 用 ~/.login 另外還有 bash 的 ~/.bashrc 及 tcsh/csh 共用的 ~/.cshrc 裡面的命令, 每進一層 shell (可以用 echo $SHLVL 檢查), 這些命令就會執行一次.

作業

  1. 請列出 /usr/share/doc/HOWTO 目錄下, 檔名長度不超過 13 的檔案.