Linux 基礎學習篇 - Fedora Core 4

第十一章、認識 BASH Shell - for Fedora Core 4

學習 bash shell

最近更新時間: 2007/07/15

本文資料主要針對 Fedora Core 4 的系統進行說明, Fedora Core 1 主要是由 Red Hat Linux 9 改版而來, 這個 Red Hat Linux 9 並不是當前大家聽到的 RHEL 喔!那是在 RHEL 出現之前的產品,基本上是在 2003 年以前的作品了!Fedora Core 4 則是在 2005 年 6 月份釋出,使用的核心是 2.6.11 版,當時是很紅的一個作品!只是生命週期太短,所以用這個 Fedora 系列來介紹 Server, 當時的決定確實有點莫名其妙了...

建議您前往本站查詢最新版本的 Linux distribution 文章來閱讀,比較不會浪費時間。那為何還需要編輯 Fedora Core 4 的資料呢? 鳥哥只想要做個自己曾經撰寫過的文件內容保存而已囉! ^_^!最新文章請前往鳥站首頁查閱囉!

文字模式 (command line) 這種指令下達的方式,在 Linux 裡面,其實就相當於是 bash 的工具與介面! 因為 Linux 就是以 bash 為預設的 shell 的!那麼前幾章我們都已經很快樂的進行了很多的指令下達囉~ 所以說, bash shell 根本就不難吧~是啦!只要能夠熟悉的話,那麼確實他也不是這麼不可親近的一項工具啊~ 這個章節中,鳥哥會由變數談起,先講到環境變數的功能與修改的問題, 然後會繼續提到歷史指令的運用。接下來,就會談一下『資料流重導向』這個重要概念, 最後就是管線命令的利用啦!好好清一清腦門,準備用功去囉~ ^_^ 這個章節幾乎是所有 command line 與未來主機維護與管理的重要基礎,一定要好好仔細的閱讀喔!

Bash shell

我們在前面的 什麼是 Linux 那個章節當中,提到了, 管理整個硬體的其實是核心 (kernel),那我們一般使用者 (user) 則是以 shell 來跟核心溝通~ 讓核心達到我們所想要達到的工作目的。那麼系統有多少 shell 可用呢? 為什麼我們要使用 bash 啊?!底下分別來談一談喔!

什麼是 Shell?

這應該是個蠻有趣的話題:『什麼是 Shell ?』相信只要摸過電腦,對於作業系統 ( 不論是 Linux 、 Unix 或者是 Windows ) 有點概念的朋友們大多聽過這個名詞,因為只要有『作業系統』那麼就離不開 Shell 這個東西。不過,在討論 Shell 之前,我們先來瞭解一下電腦的運作狀況吧! 舉個例子來說:當你要電腦傳輸出來『音樂』的時候,你的電腦需要什麼東西呢
  1. 當然就是需要你的硬體有『音效卡晶片』這個硬體配備,否則怎麼會有聲音;
  2. 作業系統的核心可以支援這個晶片組,當然還需要提供晶片的驅動程式囉;
  3. 需要使用者(就是你)輸入發生聲音的指令囉!
這就是基本的一個輸出聲音的需要的步驟!那麼也就是說,你必須要『輸入』一個指令之後, 『硬體』才會透過你下達的指令來工作!嘿嘿!那麼硬體如何知道你下達的指令呢?那就是 kernel (核心)的控制工作了!瞭解了嗎?沒錯!也就是說,我們必須要透過『 Shell 』將我們輸入的指令與 Kernel 溝通,好讓 Kernel 可以控制硬體來正確無誤的工作! 基本上,我們可以透過底下這兩張圖來說明一下:

硬體、核心與使用者的相關性圖示
圖一、硬體、核心與使用者的相關性圖示

硬體、核心與使用者的相關性圖示
圖二、硬體、核心與使用者的相關性圖示

基本上,替我們工作的是『硬體』,而控制硬體的是『核心』,再來,我們使用者乃是利用『Shell』控制一些 kernel 提供的 『工具 (Utility)』來操控硬體替我們正確的工作。再進一步來說,由於 kernel 聽不懂人類的語言,而人類也沒有辦法直接記得 kernel 的語言,所以兩者的溝通就得藉由 shell 來支援了!(其實早期的 DOS 的文字介面也是使用 shell 來溝通呀!那個 shell 的名稱就叫做 command.com ,還記得嗎? ^_^)

以字面上的意思來說, kernel 是『核心』的意思,而 Shell 是『殼』的意思,呵呵!也就是說, shell 是最外頭的咚咚!而 kernel 乃是最內層的的咚咚啦!核心是作業系統的最底層的東西! 這個核心裡頭包括了各種的支援硬體的工具!當然囉,如果你的硬體太新,而你的 kernel 並沒有支援的話,那麼很抱歉,你的 Shell 能力再怎麼強,也沒有辦法使硬體工作的! 這樣可以瞭解了嗎?呵呵!沒錯!使電腦主機工作的正是核心的任務,但是操作核心來替使用者工作的,卻是 shell 喔!因此,有時候你的 shell 搞了老半天,硬體卻不能工作的時候,請注意, 您的『核心』是否正確呢?阿!扯遠了!這是 kernel 章節才要說的東西。

  • 我幹嘛要學習文字模式的 Shell 呢?
  • 我們常常提到的 shell 其實是比較狹隘的定義,一般來說,在 Linux 裡頭,所謂的 shell 就是指 BASH 這個文字模式的 shell 囉。但是,廣義的 shell 也可以是 KDE 之類的圖形介面控制軟體呢! 因為他也可以幫我們與 kernel 進行溝通啊!不過,在鳥哥的 Linux 私房菜裡面, 如果沒有特別說明的話,那麼我們的 shell 指的是比較狹義的,也就是文字模式的 shell 喔!

    另外,鳥哥常常聽到這個問題:『我幹嘛要學習 shell 呢? 不是已經有很多的工具可以提供我設定我的主機了?我為何要花這麼多時間去學指令呢?不是以 X Window 按一按幾個按鈕就可以搞定了嗎?為什麼要這麼麻煩?』唉~還是得一再地強調, X Window 還有 Web 介面的設定工具例如 webmin 是真的好用的傢伙, 他真的可以幫助我們很簡易的設定好我們的主機,甚至是一些很進階的設定都可以幫我們搞定。

    但是鳥哥在序章裡面也已經提到過相當多次了, X Window 的介面雖然親善,功能雖然強大,而 web 介面的工具也可以提供我們很友善的服務,但是畢竟他是將所有利用到的套件都整合在一起的一個套件而已, 並非是一個完整的套件,所以某些時候當你升級或者是使用其他套件管理模組( 例如 tarball 而非 rpm 檔案等等 )時,就會造成設定的困擾了。

    此外,遠端連線時,文字介面的傳輸速度一定比較快, 而且,較不容易出現斷線或者是資訊外流的問題,因此, shell 真的是得學習的一項工具。而且,他可以讓您更深入 Linux ,更瞭解他, 而不是只會按一按滑鼠而已!所謂『天助自助者!』多摸一點文字模式的東西,會讓你與 Linux 更親近呢!

    有些朋友也很可愛,常會說:『我學這麼多幹什麼? 又不常用,也用不到!』嘿嘿!有沒有聽過『書到用時方恨少?』 當你的主機一切安然無恙的時候,您當然會覺得好像學這麼多的東西一點幫助也沒有呀! 萬一,某一天真的不幸給他中標了,您該如何是好?是直接重新安裝? 還是先追蹤入侵來源後進行漏洞的修補?或者是乾脆就關站好了?這當然涉及很多的考量, 但就以鳥哥的觀點來看,多學一點總是好的,尤其我們可以有備而無患嘛!甚至學的不精也沒有關係,瞭解概念也就 OK 啦!畢竟沒有人要您一定要背這麼多的內容啦!瞭解概念就很了不起了!

    此外,如果您真的有心想要將您的主機管理的好,那麼良好的 shell 程式編寫是一定需要的啦!就鳥哥自己來說,我管理的主機雖然還不算多, 只有區區不到十部,但是如果每部主機都要花上幾十分鐘來查閱他的 log file 以及相關的資訊,那麼我可能會瘋掉!基本上,也太沒有效率了!這個時候,如果能夠藉由 shell 提供的命令重導向( 或稱資料流重導向 ),以及管線命令,呵呵!那麼我分析 log file 只要花費不到十分鐘就可以看完所有的主機之重要資訊了!相當的好用呢!

    由於學習 shell 的好處真的是多多啦!所以,如果您是個系統管理員,或者有心想要管理系統的話,那麼 shell 這個東西與 shell scripts 這個東西,真的真的有必要看一看!

    系統的 shell 與 /etc/shells 功能

    知道什麼是 Shell 之後,那麼我們來瞭解一下 Linux 使用的是哪一個 shell 呢?什麼!哪一個?難道說 shell 不就是『一個 shell 嗎?』哈哈!那可不!由於早年的 Unix 年代,發展者眾,所以由於 shell 依據發展者的不同就有許多的版本,例如常聽到的 Bourne SHell (sh) 、在 Sun 裡頭預設的 C SHell、 商業上常用的 K SHell、, 還有 TCSH 等等,每一種 Shell 都各有其特點。至於 Linux 使用的這一種版本就稱為『 Bourne Again SHell (簡稱 bash) 』,這個 Shell 是 Bourne Shell 的增強版本,也是基準於 GNU 的架構下發展出來的呦!

    在介紹 shell 的優點之前,先來說一說 shell 的簡單歷史吧:第一個流行的 shell 是由 Steven Bourne 發展出來的,為了紀念他所以就稱為 Bourne shell ,或直接簡稱為 sh !而後來另一個廣為流傳的 shell 是由柏克萊大學的 Bill Joy 設計依附於 BSD 版的 Unix 系統中的 shell ,這個 shell 的語法有點類似 C 語言,所以才得名為 C shell ,簡稱為 csh !由於在學術界 Sun 主機勢力相當的龐大,而 Sun 主要是 BSD 的分支之一,所以 C shell 也是另一個很重要而且流傳很廣的 shell 之一 ( 因為太多的程式設計師使用的就是 C 語言啦! )!(還記得我們在 Linux 是什麼那一章提到的吧? Sun 公司的創始人就是 Bill Joy,而 BSD 最早就是 Bill Joy 發展出來的啊!)。

    那麼目前我們的 Linux (以 FC4 為例) 有多少我們可以使用的 shells 呢? 你可以檢查一下 /etc/shells 這個檔案,至少就有底下這幾個可以用的 shells:
    • /bin/sh (已經被 /bin/bash 所取代)
    • /bin/bash (就是 Linux 預設的 shell)
    • /bin/ksh (Kornshell 由 AT&T Bell lab. 發展出來的,相容於 bash)
    • /bin/tcsh (整合 C Shell ,提供更多的功能)
    • /bin/csh (已經被 /bin/tcsh 所取代)
    • /bin/zsh (基於 ksh 發展出來的,功能更強大的 shell)
    由上面的說明中,我們大概可以發現,其實各主要 shell 的功能都差不多, 有的只是語法上面的不同而已。目前一般的使用者使用習慣上,似乎是以 bash 及 csh 為主要的兩個 shell 。OK!這麼多的 shell 我要使用哪一個啊?呵呵!使用 Linux 支援最廣泛的 bash 就好了! 不要想太多!另外,咦!為什麼我們系統上的 shell 要寫入 /etc/shells 這個檔案啊? 這是因為系統某些服務在運行過程中, 會去檢查使用者能夠使用的 shells ,而這些 shell 的查詢就是藉由 /etc/shells 這個檔案囉!

    舉例來說,某些 FTP 網站會去檢查使用者的可用 shell ,而如果你不想要讓這些使用者使用 FTP 以外的主機資源時,可能會給予該使用者一些怪怪的 shell,讓使用者無法以其他服務登入主機。 這個時候,你就得將那些怪怪的 shell 寫到 /etc/shells 當中了。舉例來說,我們的 FC4 的 /etc/shells 裡頭就有個 /sbin/nologin 檔案的存在,這個就是我們說的怪怪的 shell 囉~

    那麼,再想一想,我這個使用者什麼時候可以取得 shell 來工作呢?還有, 我這個使用者預設會取得哪一個 shell 啊?!還記得我們在 首次進入 Linux -- 以文字方式登入 那個章節當中提到的登入動作吧?當我登入的時候,系統就會給我一個 shell 讓我來工作了。 而這個登入取得的 shell 就記錄在 /etc/passwd 這個檔案內!這個檔案的內容是啥?
    [root@linux ~]# cat /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    .....(中間省略).....
    
    如上所示,在每一行的最後一個資料,就是您登入後,可以取得的預設的 shell 啦! 那你也會看到, root 是 /bin/bash ,不過,系統帳號 bin 與 daemon 等等,就使用那個怪怪的 /sbin/nologin 囉~關於使用者這部分的內容,我們留在 帳號管理 時提供更多的說明。

    Bash shell 的功能

    既然 /bin/bash 是 Linux 預設的 shell ,那麼總是得瞭解一下這個玩意兒吧! BASH 是怎麼一回事呢?這個 shell 是 GNU 計畫中重要的工具軟體之一,目前也是 GNU 作業系統中標準的 shell ,他主要相容於 sh ,並且依據一些使用者需求,而加強的 shell 版本,可以說目前幾乎所有的 Linux distribution 都是使用 bash 作為管理核心的主要 shell !因此,不論您使用的是那個 distribution ,你都難逃需要學習 bash 的宿命啦!那麼這個 shell 有什麼好處,幹嘛 Linux 要使用他作為預設的 shell 呢? BASH 主要的優點有底下幾個:

  • 命令編修能力(類似 DOS 的 doskey 功能):
  • 使用 bash 裡頭,個人認為相當棒的一個功能就是『他能記憶使用過的指令!』 這功能真的相當的棒!因為我只要在指令列按『上下鍵』就可以找到前一個輸入的指令!而在很多 distribution 裡頭,預設的指令記憶功能可以到達 1000 個!也就是說, 你曾經下達過的指令都被記錄下來了,記錄的檔案在你的家目錄內的 .bash_history !不過,需要留意的是, ~/.bash_history 記錄的是前一次登入以前所執行過的指令, 而至於這一次登入所執行的指令都被暫存在暫記憶體中,當您成功的登出系統後,該指令記憶才會記錄到 .bash_history 當中

    這有什麼功能呢?最大的好處就是可以『查詢曾經做過的舉動!』, 如此可以知道你的執行步驟,那麼就可以追蹤您曾下達的指令,以作為除錯的工具! 但如此一來也有個煩惱,就是如果被駭客入侵了,那麼他只要翻你曾經執行過的指令, 剛好你的指令又跟系統有關(例如直接輸入 MySQL 的密碼在指令列上面)那麼很容易就被破解你的 Linux 主機!所以,最好是將記錄的指令數目減小一點較好

  • 命令與檔案補全功能:
  • 還記得我們在 首次進入 Linux 的熱門按鍵 一節當中提到的 [tab] 這個按鍵嗎?!這個按鍵的功能就是在 bash 裡頭才有的啦!常常在 bash 環境中使用 [tab] 是個很棒的習慣喔!因為至少可以讓你 1)少打很多字; 2)確定輸入的資料是正確的! 使用 [tab] 按鍵的時機依據 [tab] 接在指令後或參數後而有所不同。我們再複習一次:
    • [Tab] 接在一串指令的第一個字的後面,則為命令補全;
    • [Tab] 接在一串指令的第二個字以後時,則為『檔案補齊』!
    所以說,如果我想要知道我的環境中,所有可以執行的指令有幾個? 就直接在 bash 的提示字元後面輸入兩個 [tab][tab] 就能夠輸出所有的可執行指令了。 那如果想要知道系統當中所有以 c 為開頭的指令呢?就按下 c[tab][tab] 就好啦! ^_^

    是的!真的是很方便的功能,所以,有事沒事,在 bash shell 底下,多按幾次 [tab] 是一個不錯的習慣啦

  • 命令別名(alias)設定功能:
  • 假如我需要知道這個目錄底下的所有檔案(包含隱藏檔)及所有的檔案屬性,那麼我就必須要下達 ls -al 這樣的指令列,唉!真麻煩,有沒有更快的取代方式?呵呵!就使用命令別名呀!例如我最喜歡直接以 lm 這個自訂的命令來取代上面的命令,也就是說, lm 會等於 ls -al 這樣的一個功能,嘿!那麼要如何作呢?就使用 alias 即可!你可以在指令列輸入 alias 就可以知道目前的命令別名有哪些了!也可以直接下達命令來設定別名呦:
      alias lm='ls -al'
  • 工作控制(jobs)、前景背景控制:
  • 這部分我們在之後的資源管理章節中會再提及! 使用前、背景的控制可以讓工作進行的更為順利!至於工作控制(jobs)的用途則更廣, 可以讓我們隨時將工作丟到背景中執行!而不怕不小心使用了 [Ctrl] + c 來停掉該程序!真是好樣的!此外,也可以在單一登入的環境中,達到多工的目的呢!

  • Shell scripts 的強大功能:
  • 在 DOS 年代還記得將一堆指令寫在一起的所謂的『批次檔』吧?在 Linux 底下的 shell scripts 則發揮的更為強大的功能,可以將您日常生活當中常需要下達的連續指令寫成一個檔案, 該檔案並且可以透過對談互動式的方式來進行主機的偵測工作!也可以藉由 shell 提供的環境變數及相關指令來進行設計,哇!整個設計下來幾乎就是一個小型的程式語言了!該 scripts 的功能真的是超乎我的想像之外!以前在 DOS 底下需要程式語言才能寫的東西,在 Linux 底下使用簡單的 shell scripts 就可以幫你達成了!真的厲害!!這部分我們在後續章節再來談!

  • 萬用字元!
  • 除了完整的字串之外, bash 還支援許多的萬用字元來幫助使用者查詢與指令下達。 舉例來說,想要知道 /usr/X11R6/bin 底下有多少以 xt 為開頭的檔案嗎?使用: ls -l /usr/X11R6/bin/xt* 就能夠知道囉~此外,還有其他可供利用的萬用字元, 這些都能夠加快使用者的操作呢!

    Bash shell 的內建命令: type

    我們在首次進入 Linux 章節當中,提到關於 Linux 的線上說明文件 部分,也就是 man page 的內容,那麼 bash 有沒有什麼說明文件啊?開玩笑~ 這麼棒的東西怎麼可能沒有說明文件!請您在 shell 的環境下,直接輸入 man bash 瞧一瞧, 嘿嘿!不是蓋的吧!讓您看個幾天幾夜也無法看完的 bash 說明文件,可是很詳盡的資料啊! ^_^

    不過,在這個 man bash 所出現的 man page 當中,不知道您是否有察覺到,咦! 怎麼這個說明文件裡面有其他的檔案說明啊?舉例來說,那個 cd 指令的說明就在這個 man page 內? 然後我直接輸入 man cd 時,怎麼出現的畫面中,最上方竟然出現一堆指令的介紹??這是怎麼回事? 為了方便 shell 的操作,其實 bash 已經『內建』了很多指令了,例如上面提到的 cd , 還有例如 umask 等等的指令,都是內建在 bash 當中的呢!

    那我怎麼知道這個指令是來自於外部指令(指的是其他非 bash 套件所提供的指令) 或是內建在 bash 當中的呢? 嘿嘿!利用 type 這個指令來觀察即可!舉例來說:
    [root@linux ~]# type [-tpa] name
    參數:
        :不加任何參數時,則 type 會顯示出那個 name 是外部指令還是 bash 內建的指令!
    -t  :當加入 -t 參數時,type 會將 name 以底下這些字眼顯示出他的意義:
          file    :表示為外部指令;
          alias   :表示該指令為命令別名所設定的名稱;
          builtin :表示該指令為 bash 內建的指令功能;
    -p  :如果後面接的 name 為指令時,會顯示完整檔名(外部指令)或顯示為內建指令;
    -a  :會將由 PATH 變數定義的路徑中,將所有含有 name 的指令都列出來,包含 alias
    範例:
    範例一:查詢一下 ls 這個指令是否為 bash 內建?
    [root@linux ~]# type ls
    ls is aliased to `ls --color=tty'
    # 沒有加上任何參數,僅列出 ls 這個指令的最主要使用情況
    [root@linux ~]# type -t ls
    alias
    # -t 參數則僅列出 ls 這個指令的最主要使用情況說明
    [root@linux ~]# type -a ls
    ls is aliased to `ls --color=tty'
    ls is /bin/ls
    # 利用所有方法找出來的 ls 相關資訊都會被列出來!
    
    範例二:那麼 cd 呢?
    [root@linux ~]# type cd
    cd is a shell builtin
    
    透過 type 這個指令的用途,我們可以知道每個指令是否為 bash 的內建指令。 此外,由於利用 type 搜尋後面的名稱時,如果後面接的名稱並不能以執行檔的狀態被找到, 那麼該名稱是不會被顯示出來的。舉例來說,您的 FC4 應該不會有 vbird 這個指令吧?! 輸入 type -p vbird 看一下,果然沒有輸出任何資料!而如果您輸入的是 type -p touch 呢? 則會出現 /bin/touch !呵呵!所以,這個 type 也可以用來作為類似 which 指令的用途啦!找指令用的!

    指令的下達

    我們在 首次進入 Linux 一節當中,已經提到過在 shell 環境下的指令下達方式,不過,因為這個部分實在很重要,所以,我們還是再次的提醒一次!
    [root@linux ~]# command [-options] parameter1 parameter2 ...
                      指令     選項      參數(1)    參數(2)
    說明:
    0. 一行指令中第一個輸入的絕對是『指令(command)』或『可執行檔案』
    1. command 為指令的名稱,例如變換路徑的指令為 cd 等等;
    2. 中刮號[]並不存在於實際的指令中,而加入參數設定時,通常為 - 號,例如 -h;
       有時候完整參數名稱會輸入 -- 符號,例如 --help;
    3. parameter1 parameter2.. 為依附在 option 後面的參數,
       或者是 command 的參數; 
    4. command, -options, parameter1.. 這幾個咚咚中間以空格來區分,
       不論空幾格 shell 都視為一格; 
    5. 按下 [Enter] 按鍵後,該指令就立即執行。[Enter] 按鍵為 <CR> 字符,
       他代表著一行指令的開始啟動。
    6. 指令太長的時候,可以使用 \ 符號來跳脫 [Enter] 符號,
       使指令連續到下一行。注意! \ 後就立刻接特殊字符。
    7. 在 Linux 系統中,英文大小寫字母是不一樣的。舉例來說, cd 與 CD 並不同。
    範例:
    
    範例一:列出 /root 底下的各檔案名稱
    [root@linux ~]# ls -al /root
    [root@linux ~]# ls     -al      /root
    # 不論指令與參數中間空幾格,都是可以接受的!
    
    範例二:如果指令太長的話,如何使用兩行來輸出?
    [root@linux ~]# cp /var/spool/mail/root /etc/crontab \
    > /etc/fstab /root
    # 上面這個指令,就是將三個檔案複製到 /root 這個目錄下而已。不過,因為指令太長,
    # 於是鳥哥就利用 \[Enter] 來將 [Enter] 這個按鍵『跳脫!』開來,讓
    # [Enter] 按鍵不再具有上述說明的第 5 點功能!好讓指令繼續在下一行輸入。
    # 需要特別留意, [Enter] 按鍵是緊接著反斜線 (\) 的,兩者中間沒有其他字元。
    # 因為 \ 僅跳脫『緊接著的下一個字符』而已!所以,萬一我寫成:
    # \ [Enter] ,亦即 [Enter] 與反斜線中間有一個空格時,則 \ 跳脫的是『空白鍵』
    # 而不是 [Enter] 按鍵!這個地方請在仔細的看一遍!很重要!
    # 如果順利跳脫 [Enter] 後,下一行最前面就會主動出現 > 的符號,
    # 您可以繼續輸入指令囉!也就是說,那個 > 是系統自動出現的,你不需要輸入。
    
    總之,當我們順利的在終端機 (tty) 上面登入後, Linux 就會依據 /etc/passwd 檔案的設定給我們一個 shell ,預設就是 bash ,然後我們就可以依據上面的指令下達方式來操作 shell, 之後,我們就可以透過 man 這個線上查詢來查詢指令的使用方式與參數說明, 很不錯吧!那麼我們就趕緊更進一步來操作 bash 這個好玩的東西囉!

    Shell 的變數功能

    在繼續研究 BASH 之前,我們得要先就 變數 這個東西來討論一番。 為什麼要討論變數呢?又,變數是啥玩意兒啊?!先來談一談國中數學好了,您是否依稀記得, 我們國中時候學過所謂的『 y = ax + b 』這東西?其中, y 是變數, x 則是這個變數的內容啊! 講的更簡單一點,我們可以『用一個簡單的 "字眼" 來取代另一個比較複雜或者是容易變動的資料』。這有什麼好處啊?最大的好處就是『方便!』。

    如果以 Linux 主機的運作來說明好了,因為在主機裡面有太多的資料需要進行存取了, 而這些資料都是一些服務所必須的,例如某個名為 dmtsai 的帳號,他的 mail 的存取路徑預設是在 /var/spool/mail/dmtsai 、家目錄預設在 /home/dmtsai 等等。那如果換了另外一個帳號呢? 假設另一個帳號名稱為 vbird ,你猜他的郵件與家目錄在哪?應該是在 /var/spool/mail/vbird 與 /home/vbird 對吧! 那麼我們主機的郵件服務是否要記錄好幾個不同的路徑啊?會不會太麻煩?這當然很麻煩囉~ 所以為了簡化整個運作流程,我們就可以透過某個變數功能,讓這個變數可以依據不同的使用者而變更內容, 如此一來,系統的郵件服務只要依據那個變數去取得所需要的資料即可,就不需要記錄不同的路徑囉。

    舉例來說,我們每個帳號的郵件信箱預設是以 MAIL 這個變數來進行存取的, 當 dmtsai 這個使用者登入時,他便會取得 MAIL 這個變數,而這個變數的內容其實就是 /var/spool/mail/dmtsai, 那如果 vbird 登入呢?他取得的 MAIL 這個變數的內容其實就是 /var/spool/mail/vbird 。 而我們使用信件讀取指令 mail 來讀取自己的郵件信箱時,嘿嘿,這支程式可以直接讀取 MAIL 這個變數的內容, 就能夠自動的分辨出屬於自己的信箱信件囉!這樣一來,設計程式的設計師就真的很方便的啦!

    當然我們可以改變這些個變數,但是如果該變數是直接深植於套件當中, 那麼當你修改了某些參數之後,嘿嘿!你的套件就必須要『由原始碼直接更新再編譯』 才行!這樣似乎很麻煩,所以囉,變數真的是很方便的啦!
    Tips 鳥哥 舉個簡單的例子來說, sendmail 的 smtp 存放 mail 路徑是經由 /etc/profile 裡頭的:
      MAIL="/var/spool/mail/$USER"
    來設定的,而當我修改了上面這一個咚咚,然後重新開機之後,嘿嘿嘿嘿! 我的郵件就可以存放到不同的路徑去了!而且不會有問題!可以順利的『在 Linux 主機上面』收發。然而問題發生在 pop3 這個服務上面,由於 pop3 的預設路徑是在 source code 裡頭,而且就正是 /var/spool/mail 這個路徑,也就是說,不論我怎麼修正我的『變數』, pop3 都不為所動!唉~真慘,所以就無法直接以 pop3 來收信了(例如 OutLook 就不能工作了)!會發生密碼不接受的問題呢!
    再來繼續講到其他的變數功能好了,我們前面已經提到過很多次,能不能執行某個指令, 與 PATH 這個變數也有很大的關係的。舉例來說,我們在任何地方下達 ls 這個指令時,系統就是透過 PATH 這個變數裡面的內容所記錄的路徑順序來搜尋指令的呢!如果在搜尋完 PATH 變數內的路徑還找不到 ls 這個指令時, 就會在螢幕上顯示『 command not found 』的錯誤訊息了。

    這些還都只是系統預設的變數的目的,如果是個人的設定方面的應用呢:例如你要寫一個大型的 script (批次檔)時,有些資料因為可能由於使用者習慣的不同而有差異,比如說路徑好了,由於該路徑在 script 被使用在相當多的地方,如果下次換了一部主機,都要修改 script 裡面的所有路徑,那麼我一定會瘋掉! 這個時候如果使用變數,而將該變數的定義寫在最前面,後面相關的路徑名稱都以變數來取代, 嘿嘿!那麼你只要修改一行就等於修改整篇 script 了!方便的很!所以,良好的程式設計師都會善用變數的定義! ( 這個部分我們在後續的 shell script 再次提及的!)

    如果說的學理一點,那麼由於在 Linux System 下面,所有的執行續都是需要一個執行碼, 而就如同上面提到的,你『真正以 shell 來跟 Linux 溝通,是在正確的登入 Linux 之後!』這個時候你就有一個 bash 的執行程序,也才可以真正的經由 bash 來跟系統溝通囉!而在進入 shell 之前,也正如同上面提到的,由於系統需要一些變數來提供他資料的存取(或者是一些環境的設定參數值, 例如是否要顯示彩色等等的),所以就有一些所謂的『環境變數』 需要來讀入系統中了!這些環境變數例如 PATH、HOME、MAIL、SHELL 等等,都是很重要的, 為了區別與自訂變數的不同,環境變數通常以大寫字元來表示呢!

    好了,那麼我們就簡單的來對『什麼是變數』作個簡單的定義好了: 『變數就是以一組文字或符號等,來取代一些設定或者是一串保留的資料!』, 例如:我設定了『myname』就是『VBird』,所以當你讀取 myname 這個變數的時候,系統自然就會知道!哈!那就是 VBird 啦!最簡單的例子可以取 PATH 來說明!如果你對於『相對路徑與絕對路徑』還有點印象的話, 那麼應該曉得『要下達正確的指令,應該需要指定路徑與檔名』才行!例如你的 ls 指令應該需要以『/bin/ls』來下達指令才對,那麼為何你在任意的路徑下都可以執行 ls 呢?而不需要指定路徑呢?這是因為系統已經預設了一些『搜尋路徑(PATH)』了, 所以當你需要執行一些指令的時候,系統就會依照該 PATH 的設定來進行指令的搜尋!而這個 PATH 就是所謂的變數了!

    那麼如何『顯示變數』呢?這就需要使用到 echo 這個指令啦!

    變數的取用與設定:echo, 變數設定規則, unset

    說的口沫橫飛的,也不知道『變數』與『變數代表的內容』有啥關係? 當然啦,那我們就將『變數』的『內容』拿出來給您瞧瞧就好了。利用 echo 這個指令來取用變數, 但是,變數在被取用時,前面必須要加上 $ 才行,舉例來說,要知道 PATH 的內容,該如何是好?
    [root@linux ~]# echo $variable
    [root@linux ~]# echo $PATH
    /bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin
    [root@linux ~]# echo ${PATH}
    
    變數的取用就如同上面的範例,利用 ehco 就能夠讀出,只是需要在變數名稱前面加上 $ , 或者是以 ${variable} 的方式來取用都可以!當然啦,那個 echo 的功能可是很多的, 我們這裡單純是拿 echo 來讀出變數的內容而已,更多的 echo 使用,請自行給他 man echo 吧! ^_^

    例題一:請在螢幕上面顯示出您的環境變數 HOME 與 MAIL:
    答:
      echo $HOME
      echo $MAIL

    OK!現在我們知道了變數與變數內的之間的相關性了,好了,那麼我要如何『設定』或者是『修改』 某個變數的內容啊?!很簡單啦!用『等號(=)』連接變數與他的內容就好啦!舉例來說: 我要將 myname 這個變數名稱的內容設定為 VBird ,那麼:
    [root@linux ~]# echo $myname
       <==這裡並沒有任何資料~因為這個變數尚未被設定!是空的!
    [root@linux ~]# myname=VBird
    [root@linux ~]# echo $myname
    VBird  <==出現了!因為這個變數已經被設定了!
    
    瞧!如此一來,這個變數名稱 myname 的內容就帶有 VBird 這個資料囉~ 而由上面的例子當中,我們也可以知道: 當一個變數名稱尚未被設定時,預設的內容是『空』的另外,變數在設定時,還是需要符合某些規定的,否則會設定失敗喔! 這些規則如下所示啊!
    1. 變數與變數內容以等號『=』來連結;
    2. 等號兩邊不能直接接空白字元;
    3. 變數名稱只能是英文字母與數字,但是數字不能是開頭字元;
    4. 若有空白字元可以使用雙引號『 " 』或單引號『 ' 』來將變數內容結合起來,但須要特別留意, 雙引號內的特殊字元可以保有變數特性,但是單引號內的特殊字元則僅為一般字元;
    5. 必要時需要以跳脫字元『 \ 』來將特殊符號 ( 如 Enter, $, \, 空白字元, ' 等 ) 變成一般符號;
    6. 在一串指令中,還需要藉由其他的指令提供的資訊,可以使用 quote 『 ` command` 』;(特別特別注意,那個 ` 是鍵盤上方的數字鍵 1 左邊那個按鍵,而不是單引號!)
    7. 若該變數為擴增變數內容時,則需以雙引號及 $變數名稱 如:『 "$PATH":/home』繼續累加內容;
    8. 若該變數需要在其他子程序執行,則需要以 export 來使變數變成環境變數, 如『export PATH』;
    9. 通常大寫字元為系統預設變數,自行設定變數可以使用小寫字元,方便判斷 ( 純粹依照使用者興趣與嗜好 ) ;
    10. 取消變數的方法為:『unset 變數名稱』。
    底下我們舉幾個例子來讓您試看看,就知道怎麼設定好您的變數囉!
    範例一:設定一變數 name ,且內容為 VBird 。
    [root@linux ~]# 12name=VBird
    -bash: 12name=VBird: command not found  <==螢幕會顯示錯誤!因為不能以數字開頭!
    [root@linux ~]# name = VBird  <==還是錯誤!因為有空白!
    [root@linux ~]# name=VBird    <==OK 的啦!
    
    範例二:承上題,若變數內容為 VBird's name 呢?
    [root@linux ~]# name=VBird's name  
    # 因為單引號可以將 Enter 這個特殊字符取消,所以,您可以繼續在下一行輸入內容~
    # 不過,這與我們要達到的功能不同,所以,算是失敗的啦!
    [root@linux ~]# name="VBird's name"  <==OK 的啦!
    [root@linux ~]# name=VBird\'s\ name
    # 利用反斜線 (\) 跳脫特殊字元,例如單引號與空白鍵,這也是 OK 的啦!
    
    範例三:我要在 PATH 這個變數當中『累加』:/home/dmtsai/bin 這個目錄
    [root@linux ~]# PATH=$PATH:/home/dmtsai/bin
    [root@linux ~]# PATH="$PATH":/home/dmtsai/bin
    # 上面這兩種格式在 PATH 裡頭的設定都是 OK 的!但是底下的例子就不見得囉!
    
    範例四:呈範例三,我要將 name 的內容多出 "yes" 呢?
    [root@linux ~]# name=$nameyes  
    # 知道了吧?如果沒有雙引號,那麼變數成了啥?name 的內容是 $nameyes 這個變數!
    # 呵呵!我們可沒有設定過 nameyes 這個變數吶!所以,應該是底下這樣才對!
    [root@linux ~]# name="$name"yes
    [root@linux ~]# name=${name}yes
    
    範例五:如何讓我剛剛設定的 name=VBird 可以用在下個 shell 的程序?
    [root@linux ~]# name=VBird
    [root@linux ~]# bash        <==進入到所謂的子程序
    [root@linux ~]# echo $name  <==嘿嘿!並沒有剛剛設定的內容喔!
    [root@linux ~]# exit        <==離開剛剛的子程序
    [root@linux ~]# export name
    [root@linux ~]# bash        <==進入到所謂的子程序
    [root@linux ~]# echo $name  <==出現了設定值了!
    [root@linux ~]# exit        <==離開剛剛的子程序
    # 什麼是『子程序』呢?就是說,在我目前這個 shell 的情況下,
    # 去啟用另一個新的 shell ,新的那個 shell 就是子程序啦!在一般的狀態下,
    # 父程序的自訂變數是無法在子程序內使用的。但是透過 export 將變數變成
    # 環境變數後,就能夠在子程序底下應用了!很不賴吧!至於程序的相關概念,
    # 我們會在『程序與資源管理』章節當中提到的喔!
    
    範例六:如何進入到您目前核心的模組目錄?
    [root@linux ~]# cd /lib/modules/`uname -r`/kernel
    # 每個作業系統核心版本都不相同,以 FC4 為例,他的預設核心版本是 
    # 2.6.11-1.1369_FC4 所以,他的模組目錄在 /lib/modules/2.6.11-1.1369_FC4/kernel 。
    # 因為每個 distributions 的這個值都不相同,但是我們卻可以利用 uname -r 這個指令
    # 先取得版本資訊,所以囉,就可以透過上面指令當中的內含指令 `uname -r` 
    # 先取得版本輸出到 cd .. 那個指令當中,就能夠順利的進入目前核心的驅動程式所放置
    # 的目錄囉!很方便吧!
    
    範例七:取消剛剛設定的 name 這個變數內容
    [root@linux ~]# unset name
    
    根據上面的案例你可以試試看!就可以瞭解變數的設定囉!這個是很重要的呦!請勤加練習!! 其中,較為重要的一些特殊符號的使用囉!例如單引號、雙引號、跳脫字元、錢字號、quote 符號等等,底下的例題想一想吧!

    例題二:在變數的設定當中,單引號與雙引號的用途有何不同?
    答:
      單引號與雙引號的最大不同在於雙引號仍然可以保有變數的內容,但單引號內僅能是一般字元 ,而不會有特殊符號。我們以底下的例子做說明:假設您定義了一個變數, name=VBird ,現在想以 name 這個變數的內容定義出 myname 顯示 VBird its me 這個內容,要如何訂定呢?

        [root@linux ~]# name=VBird
        [root@linux ~]# echo $name
        VBird
        [root@linux ~]# myname="$name its me"
        [root@linux ~]# echo $myname
        VBird its me
        [root@linux ~]# myname='$name its me'
        [root@linux ~]# echo $myname
        $name its me

      發現了嗎?沒錯!使用了單引號的時候,那麼 $name 將失去原有的變數內容, 僅為一般字元的顯示型態而已!這裡必需要特別小心在意!

    例題三:在指令下達的過程中, quote ( ` ) 這個符號代表的意義為何?
    答:
      在一串指令中,在 ` 之內的指令將會被先執行,而其執行出來的結果將做為外部的輸入資訊!例如 uname -r 會顯示出目前的核心版本,而我們的核心版本在 /lib/modules 裡面,因此,你可以先執行 uname -r 找出核心版本,然後再以『 cd 目錄』到該目錄下,當然也可以執行如同上面範例六的執行內容囉。

      另外再舉個例子,我們也知道, locate 指令可以列出所有的相關檔案檔名,但是, 如果我想要知道各個檔案的權限呢?舉例來說,我想要知道每個 crontab 相關檔名的權限:

        [root@linux ~]# ls -l `locate crontab`

      如此一來,先以 locate 將檔名資料都列出來,再以 ls 指令來處理的意思啦!瞭了嗎? ^_^

    變數的用途

    我們知道 PATH 這個變數是我們在執行指令的時候,所需要具備的指令搜尋目錄資料, 沒有他,我們就得要使用絕對路徑來下達指令才行。當然,還有很多變數都有他特別的意義存在。 除此之外,『我為何需要設定變數』呢? 要跟大家介紹這個『變數』,當然是因為他有相當程度的意義存在的啊! 底下就跟大家介紹一下,鳥哥設定變數的時機喔!
      我的案例一:最簡單的例子就是 『簡化路徑名稱』囉!以鳥哥為例,我的工作在 Unix 系統之下進行一些數值模式的模擬工作,偏偏由於資料量太大, 為了怕日後忘記這個目錄的內容與主要的意義,所以我的檔名都取的很長, 偏偏在執行模式的過程中,常常會切換目錄!我哩ㄌㄟ,光是打那幾行路徑名稱就快要瘋掉了! 所以我就設定那幾行目錄名稱成為一個四個字元的變數,如此一來我只要輸入『 cd $VARI 』這個指令,嘿嘿!馬上就移動到該路徑下了!很方便吧!當然變數的意義還不止於此, 不過這是最簡單的實例說明囉!
      我的案例二:另外一個常常需要變數的咚咚是在 scripts 裡面,例如我寫的一個偵測登錄檔的小程式 logfile.sh 這個咚咚, 由於裡頭常常需要用到『儲存路徑』,偏偏可能每個人的存取路徑都不太一樣, 而如果要修改存取路徑的話,嘿嘿!好幾十行要同時修改呢!還可能會改錯! 那麼我只要定義一個變數,然後後續的所有資料都使用這個變數的內容!嘿嘿! 那麼只要大家修改了這個變數的內容(只要一行),後續的動作就不需要修正了!這個動作常在程式或者是 script 當中看到的!
    所以囉,有很多的時候為了方便或者是使用於 scripts 的意義,我們必須要設定變數! 當然囉,如果是跟系統終端機環境有關的設定值,很多也是利用變數來幫助達成的~ 底下我們就來談一談所謂的『環境變數』吧!

    環境變數的功能

    環境變數可以幫我們達到很多功能~包括家目錄的變換啊、提示字元的顯示啊、執行檔搜尋的路徑啊等等的, 還有很多很多啦!那麼,既然環境變數有那麼多的功能,問一下,目前我的 shell 環境中, 有多少變數啊?!呵呵!我們可以利用兩個指令來查閱,分別是 env 與 export 呢!


  • 一些環境變數的說明: env
  • 範例一:列出目前的 shell 環境下的所有環境變數與其內容。
    [root@linux ~]# env
    HOSTNAME=linux.dmtsai.tw   <== 這部主機的主機名稱
    SHELL=/bin/bash            <== 目前這個環境下,使用的 Shell 是哪一個程式?
    TERM=xterm                 <== 這個終端機使用的環境是什麼類型
    HISTSIZE=1000              <== 這個就是『記錄指令的筆數』在 FC4 預設可記錄 1000 筆
    USER=root                  <== 使用者的名稱啊!
    LS_COLORS=no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:
    or=01;05;37;41:mi=01;05;37;41:ex=00;32:*.cmd=00;32:*.exe=00;32:*.com=00;32:*.btm=0
    0;32:*.bat=00;32:*.sh=00;32:*.csh=00;32:*.tar=00;31:*.tgz=00;31:*.arj=00;31:*.taz=
    00;31:*.lzh=00;31:*.zip=00;31:*.z=00;31:*.Z=00;31:*.gz=00;31:*.bz2=00;31:*.bz=00;3
    1:*.tz=00;31:*.rpm=00;31:*.cpio=00;31:*.jpg=00;35:*.gif=00;35:*.bmp=00;35:*.xbm=00
    ;35:*.xpm=00;35:*.png=00;35:*.tif=00;35: <== 一些顏色顯示
    ENV=/root/.bashrc          <== 使用的個人環境設定檔
    MAIL=/var/spool/mail/root  <== 這個使用者所取用的 mailbox 位置
    PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin:
    /root/bin                  <== 不再多講啊!是執行檔指令搜尋路徑
    INPUTRC=/etc/inputrc       <== 與鍵盤按鍵功能有關。可以設定特殊按鍵!
    PWD=/root                  <== 目前使用者所在的工作目錄 (利用 pwd 取出!)
    LANG=en_US.UTF-8           <== 這個與語系有關,底下會再介紹!
    HOME=/root                 <== 這個使用者的家目錄啊!
    _=/bin/env                 <== 上一次使用的指令的最後一個參數(或指令本身)
    
    env 是 environment (環境) 的簡寫啊~ 上面的例子當中,是列出來所有的環境變數。當然,如果使用 export 也會是一樣的內容~ 只不過, export 還有其他額外的功能就是了,我們等一下再提這個 export 指令。 那麼上面這些變數有些什麼功用呢?底下我們就一個一個來分析分析!
    • HOME : 代表使用者的家目錄。還記得我們可以使用 cd ~ 去到使用者的家目錄嗎?或者利用 cd 就可以直接回到使用者家目錄了。那就是取用這個功能啦~ 有很多程式都可能會取用到這個變數的值喔!

    • SHELL : 告知我們,目前這個環境使用的 SHELL 是哪支程式? 如果是 bash 的話,預設是 /bin/bash 的啦!

    • HISTSIZE : 這個與『歷史命令』有關,亦即是, 我們曾經下達過的指令可以被系統記錄下來,而記錄的『筆數』則是由這個值來設定的。

    • ENV : 這個使用者所使用的個人化環境設定檔的讀取檔案。

    • MAIL : 當我們使用 mail 這個指令在收信時,系統會去讀取的郵件信箱檔案 (mailbox)。

    • PATH : 就是執行檔搜尋的路徑啦~目錄與目錄中間以冒號(:)分隔, 由於檔案的搜尋是依序由 PATH 的變數內的目錄來查詢,所以,目錄的順序也是重要的喔。

    • LANG : 這個重要!就是語系檔案囉~很多資料都會用到他, 舉例來說,當我們在啟動某些 perl 的程式語言檔案時,他會主動的去分析語系資料檔案, 如果發現有他無法解析的編碼語系,可能會產生錯誤喔!一般來說,我們中文編碼通常是 zh_TW.Big5 或者是 zh_TW.UTF-8,這兩個編碼偏偏不容易被解譯出來,所以,有的時候,可能需要修訂一下語系資料。 這部分我們會在下個小節做介紹的!

    • RANDOM : 這個玩意兒就是『隨機亂數』的變數啦!目前大多數的 distributions 都會有亂數產生器,那就是 /dev/random 這個檔案。 我們可以透過這個亂數檔案相關的變數 ($RANDOM) 來隨機取得亂數值喔。在 BASH 的環境下,這個 RANDOM 變數的內容,介於 0~32767 之間,所以,你只要 echo $RANDOM 時,系統就會主動的隨機取出一個介於 0~32767 的數值。萬一我想要使用 0~9 之間的數值呢?呵呵~利用 declare 宣告數值類型, 然後這樣做就可以了:
      [root@linux ~]# declare -i number=$RANDOM*10/32767 ; echo $number
      8   <== 此時會隨機取出 0~9 之間的數值喔!
      
    大致上是有這些環境變數啦~裡面有些比較重要的參數,在底下我們都會另外進行一些說明的~

  • 其他所有的變數說明: set
  • 而除了這些環境變數之外,還有沒有什麼重要的變數呢?當然有啊! 我們在 bash 的環境下,其實還有一些挺重要的變數,這些變數是『在這個 shell 環境下有效』的, 如果是在『子程序』,這些變數值就不會相同了。 那麼如何觀察目前 shell 環境下的所有變數呢?很簡單啊,就用 set 即可!set 這個指令除了會將環境變數列出來之外,其他我們的自訂變數,與所有的變數,都會被列出來喔!資訊多好多。 底下僅列出幾個重要的內容。
    [root@linux ~]# set
    BASH=/bin/bash           <== bash 的主程式放置路徑
    BASH_VERSINFO=([0]="3" [1]="00" [2]="16" [3]="1" [4]="release" 
    [5]="i386-redhat-linux-gnu")      <== bash 的版本啊!
    BASH_VERSION='3.00.16(1)-release' <== bash 的版本啊!
    COLORS=/etc/DIR_COLORS.xterm      <== 使用的顏色紀錄檔案
    COLUMNS=115              <== 在目前的終端機環境下,使用的欄位有幾個字元長度
    HISTFILE=/root/.bash_history      <== 歷史命令記錄的放置檔案,隱藏檔
    HISTFILESIZE=1000        <== 存起來(與上個變數有關)的檔案之指令的最大紀錄筆數。
    HISTSIZE=1000            <== 目前環境下,可記錄的歷史命令最大筆數。
    HOSTTYPE=i386            <== 主機安裝的軟體主要類型。我們用的是 i386 相容機器軟體
    IFS=$' \t\n'             <== 預設的分隔符號
    LINES=35                 <== 目前的終端機下的最大行數
    MACHTYPE=i386-redhat-linux-gnu    <== 安裝的機器類型
    MAILCHECK=60             <== 與郵件有關。每 60 秒去掃瞄一次信箱有無新信!
    OLDPWD=/home             <== 上個工作目錄。我們可以用 cd - 來取用這個變數。
    OSTYPE=linux-gnu         <== 作業系統的類型!
    PPID=20046               <== 父程序的 PID (會在後續章節才介紹)
    PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}\007"'
                             <== 上面這個是命令提示字元!與底下也有關。
    PS1='[\u@\h \W]\$ '      <== PS1 就厲害了。這個是命令提示字元,也就是我們常見的
                                 [root@linux ~]# 或 [dmtsai ~]$ 的設定值啦!可以更動的!
    RANDOM=13586             <== 亂數啊!上面已經提過囉~
    SUPPORTED=zh_TW.UTF-8:zh_TW:zh:en_US.UTF-8 <== 本系統所支援的語系
    name=VBird               <== 剛剛設定的自訂變數也可以被列出來喔!
    $                        <== 目前這個 shell 所使用的 PID
    ?                        <== 剛剛執行完指令的回傳值。
    
    一般來說,不論是否為環境變數,只要跟我們目前這個 shell 的操作介面有關的變數, 通常都會被設定為大寫字元,也就是說,『基本上,在 Linux 預設的情況中,使用{大寫的字母}來設定的變數一般為系統內定需要的變數』。

    使用 set 除了會將系統的預設值秀出來之外,連帶的所有的你自己設定的變數也會被秀出來! 同時需要注意的是,若當時有相當多人同時在線上的話,那麼 你的變數只能給自己使用 ( 除非改的是系統的預設參數檔,如 /etc/profile ),而不會干擾到別人的!就如同前面所說的, 由於你登入 Linux 之後會取得一個 PID ,而你的設定將只對這個 PID 與子程序有關!此外, 這次登入所進行的變數設定,如果沒有更動到設定檔, 那麼這次設定的變數在下次登入時將被取消掉 ( 因為程序 PID 不見囉! ) !所以囉, 如果你想要你的變數每次都能在你登入的時候自動就設定好了,那麼就必須將你的設定寫入登入時載入的設定檔! ( 更多的程序相關的說明,不要急~我們會在後面的 程序與資源管理 當中好好的提一提的! )

    OK!OK!那麼上頭那些變數當中,有哪些是比較重要的?大概有這幾個吧!
    • PS1:(提示字元的設定)

      這是 PS1 (數字的 1 不是英文字母!),這個東西就是我們的『命令提示字元』啊! 當我們每次按下 [Enter] 按鍵去執行某個指令後,最後要再次出現提示字元時, 就會主動去讀取這個變數值了。上頭 PS1 內顯示的是一些特殊符號,每個版本 bash 的 PSI 變數內的特殊符號可能有些許的差異, 你應該主動的以 man bash 去查詢一下相關的變數。底下我列出 FC4 的環境下, 預設的 bash 的 PS1 變數內的特殊符號代表意義:

      • \d :代表日期,格式為 Weekday Month Date,例如 "Mon Aug 1"
      • \H :完整的主機名稱。舉例來說,鳥哥的練習機 linux.dmtsai.tw ,那麼這個主機名稱就是 linux.dmtsai.tw
      • \h :僅取主機名稱的第一個名字。以上述來講,就是 linux 而已, .dmtsai.tw 被省略。
      • \t :顯示時間,為 24 小時格式,如: HH:MM:SS
      • \T :顯示時間,12 小時的時間格式!
      • \A :顯示時間,24 小時格式, HH:MM
      • \u :目前使用者的帳號名稱;
      • \v :BASH 的版本資訊;
      • \w :完整的工作目錄名稱。家目錄會以 ~ 取代;
      • \W :利用 basename 取得工作目錄名稱,所以僅會列出最後一個目錄名。
      • \# :下達的第幾個指令。
      • \$ :提示字元,如果是 root 時,提示字元為 # ,否則就是 $ 囉~

      OK!所以,由預設的 PS1 內容為: '\[\u@\h \W\]\$ ' 就可以瞭解為何我們的提示字元會是: [root@linux ~]# 了吧!好了,那麼假設我想要有類似底下的提示字元:

        [root@linux /home/dmtsai 16:50 #12]#

      ,那個 # 代表第 12 次下達的指令。 那麼應該如何設定 PS1 呢?可以這樣啊:
      [root@linux home]# PS1='[\u@\h \w \A #\#]\$ '
      [root@linux /home 17:02 #85]# 
      # 看到了嗎?提示字元變了!變的很有趣吧!其中,那個 #85 比較有趣,
      # 如果您按下 [Enter] 後,該數字就會增加喔!為啥?上面有說明ㄇㄟ!
      

    • $:(關於本 shell 的 PID)

      其實這個咚咚代表的是『目前這個 Shell 的執行緒代號』,亦即是所謂的 PID (Process ID)。 更多的程序觀念,我們會在第四章的時候提及。想要知道我們的 shell 的 PID ,就可以: echo $$ 即可!

    • ?:(關於上個執行指令的回傳碼)

      蝦密?問號也是一個特殊的變數?沒錯!在 bash 裡面這個變數可重要的很! 這個變數是:『上個執行的指令所回傳的值』, 上面這句話的重點是『上一個指令』與『回傳值』兩個地方。當我們執行某些指令時, 這些指令都會回傳一個執行後的代碼。一般來說,如果成功的執行該指令, 則會回傳一個 0 值,如果執行過程發生錯誤,就會回傳『錯誤代碼』才對!一般就是以非為 0 的數值來取代。 我們以底下的例子來看看:
      [root@linux ~]# echo $SHELL
      /bin/bash
      [root@linux ~]# echo $?
      0
      # 因為上個指令執行過程中,並沒有錯誤,為成功的執行完畢,所以回傳 0 。
      [root@linux ~]# 12name=VBird
      -bash: 12name=VBird: command not found
      [root@linux ~]# echo $?
      127
      # 發生錯誤啦!所以 echo $? 時,就會出現錯誤的代碼!
      # 我們可以利用這個代碼來搜尋錯誤的原因喔!
      [root@linux ~]# echo $?
      0
      # 咦!怎麼又變成正確了?這是因為 "?" 只與『上一個執行指令』有關,
      # 所以,我們上一個指令是執行『 echo $? 』,當然沒有錯誤,所以是 0 沒錯!
      

    • OSTYPE, HOSTTYPE, MACHTYPE:(主機硬體與核心的等級)

      這幾個東西與程式的安裝有關。我們在『Linux 主機規劃』 裡面提到過關於主機的等級方面的問題,當我們在安裝軟體的時候, 需要透過編譯器來將原始碼編譯成為二進位的檔案 (binary file)。但是, 我們可以針對硬體的配備來進行編譯的最佳化,此時,這些參數就可以被用到了! 基本上,目前主要的 distribution 都是針對 i386 亦即最低等級的機器進行最佳化, 這樣才能夠安裝在較高階的機器上,如果以 686 的機型來最佳化, 那麼,可就無法向下相容的喔!(早期的 OpenLinux 是針對 686 機器來釋出軟體, 所以,當時的 OpenLinux 是無法安裝在 P-166 的機器上的。 )

    自訂變數轉成環境變數: export
    好了,上面我們環境變數也提過了,一些自訂變數也提過了,那麼,這兩者有啥不同? 他的不同處,我們在 變數設定規則 當中稍微提過, 主要是由於變數可否被子程序所引用。

    當你取得一個 bash 之後,亦即得到了一個程序了,但是若你再次的執行一次 bash ,那麼你將進入『子程序』,這個程序的概念我們在資源管理章節中再詳談,這裡您先有個概念即可。 那麼由於您已經進入了該子程序,所以在父程序中的自訂變數設定將不再繼續的存在。 會存在子程序中的,僅有『環境變數』

    換個角度來想,也就是說,如果我能將自訂變數變成環境變數的話,那不就可以讓該變數值繼續存在於子程序了? 呵呵!沒錯!此時,那個 export 指令就很有用啦! 如您想要讓該變數內容繼續的在子程序中使用,那麼就請執行:
      export 變數
    這個東西用在『引用他人的檔案或者其他程序』時,相當的重要的! 尤其像鳥哥常常兩三個檔案互相引用來引用去的,如果忘記設定 export 的話,那麼不同的檔案中的相同變數值,將需要一再地重複設定才行!所以,我只要在頭一個檔案使用 export 的話,那麼後續的檔案引用時,將會把該變數內容讀進來!好用的很,如果僅下達 export 而沒有接變數時,那麼此時將會把所有的『環境變數』秀出來喔!例如:
    [root@linux ~]# export
    declare -x ENV="/root/.bashrc"
    declare -x HISTSIZE="1000"
    declare -x HOME="/root"
    declare -x HOSTNAME="linux.dmtsai.tw"
    declare -x INPUTRC="/etc/inputrc"
    declare -x LANG="en_US.UTF-8"
    declare -x MAIL="/var/spool/mail/root"
    declare -x SHELL="/bin/bash"
    # 很多都直接省略了!不然....重複性太高,浪費版面~ ^_^
    

    語系檔案的變數 (locale)

    還記得我們在首次進入 Linux 那個章節裡面提到的,關於語系編碼的問題嗎? 就是當我們使用 man command 的方式去查詢某個資料的說明檔時,該說明檔的內容可能會因為我們使用的語系, 而產生一些亂碼。另外,利用 ls 查詢檔案的時間時,也可能會有亂碼出現在時間的部分。 那個問題其實就是語系的問題啦。

    目前大多數的 Linux distributions 已經都是支援萬國碼,此外,也都支援大部分的語言語系了。 這有賴於 i18n 支援的幫助呢! 那麼我們的 Linux 到底支援了多少的語系呢?這可以由 locale 這個指令來查詢到喔!
    [root@linux ~]# locale -a
    aa_DJ
    aa_DJ.iso88591
    en_US
    en_US.iso88591
    en_US.iso885915
    en_US.utf8
    zh_TW
    zh_TW.big5
    zh_TW.euctw
    zh_TW.utf8
    # 其實輸出的內容有很多,鳥哥將一些資訊捨棄了~
    # 從上面的輸出中,我們也不難看出,系統是有支援 big5, utf8 等中文語系資料的!
    
    中文語系至少支援了兩種以上的編碼,一種是目前還是很常見的 big5 ,另一種則是越來越熱門的 utf-8 編碼。 那麼我們如何修訂這些編碼呢?其實可以透過底下這些變數的說:
    [root@linux ~]# LANG         <==主語言的環境
    [root@linux ~]# LC_CTYPE     <==字元辨識的編碼
    [root@linux ~]# LC_NUMERIC   <==數字系統的顯示訊息
    [root@linux ~]# LC_TIME      <==時間系統的顯示資料
    [root@linux ~]# LC_COLLATE   <==字串的比較與排序等
    [root@linux ~]# LC_MONETARY  <==幣值格式的顯示等
    [root@linux ~]# LC_MESSAGES  <==訊息顯示的內容,如功能表、錯誤訊息等
    [root@linux ~]# LC_ALL       <==語言環境的整體設定。
    
    基本上,你可以逐一設定每個與語系有關的變數資料,但事實上,如果其他的語系變數都未設定, 且您有設定 LANG 或者是 LC_ALL 時,則其他的語系變數就會被這兩個變數所取代! 這也是為什麼我們在 FC4 當中,通常僅設定 LANG 這個變數而已!因為他是最主要的設定變數。 好了,那麼你應該要覺得奇怪的是,為什麼在 Linux 主機的終端機介面 (tty1 ~ tty6) 的環境下,如果 LANG=zh_TW.big5 這個設定值生效後,使用 man 或者其他訊息輸出時, 都會有一堆亂碼,尤其是使用 ls -l 這個參數時?

    因為在 Linux 主機的終端機介面下,那個環境是無法顯示像中文這麼複雜的編碼的文字, 所以,就會產生亂碼了。也就是如此,所以,我們才會必須要在 tty1 ~ tty6 的環境下, 加裝一些中文化介面的軟體,才能夠看到中文啊!不過,如果您是在 Windows 主機以遠端連線伺服器的軟體連線到主機的話,那麼,嘿嘿!其實文字介面確實是可以看到中文的。 所以,此時反而您得要在 LANG 設定中文編碼才好呢!

    無論如何,如果發生一些亂碼的問題,那麼設定系統裡面保有的語系編碼, 例如: en_US 或 en_US.utf8 等等的設定,應該就 OK 的啦!好了,那麼系統預設支援多少種語系呢? 當我們使用 locale 時,系統是列出目前 Linux 主機內保有的語系檔案, 這些語系檔案都放置在: /usr/lib/locale/ 這個目錄中。 但是,目前的這個 shell 環境所支援的語系,則是要看 SUPPORTED 這個變數才對喔!

    那麼,如果我想要修訂系統的語系支援呢?可以修訂 /etc/sysconfig/i18n 這個檔案呢! 這個檔案的內容有點像這樣:
    [root@linux ~]# vi /etc/sysconfig/i18n
    LANG="en_US.UTF-8"
    SYSFONT="latarcyrheb-sun16"
    SUPPORTED="zh_TW.UTF-8:zh_TW:zh:en_US.UTF-8"
    
    你可以在這個檔案當中加入 LC_TIME 或者其他語系相關變數的設定內容, 也可以直接修改 LANG 那個變數即可啊! ^_^ 但,事實上,我們還可以透過個人的環境設定檔來設定 LANG 呢! 如此一來,則不必修訂系統的語系檔案,比較安全啦!
    Tips 鳥哥 假設你用 vi 編輯一個純文字檔,這個純文字檔在編輯的時候,是在 Windows 上面編輯的, 那麼這個檔案的預設編碼應該是以 zh_TW.big5 所編輯的才對。但是,如果你的 shell 環境中, 卻是使用 LANG=en_US 時,則當你編輯該檔案時,就可能會看到『亂碼』, 或者輸入的中文可能會變成『亂碼』了。此時,只要你離開 vi ,然後執行 LANG=zh_TW.big5 , 然後再重新以 vi 編輯該檔案,呵呵!應該就能夠看到中文啦!但是請注意, 這個方法當然不適用 tty1 ~ tty6 的環境,原因上面已經提過囉~ 僅適合以類似 putty 軟體由 Windows 電腦連線到 linux 主機上的做業!

    變數的有效範圍

    蝦密??變數也有使用的『範圍』?沒錯啊~我們在上頭的 export 指令說明中,就提到了這個概念了。如果在跑程式的時候,有父程序與子程序的不同程序關係時, 則『變數』可否被引用是 export 有關。被 export 後的變數,我們可以稱他為『環境變數』! 環境變數可以被子程序所引用,但是其他的自訂變數內容就不會存在於子程序中。也就是說: 我們自行設定的變數,只在目前這個 shell 環境當中存在, 在子程序中將不會存在此一變數。除非使用 export 將自訂變數變成環境變數。

    其實除了 shell 的父、子程序外,在腳本( scripts )的編寫當中,由於有的軟體會使用到 2 個以上的 scripts 做為一個完整的套件!也就是說,假如你有兩支程式,一支為 scripts1.sh 以及 scripts2.sh ,而 scripts2.sh 會去引用 scripts1.sh 的變數,這個時候,嘿嘿!你在 scripts1.sh 當中設定的變數請『千萬記得以 export 設定』, 否則你的變數將無法在兩個 scripts 之間互相被引用喔!當這個 scripts 執行完畢之後,剛剛在 scripts 當中設定的變數也就『失效了!』。

    其實,要瞭解不同程序之間變數的變換,應該要先瞭解『程序』的概念比較好, 但是我們還沒有講到.....沒關係~等你念到程序章節後,還可以再回來好好的看一看。 基本上,環境變數可以讓子程序繼續引用的原因,是因為:
    • 當啟動一個 shell ,作業系統分配一記憶區塊給 shell 使用,此區域之變數可以讓子程序存取;
    • 利用 export 功能,可以讓變數的內容寫到上述的記憶區塊當中(環境變數);
    • 當載入另一個 shell 時 (亦即啟動子程序,而離開原本的父程序了),子 shell 可以將父 shell 的環境變數所在的記憶區塊導入自己的環境變數區塊當中。
    透過這樣的關係,我們就可以讓某些變數可以在相關的程序之間存在,以幫助自己更方便的操作環境喔!

    變數鍵盤讀取、陣列與宣告: read, array, declare

    我們上面提到的變數設定功能,都是直接由指令列直接設定的,那麼,可不可以讓使用者能夠經由鍵盤輸入? 什麼意思呢?是否記得某些程式執行的過程當中,會等待使用者輸入 "yes/no" 之類的訊息啊!? 在 bash 裡面也有相對應的功能喔!此外,我們還可以宣告這個變數的屬性, 例如:陣列或者是數字等等的。底下就來看看吧!

  • read
  • 要讀取來自鍵盤輸入的變數,就是用 read 這個指令了。這個指令最常被用在 shell script 的撰寫當中, 以跟使用者進行對談。關於 script 的寫法,我們會在後面章節介紹,底下先來瞧一瞧 read 的相關語法吧!
    [root@linux ~]# read [-pt] variable
    參數:
    -p  :後面可以接提示字元!
    -t  :後面可以接等待的『秒數!』這個比較有趣~不會一直等待使用者啦!
    範例:
    
    範例一:讓使用者由鍵盤輸入一內容,將該內容變成 atest 變數
    [root@linux ~]# read atest
    This is a test
    [root@linux ~]# echo $atest
    This is a test
    
    範例二:提示使用者 30 秒內輸入自己的大名,將該輸入字串做成 named 變數
    [root@linux ~]# read -p "Please keyin your name: " -t 30 named
    Please keyin your name: VBird Tsai
    [root@linux ~]# echo $named
    VBird Tsai
    
    read 之後不加任何參數,直接加上變數名稱,那麼底下就會主動出現一個空白行,等待您輸入。 如果加上 -t 後面接秒數之後,例如上面的範例當中,那麼 30 秒之內沒有任何動作時, 該指令就會自動略過了~如果是加上 -p ,嘿嘿!後面就會有比較多可以用的提示字元給我們參考! 在指令的下達裡面,比較美觀啦! ^_^

  • declare / typeset
  • declare 或 typeset 是一樣的功能,就是在宣告變數的屬性。如果使用 declare 後面並沒有接任何參數, 那麼 bash 就會主動的將所有的變數名稱與內容通通叫出來,就好像使用 set 一樣啦! 那麼 declare 還有什麼語法呢?看看先:
    [root@linux ~]# declare [-aixr] variable
    參數:
    -a  :將後面的 variable 定義成為陣列 (array)
    -i  :將後面接的 variable 定義成為整數數字 (integer)
    -x  :用法與 export 一樣,就是將後面的 variable 變成環境變數;
    -r  :將一個 variable 的變數設定成為 readonly ,該變數不可被更改內容,也不能 unset
    範例:
    範例一:讓變數 sum 進行 100+300+50 的加總結果
    [root@linux ~]# sum=100+300+50
    [root@linux ~]# echo $sum
    100+300+50  <==咦!怎麼沒有幫我計算加總?因為這是文字型態的變數屬性啊!
    [root@linux ~]# declare -i sum=100+300+50
    [root@linux ~]# echo $sum
    450         <==瞭乎??
    
    範例二:將 sum 變成環境變數
    [root@linux ~]# declare -x sum
    
    範例三:讓 sum 變成唯讀屬性,不可更動!
    [root@linux ~]# declare -r sum
    [root@linux ~]# sum=tesgting
    -bash: sum: readonly variable  <==老天爺~不能改這個變數了!
    
    declare 也是個很有用的功能~尤其是當我們需要使用到底下的陣列功能時, 他也可以幫我們宣告陣列的屬性喔!不過,老話一句,陣列也是在 shell script 比較常用的啦!

  • 陣列屬性 array 說明
  • 某些時候,我們必須使用陣列來宣告一些變數,這有什麼好處啊?在一般人的使用上, 果然是看不出來有什麼好處的!不過,如果您曾經寫過程式的話,那才會比較瞭解陣列的意義~ 陣列對寫數值程式的設計師來說,可是不能錯過學習的重點之一哩!好!不囉唆~ 那麼要如何設定陣列的變數與內容呢?在 bash 裡頭,陣列的設定方式是:
      var[index]=content
    意思是說,我有一個陣列名稱為 var ,而這個陣列的內容為 var[1]=小明, var[2]=大明, var[3]=好明 .... 等等,那個 index 就是一些數字啦,重點是用中刮號 ([ ]) 來設定的。 目前我們 bash 提供的是一維陣列。老實說,如果您不必寫一些複雜的程式, 那麼這個陣列的地方,可以先略過,等到有需要再來學習即可!因為要製作出陣列, 通常與迴圈或者其他判斷式交互使用才有比較高的意義存在!
    範例:設定上面提到的 var[1] ~ var[3] 的變數。
    [root@linux ~]# var[1]="small min"
    [root@linux ~]# var[2]="big min"
    [root@linux ~]# var[3]="nice min"
    [root@linux ~]# echo "${var[1]}, ${var[2]}, ${var[3]}"
    
    比較有趣的地方在於『讀取』,一般來說,建議直接以 ${陣列} 的方式來讀取, 比較正確無誤的啦!

    與檔案系統及程序的限制關係: ulimit

    想像一個狀況:我的 Linux 主機裡面同時登入了十個人,這十個人不知怎麼搞的, 同時開啟了 100 個檔案,每個檔案的大小約 10MBytes ,請問一下, 我的 Linux 主機的記憶體要有多大才夠? 10*100*10 = 10000 MBytes ~~ 老天爺,這樣,系統不掛點才有鬼哩!為了要預防這個情況的發生,所以, 我們的 bash 是可以『限制使用者的某些系統資源』的,包括可以開啟的檔案數量, 可以使用的 CPU 時間,可以使用的記憶體總量等等。如何設定?用 ulimit 吧!
    [root@linux ~]# ulimit [-SHacdflmnpstuv] [配額]
    參數:
    -H  :hard limit ,嚴格的設定,必定不能超過設定的值;
    -S  :soft limit ,警告的設定,可以超過這個設定值,但是會有警告訊息,
          並且,還是無法超過 hard limit 的喔!也就是說,假設我的 soft limit
          為 80 , hard limit 為 100 ,那麼我的某個資源可以用到 90 ,
          可以超過 80 ,還是無法超過 100 ,而且在 80~90 之間,會有警告訊息的意思。
    -a  :列出所有的限制額度;
    -c  :可建立的最大核心檔案容量 (core files)
    -d  :程序資料可使用的最大容量
    -f  :此 shell 可以建立的最大檔案容量 (一般可能設定為 2GB)單位為 Kbytes
    -l  :可用於鎖定 (lock) 的記憶體量
    -p  :可用以管線處理 (pipe) 的數量
    -t  :可使用的最大 CPU 時間 (單位為秒)
    -u  :單一使用者可以使用的最大程序(process)數量。
    範例:
    範例一:列出所有的限制資料
    [root@linux ~]# ulimit -a
    
    範例二:限制使用者僅能建立 1MBytes 以下的容量的檔案
    [root@linux ~]# ulimit -f 1024
    
    還記得我們在 Linux 磁碟檔案系統 裡面提到過,單一 filesystem 能夠支援的單一檔案大小與 block 的大小有關。例如 block size 為 1024 byte 時,單一檔案可達 16GB 的容量。但是,我們可以用 ulimit 來限制使用者可以建立的檔案大小喔! 利用 ulimit -f 就可以來設定了!例如上面的範例二,要注意單位喔!單位是 Kbytes。 若改天你一直無法建立一個大容量的檔案,記得瞧一瞧 ulimit 的資訊喔!( 不過,要注意的是,一般身份使用者如果以 ulimit 設定了 -f 的檔案大小, 那麼他『只能減小檔案大小,不能增加檔案大小喔!』)

    額外的變數設定功能

    剛剛我們提到了兩種變數取用的方法,分別是這樣:
    [root@linux ~]# echo $HOME
    [root@linux ~]# echo ${HOME}
    
    那麼,在那個 ${variable} 的使用方法中,其實,我們還可以將變數進行一些修訂的工作喔! 只要加上一些字符標誌,後面再接著使用比對字串,就能夠修改變數的內容了! 我們取底下的例子來說明:在底下的例子中,假設我的變數名稱為 vbird ,且內容為 /home/vbird/testing/testing.x.sh。
    1. 完整呈現 vbird 這個變數的內容;
    [root@linux ~]# vbird="/home/vbird/testing/testing.x.sh"
    [root@linux ~]# echo ${vbird}
    /home/vbird/testing/testing.x.sh
    
    2. 在 vbird 變數中,從最前面開始比對,若開頭為 / ,則刪除兩個 / 
       之間的所有資料,亦即 /*/
    [root@linux ~]# echo ${vbird##/*/}
    testing.x.sh    <==刪除了 /home/vbird/testing/
    [root@linux ~]# echo ${vbird#/*/}
    vbird/testing/testing.x.sh   <==僅刪除 /home/ 而已
    # 這兩個小例子有趣了~變數名稱後面如果接了兩個 ## ,表示在 ##
    # 後面的字串取『最長的』那一段;如果僅有一個 # ,表示取『最小的那一段』喔!
    
    3. 承上題,如果是從後面開始,刪除 /* 呢?
    [root@linux ~]# echo ${vbird%%/*/}
    /home/vbird/testing/testing.x.sh  <==都沒被刪除
    [root@linux ~]# echo ${vbird%%/*}
        <==被刪除光了!
    [root@linux ~]# echo ${vbird%/*}
    /home/vbird/testing   <==只刪除 /testing.x.sh 部分
    # 這個例子當中需要特別注意,那個 % 比對的是『最後面那個字元』的意思,
    # 所以囉,第一個方式當然不對~因為 vbird 這個變數的內容最後面是 h 而不是 / 啊!
    # 至於 %%/* 則是刪除『最長的那個 /* 』,當然就是全部喔!而 %/* 則是最短的那個!
    
    4. 將 vbird 變數中的 testing 取代為 TEST
    [root@linux ~]# echo ${vbird/testing/TEST}
    /home/vbird/TEST/testing.x.sh
    [root@linux ~]# echo ${vbird//testing/TEST}
    /home/vbird/TEST/TEST.x.sh
    # 如果變數後面接的是 / 時,那麼表示後面是進行『取代』的工作~而且僅取代『第一個』
    # 但如果是 // ,則表示全部的字串都取代啊!
    
    這裡您稍微留意一下就好了~反正就是變數後面可以接 #, ##, %, %%, /, // , 而他們存在的意義並不相同的啦~

    另外,幾個不同的變數內容還可以進行判斷呢! 舉例來說,目前我需要用到兩個變數,分別是 var 與 str , 那我想要針對 str 這個變數內容是否有設定成一個字串,亦即 "expr" 來決定 var 的內容。 那我可以使用什麼方法來進行判斷呢?如果您會寫 shell script 的話, 直接用 shell script 就好了,如果不會寫,那麼我們就透過簡單的變數判斷吧!
    Tips 鳥哥 底下的例子當中,那個 var 與 str 為變數,我們想要針對 str 是否有設定來決定 var 的值喔! 一般來說, str: 代表『str 沒設定或為空的字串時』;至於 str 則僅為『沒有該變數』。
    變數設定方式str 沒有設定 str 為空字串str 已設定非為空字串
    var=${str-expr}var=exprvar=var=$str
    var=${str:-expr}var=exprvar=exprvar=$str
    var=${str+expr}var=var=exprvar=expr
    var=${str:+expr}var=var=var=expr
    var=${str=expr}str=expr
    var=expr
    str 不變
    var=
    str 不變
    var=$str
    var=${str:=expr}str=expr
    var=expr
    str=expr
    var=expr
    str 不變
    var=$str
    var=${str?expr}expr 輸出至 stderrvar=var=str
    var=${str:?expr}expr 輸出至 stderrexpr 輸出至 stderrvar=str

    根據上面這張表,我們來進行幾個範例的練習吧! ^_^
    範例一:若 str 這個變數內容存在,則 var 設定為 str ,否則 var 設定為 "newvar"
    [root@linux ~]# unset str; var=${str-newvar}
    [root@linux ~]# echo var="$var", str="$str"
    var=newvar, str=        <==因為 str 不存在,所以 var 為 newvar
    [root@linux ~]# str="oldvar"; var=${str-newvar}
    [root@linux ~]# echo var="$var", str="$str"
    var=oldvar, str=oldvar  <==因為 str 存在,所以 var 等於 str 的內容
    
    範例二:若 str 不存在,則 var 與 str 均設定為 newvar,否則 var 與 str 相同
    [root@linux ~]# unset str; var=${str=newvar}
    [root@linux ~]# echo var="$var", str="$str"
    var=newvar, str=newvar  <==因為 str 不存在,所以 var/str 均為 newvar
    [root@linux ~]# str="oldvar"; var=${str=newvar}
    [root@linux ~]# echo var="$var", str="$str"
    var=oldvar, str=oldvar  <==因為 str 存在,所以 var 等於 str 的內容
    
    範例三:若 str 這個變數存在,則 var 等於 str ,否則輸出 "novar"
    [root@linux ~]# unset str; var=${str?novar}
    -bash: str: novar       <==因為 str 不存在,所以輸出錯誤訊息 
    [root@linux ~]# str="oldvar"; var=${str?novar}
    [root@linux ~]# echo var="$var", str="$str"
    var=oldvar, str=oldvar  <==因為 str 存在,所以 var 等於 str 的內容
    
    # 上面這三個案例都沒有提到當 str 有設定,且為空字串的情況喔!
    # 您可以自行測試一下哩!
    
    雖然猛一看,覺得變數沒有什麼奇特的地方,但是,如果仔細瞧一瞧,嘿!一堆環境變數與系統資源方面的變數, 可是會影響到我們在 bash 裡頭是否能夠順利作業的呢!例如 PATH 啊、ulimit 之類的~ 所以,您還是得要瞭解變數這個玩意才行喔! ^_^

    命令別名與歷史命令:

    我們知道在早期的 DOS 年代,清除螢幕上的資訊可以使用 cls 來清除,但是在 Linux 裡面, 我們則是使用 clear 來清除畫面的。那麼可否讓 cls 等於 clear 呢?可以啊!用啥方法? link file 還是什麼的?別急!底下我們介紹不用 link file 的命令別名來達成。那麼什麼又是歷史命令? 曾經做過的舉動我們可以將他記錄下來喔!那就是歷史命令囉~底下分別來談一談這兩個玩意兒。

    命令別名設定: alias, unalias

    命令別名是一個很有趣的東西,特別是你的慣用指令特別長的時候!還有, 增設預設的屬性在一些慣用的指令上面,可以預防一些不小心誤殺檔案的情況發生的時候! 舉個例子來說,如果你要查詢隱藏檔,並且需要長的列出與一頁一頁翻看,那麼需要下達『 ls -al | more 』這個指令,我是覺得很煩啦! 要輸入好幾個單字!那可不可以使用 lm 來簡化呢?!當然可以,你可以在命令列下面下達:
    [root@linux ~]# alias lm='ls -l | more'
    
    嘿嘿!我立刻多出了一個可以執行的指令喔!這個指令名稱為 lm ,且其實他是執行 ls -al | more 啊!真是方便。不過, 要注意的是:『alias 的定義規則與變數定義規則幾乎相同』, 所以你只要在 alias 後面加上你的 {『別名』='指令 參數' }, 以後你只要輸入 lm 就相當於輸入了 ls -al|more 這一串指令!很方便吧!

    另外,命令別名的設定還可以取代既有的指令喔!舉例來說,我們知道 root 可以移除( rm )任何資料!所以當你以 root 的身份在進行工作時,需要特別小心, 但是總有失手的時候,那麼 rm 提供了一個參數來讓我們確認是否要移除該檔案,那就是 -i 這個參數!所以,你可以這樣做:
    [root@linux ~]# alias rm='rm -i'
    
    嘿嘿!那麼以後使用 rm 的時候,就不用太擔心會有錯誤刪除的情況了!這也是命令別名的優點囉! 那麼如何知道目前有哪些的命令別名呢?就使用 alias 呀!
    [root@linux ~]# alias
    alias l.='ls -d .* --color=tty'
    alias ll='ls -l --color=tty'
    alias lm='ls -al | more'
    alias ls='ls --color=tty'
    alias vi='vim'
    alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
    
    由上面的資料當中,您也會發現一件事情啊,我們在 vi 文書編輯器 裡面提到 vi 與 vim 是不太一樣的, vi 是比較老,而 vim 可以用來取代 vi 喔。我們的 FC4 明明就同時有 vi/vim , 為何我執行 vi 會是進入 vim 呢?呵呵!那就是因為上面的表格當中的『 alias vi='vim' 』這個設定啦! 至於如果要取消命令別名的話,那麼就使用 unalias 吧!例如要將剛剛的 lm 命令別名拿掉,就使用:
    [root@linux ~]# unalias lm
    
    那麼命令別名與變數有什麼不同呢?基本上,他們的意義就不太一樣了! alias 這種命令別名,你可以將他想成是建立一個新的指令名稱, 至於變數則僅是將一個數值或者字串存在某個代表意義當中!舉個例子好了,我們知道以前的 DOS 年代,列出目錄與檔案就是 dir ,而清除螢幕就是 cls ,那麼如果我想要在 linux 裡面也使用相同的指令呢?那就以 alias 來進行指令的別名設定:
      alias cls='clear'
      alias dir='ls -l'
      只要加入這兩行,以後你輸入 cls 及 dir 就可以執行了!很方便吧!

    歷史命令:history

    前面我們提過 bash 有提供指令歷史的服務!那麼如何查詢我們曾經下達過的指令呢?就使用 history 囉!當然,如果覺得 histsory 要輸入的字元太多太麻煩,可以使用命令別名來設定呢! 不要跟我說還不會設定呦! ^_^
      alias h='history'
    如此則輸入 h 等於輸入 history 囉!好了,我們來談一談 history 的用法吧!
    [root@linux ~]# history [n]
    [root@linux ~]# history [-c]
    [root@linux ~]# history [-raw] histfiles
    參數:
    n   :數字,意思是『要列出最近的 n 筆命令列表』的意思!
    -c  :將目前的 shell 中的所有 history 內容全部消除
    -a  :將目前新增的 history 指令新增入 histfiles 中,若沒有加 histfiles ,
          則預設寫入 ~/.bash_history
    -r  :將 histfiles 的內容讀到目前這個 shell 的 history 記憶中;
    -w  :將目前的 history 記憶內容寫入 histfiles 中!
    範例:
    範例一:列出目前記憶體內的所有 history 記憶
    [root@linux ~]# history
    # 前面省略
     1017  man bash
     1018  ll
     1019  history 
     1020  history
    # 列出的資訊當中,共分兩欄,第一欄為該指令在這個 shell 當中的代碼,
    # 另一個則是指令本身的內容喔!至於會秀出幾筆指令記錄,則與 HISTSIZE 有關!
    
    範例二:列出目前最近的 3 筆資料
    [root@linux ~]# history 3
     1019  history 
     1020  history
     1021  history 3
    
    範例三:立刻將目前的資料寫入 histfile 當中
    [root@linux ~]# history -w
    # 在預設的情況下,會將歷史紀錄寫入 ~/.bash_history 當中!
    [root@linux ~]# echo $HISTSIZE
    1000
    
    在正常的情況下,當我們以 bash 登入 Linux 主機之後,系統會主動的由家目錄的 ~/.bash_history 讀取以前曾經下過的指令,那麼 ~/.bash_history 會記錄幾筆資料呢?這就與你 bash 的 HISTSIZE 這個變數設定值有關了!在預設的 FC4 底下,是會記錄 1000 筆資料的! 那麼假設我這次登入主機後,共下達過 100 次指令,『等我登出時, 系統就會將 101~1100 這總共 1000 筆歷史命令更新到 ~/.bash_history 當中。』 也就是說,歷史命令在我登出時,會將最近的 HISTSIZE 筆記錄到我的紀錄檔當中啦! 當然,也可以用 history -w 強制立刻寫入的!那為何用『更新』兩個字呢? 因為 ~/.bash_history 記錄的筆數永遠都是 HISTSIZE 那麼多,舊的訊息會被主動的拿掉! 僅保留最新的!

    那麼 history 這個歷史命令只可以讓我查詢命令而已嗎?呵呵!當然不止啊! 我們可以利用相關的功能來幫我們執行命令呢!舉例來說囉:
    [root@linux ~]# !number
    [root@linux ~]# !command
    [root@linux ~]# !!
    參數:
    number  :執行第幾筆指令的意思;
    command :由最近的指令向前搜尋『指令串開頭為 command』的那個指令,並執行;
    !!      :就是執行上一個指令(相當於按↑按鍵後,按 Enter)
    範例:
    [root@linux ~]# history
       66  man rm
       67  alias
       68  man history
       69  history 
    [root@linux ~]# !66  <==執行第 66 筆指令
    [root@linux ~]# !!   <==執行上一個指令,本例中亦即 !66 
    [root@linux ~]# !al  <==執行最近以 al 為開頭的指令(上頭列出的第 67 個)
    
    經過上面的介紹,瞭乎?歷史命令用法可多了!如果我想要執行上一個指令, 除了使用上下鍵之外,我可以直接以『 !! 』 來下達上個指令的內容,此外, 我也可以直接選擇下達第 n 個指令,『 !n 』來執行,也可以使用指令標頭,例如 『 !vi 』來執行最近指令開頭是 vi 的指令列!相當的方便而好用!基本上 history 的用途很大的!但是需要小心安全的問題!尤其是 root 的歷史紀錄檔案,這是 Cracker 的最愛!因為不小心的 root 會將很多的重要資料在執行的過程中會被紀錄在 ~/.bash_history 當中,如果這個檔案被解析的話,後果不堪吶!無論如何,使用 history 配合『 ! 』曾經使用過的指令下達是很有效率的一個指令方法!

    Bash Shell 使用環境:

    是否記得我們登入主機的時候,螢幕上頭會有一些說明文字,告知我們的 Linux 版本啊什麼的, 還有,登入的時候,我們還可以給予使用者一些訊息或者歡迎文字呢。此外, 我們習慣的環境變數、命令別名等等的,是否可以登入就主動的幫我設定好? 這些都是需要來注意的。另外,這些設定值又可以分為系統整體設定值與各人喜好設定值, 僅是一些檔案放置的地點不同啦!這我們後面也會來談一談的!

    絕對路徑與相對路徑

    這個議題說到快要爛掉了~從一開始到現在,這個絕對路徑與相對路徑的問題我們就提到不知道多少次了, 因為他實在很重要~這與 PATH 這個變數關係很大!老實說, 萬一你的 PATH 沒有設定完整的時候,下達指令就必須要以『 一長列的指令連帶根目錄都要列出來 』,呵呵那就是絕對路徑的設定法啦! 基本上,這個『 絕對路徑』與『相對路徑 』的觀念是很重要的!否則你將常常會找不到檔案說! 所謂的『絕對路徑』就是以根目錄開始寫入到檔案的一種命令寫定方法,舉例來說,我目前在 /home/test 這個 test 使用者的家目錄中,我想要看看裡面的 .bashrc 這個檔案的資料,使用的是 more 這個指令,而這個指令在 /bin/more 當中,則正確的下達指令的方法為:
    [root@linux ~]# /bin/more .bashrc
    
    我在的目錄為 /home/test !這是絕對路徑寫法! 而如果你還記得我們在 Linux 檔案與目錄管理 那一篇文章中提到的觀念的話,那麼應該記得使用 ls -al 時會出現兩個一定存在的目錄,分別是『.』與『..』,分別代表是『這個路徑』,與『上一層路徑』!
    [root@linux ~]# ls -al
    drwxrwxr-x  2 root  root   4096  Aug 15 11:05 .
    drwxrwxr-x  2 root  root   4096  Aug 14 23:26 ..
    
    所以說,要執行上一層目錄中的命令,可以下達『../command 』那個 command 指的是存在的可執行檔!那麼我因為在 /home/test 裡面,距離 /bin 有兩層上層目錄,所以我要使用 /bin/more 這個執行檔,並且使用相對路徑的方法,就必須使用:
    [root@linux ~]# ../../bin/more .bashrc
    
    這種相對路徑的方法相當廣泛的被運用於 script 當中,這是因為如前面提到的, 每個人的安裝預設的目錄都不相同,則使用相對路徑的話, 很容易就可以找到套件之間相依軟體或者是設定檔案的相關性!

    例題:關於路徑搜尋的問題!為何不執行目前所在目錄下的檔案?
    答:
      咦!剛剛不是提到『.』與『..』嗎?那麼那個『 . 』是幹嘛用的?!眼尖的朋友應該已經發現了, 就是『我在執行檔案的時候,基本上,並不會主動搜尋目前目錄下的檔案』舉個例子來說, 我安裝的 squid 這個執行檔在 /usr/local/squid/bin/squid 這個檔案,然而我在 /usr/local/squid/bin 下達 squid 的時候,系統會告訴你『查不到這個檔案!』真是見鬼了! 明明有這個檔案的呀!這是因為系統預設的 PATH (路徑) 並沒有執行目前目錄下的設定,也就是『.』這個路徑!你可以使用『 echo $PATH 』看看,就可以知道為什麼了!

      那麼為何不要設定這個路徑呢?這是因為『 安全』的考量。由於系統預設是允許任何人在 /tmp 底下寫入任何檔案的,那麼萬一有居心不良的使用者或者是 Cracker 入侵你的電腦,並在你的 /tmp 裡頭埋了一個小木馬,並取名為 ls ,好了,改天你以 root 身份登入後,到 /tmp 底下,並執行 ls ,你看會有什麼結果?!這個 /tmp/ls 由其他身份的人來執行或許沒有問題,但是由 root 來執行卻可能會導致 Cracker 所樂意見到的結果!那曉得為何了吧?!

      當然囉!您還是可以選擇在 ~/.bashrc 當中設定你的 . 在你的 PATH 當中,不過並不這麼建議就是了!

    好了,由於系統預設並不主動搜尋目前目錄下的執行檔,那麼你應該如何執行『目前目錄下的執行檔』呢? 很簡單呀!就是以相對路徑的觀念,由於『 .. 』是上層,而『 . 』是這一層,所以要執行這一層目錄的命令就使用『 ./command 』即可!例如你的 /usr/local/squid/bin 底下執行 squid 則可以寫成:
    [root@linux ~]# ./squid
    
    請特別留意這方面的問題!『新手特別容易犯這個錯誤呢!

    登錄訊息顯示資料: /etc/issue, /etc/motd

    還記得我們在終端機介面 (tty1 ~ tty6) 登入的時候,會有幾行提示的字串嗎? 那個字串寫在哪裡啊?呵呵!在 /etc/issue 裡面啊!先來看看:
    [root@linux ~]# cat /etc/issue
    Fedora Core release 4 (Stentz)
    Kernel \r on an \m
    
    
    在 FC4 裡面預設有三行,這個在我們本機登入時就會顯示在 title 的地方呢~ 咦!那麼那個 \r 及 \m 是啥?您可以使用 man issue 配合 man mingetty 就能夠知道:

    issue 內的各代碼意義
    \d 本地端時間的日期;
    \l 顯示第幾個終端機介面;
    \m 顯示硬體的等級 (i386/i486/i586/i686...);
    \n 顯示主機的網路名稱;
    \o 顯示 domain name;
    \r 作業系統的版本 (相當於 uname -r)
    \t 顯示本地端時間的時間;
    \s 作業系統的名稱;
    \v 作業系統的版本。

    所以,如果您想要顯示終端機的號碼,就可以加上 \l 在 /etc/issue 檔案內囉~就能夠修改登入字元。 咦!但是還有個 /etc/issue.net 呢!這是啥?沒啥啦!這個是提供給 telnet 這個遠端登入程式用的。 當我們使用 telnet 連接到主機時,主機的登入畫面就會顯示 /etc/issue.net 而不是 /etc/issue 呢!

    至於如果您想要讓使用者登入後取得一些訊息,例如您想要讓大家都知道的訊息, 那麼可以將訊息加入 /etc/motd 裡面去!例如:當登入後,告訴登入者, 系統將會在某個固定時間進行維護工作,可以這樣做:
    [root@linux ~]# vi /etc/motd
    Hello everyone,
    Our server will be maintained at 2005/10/10 0:00 ~ 24:00.
    Please don't login at that time. ^_^
    
    那麼當你的使用者登入主機後,就會顯示這樣的訊息出來:
    Last login: Mon Aug 15 10:17:10 2005 from 127.0.0.1
    Hello everyone,
    Our server will be maintained at 2005/10/10 0:00 ~ 24:00.
    Please don't login at that time. ^_^
    
    是否很方便啊!? ^_^

    環境設定檔: bashrc, ~/.bashrc, ~/.profile, profile...,/etc/inputrc, source

    關於取得 bash 的環境變數等資料,其實可以有系統規劃與各人喜好, 一般來說,建議使用者直接修改個人設定值即可,不需要更動到系統啦~ 底下我們分別來談一談幾個有趣的設定檔喔!要注意的是,在指令列輸入的變數也好、命令別名也罷, 都是針對該次登入的設定而已,所以只要您一登出,那麼上次的設定值就會不見去! 因此,我們需要有幾個檔案來幫助我們,每次登入的時候,就已經幫我們搞定了環境的設定囉!


  • 系統設定值
  • 所謂的系統設定值,也就是說每個使用者進入到 bash shell 之後,會先讀取的設定檔案! 預設的設定檔案有下列幾個:

  • /etc/sysconfig/i18n
  • 記得我們在幾個重要變數內談到的語系資料嗎?! 那個語系是由 i18n 所維護的,而 FC4 預設的系統語系設定檔就在 /etc/sysconfig/i18n 當中。 這個檔案有點像這樣:
    [root@linux ~]# cat /etc/sysconfig/i18n
    LANG="zh_TW.UTF-8"
    SYSFONT="latarcyrheb-sun16"
    SUPPORTED="zh_TW.UTF-8:zh_TW:zh:en_US.UTF-8"
    
    我預設使用 zh_TW.UTF-8 來作為我的整體語系,當然,我可以在這裡修改 LANG 以及其他相關的語系變數, 例如 LC_CTYPE 或者是 LC_TIME 等等的。不過,一般來說,使用者自己個人的設定不建議在這裡做更動啦! 他們可以自行設定他們自己的設定檔啊!

  • /etc/profile
  • 這個檔案設定了幾個重要的變數,例如:『PATH、USER、MAIL、 HOSTNAME、HISTSIZE、umask』等等,也同時規劃出 /etc/inputrc 這個針對鍵盤熱建設定的檔案的資料內容。你可以在這裡設定總體的 PATH 等等的資訊! 同時,這個檔案也規劃出 /etc/profile.d 及 /etc/inputrc 這兩個目錄與檔案!

    總之,你可以瞭解到剛剛我們學會的變數設定方式,在這個檔案中也可以設定呢! 但是設定上需要特別小心,因為所有的使用者皆會使用到這個檔案的資訊。通常我都喜歡將 /usr/local/bin 這個路徑加成最前面,這是因為通常自己安裝的套件自己最喜歡, 所以當然是最先搜尋囉! ^_^!此外,請注意一下,可以將 HISTSIZE 的大小改變一下,改成 50 就可以啦!比較安全!( 註:這個檔案不論在那個 Linux distributions 當中均存在 /etc/profile 當中,所以,請特別留意此一檔案即可! )。

  • /etc/bashrc
  • 這個檔案在規劃 umask 的功能,也同時規劃出提示字元的內容 (就是裡頭那個 PS1 啦!) 。特別留意的是,這個檔案在不同的 Linux distribution 裡面,擺放的位置可能不太一樣呢! 所以需要查詢一下才行呦!

  • /etc/profile.d/*.sh
  • /etc/profile.d 是一個目錄,裡面針對 bash 及 C-shell 規範了一些資料。 以 FC4 為例,這個目錄裡面就針對了顏色、語系、vim 及 which 等指令進行一些額外的設定, 例如 alias 之類的規範值。我們的 vim 被用 alias 命名為 vi 就是在這個目錄下被設定好的。 當然啦,這個目錄的由來其實是在 /etc/profile 這個檔案內規範的啦! 你可以自行設定一些 *.sh 的檔名的檔案來書寫自己的系統設定值喔!

  • /etc/man.config
  • 這個檔案乍看之下好像跟 bash shell 沒相關性,但是對於系統管理員來說, 卻也是很重要的一個檔案!這的檔案的內容『規範了使用 man 的時候, man page 的路徑到哪裡去尋找!』所以說的簡單一點,這個檔案規定了下達 man 的時候,該去哪裡查看資料的路徑設定!那麼什麼時候要來修改這個檔案呢?如果你是以 tarball 的方式來安裝你的資料,那麼你的 man page(指令說明檔案)可能會放置在 /usr/local/softpackage/man 裡頭,那個 softpackage 是你的套件名稱, 這個時候你就得以手動的方式將該路徑加到 /etc/man.config 裡頭,否則使用 man 的時候就會找不到相關的說明檔囉。

    事實上,這個檔案內最重要的其實是 MANPATH 這個變數設定啦! 我們搜尋 man page 時,會依據 MANPATH 的路徑去分別搜尋啊!另外,要注意的是, 這個檔案在各大不同版本 Linux distributions 中,檔名都不太相同,例如 FC4 用的是 /etc/man.config ,而 SuSE 用的則是 /etc/manpath.config , 可以利用 [tab] 按鍵來進行檔名的補齊啦!

    這就是系統在設定的時候常常會使用的檔案!需要特別留意的是,通常設定完了這幾個檔案之後,都需要先 logout 在 login 之後才會將設定整個啟動起來!


  • 個人設定值
  • 那麼個人的喜好設定在哪裡?嘿嘿嘿嘿!那就是在個人家目錄的幾個隱藏檔當中囉! 分別會使用到底下的幾個檔案啦!( 注意!底下的檔案都是隱藏檔,需要使用 ls -al 方能顯示出來 ) ,另外,注意一下囉!底下那個『 ~ 』代表的是『家目錄』的意思:

  • ~/.bash_profile, ~/.bash_login, ~/.profile
  • 這三個檔案通常只要一個就夠了,一般預設是以 ~/.bash_profile 的檔名存在。 會有這麼多的檔案,其實是因應其他 shell 轉換過來的使用者的習慣而已。 這個檔案可以定義個人化的路徑 (PATH) 與環境變數等等。不過,還是有順位上的差異, bash 啟動時,會先去讀取 ~/.bash_profile,找不到時,就去讀取 ~/.bash_login ,然後才是 ~/.profile

  • ~/.bashrc
  • 鳥哥一般都是將自己的需要輸入在這個檔案裡面的呢! 我的個人化設定值都會寫在這裡說~例如命令別名、路徑等等。

  • ~/.bash_history
  • 還記得我們在歷史命令提到過這個檔案吧?!呵呵!沒錯~預設的情況下, 我們的歷史命令就記錄在這裡啊!而這個檔案能夠記錄幾筆資料,則與 HISTSIZE 這個變數有關啊。每次登入 bash 後,bash 會先讀取這個檔案,將所有的歷史指令讀入記憶體, 因此,當我們登入 bash 後就可以查知上次使用過哪些指令囉。至於更多的歷史指令, 請自行回去參考喔!

  • ~/.bash_logout
  • 這個檔案則記錄了『當我登出 bash 後,系統再幫我做完什麼動作後才離開』的意思。 你可以去讀取一下這個檔案的內容,預設的情況下,登出時, bash 只是幫我們清掉螢幕的訊息而已。 不過,你也可以將一些備份或者是其他你認為重要的工作寫在這個檔案中(例如清空暫存檔), 那麼當你離開 Linux 的時候,就可以解決一些煩人的事情囉!

    好了,我們知道在變數的設定規範當中,後輸入的設定值可以取代先輸入的設定值, 那麼在我們登入 bash 的時候,這些設定檔到底是如何讀取的呢?他是這樣讀取的:
    1. 先讀取 /etc/profile ,再根據 /etc/profile 的內容去讀取其他額外的設定檔, 例如 /etc/profile.d 與 /etc/inputrc 等等設定檔;
    2. 根據不同的使用者,到使用者家目錄去讀取 ~/.bash_profile 或 ~/.bash_login 或 ~/.profile 等設定檔;
    3. 根據不同使用者,到他家目錄去讀取 ~/.bashrc 。
    所以囉,當我登入 bash 後,最終讀取的設定檔竟然是 ~/.bashrc 呢! 也就是說,在 ~/.bashrc 裡面的設定會是最終的設定值!所以囉, 通常鳥哥我喜歡將個人的一些常用 alias 或 PATH 等環境變數或自訂變數都寫到這個檔案去, 如此一來,不論原來系統幫我們做了什麼設定值,我都可以使用屬於自己熟悉的環境呢! 鳥哥的 ~/.bashrc 有點像這樣:
    [root@linux ~]# vi ~/.bashrc
    # .bashrc
    
    # Source global definitions
    if [ -f /etc/bashrc ]; then
            . /etc/bashrc
    fi
    
    # User specific aliases and functions
    PATH="/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
    PATH="$PATH":/usr/X11R6/bin:/home/dmtsai/bin
    LANG=zh_TW.big5
    LC_TIME=C
    export PATH LC_TIME LANG
    umask 022
    
    alias   rm='rm -i'
    alias   cp='cp -i'
    alias   mv='mv -i'
    alias   ll='ls -l'
    alias   lm='ls -al|more'
    alias   h='history'
    
    仔細看到上頭這個檔案,會不會覺得奇怪啊!為什麼會有第五行的『 . /etc/bashrc 』呢? 那個小數點 (.) 代表什麼意思啊??其實 if [ ... ]; then .... fi 是 shell script 當中的程式寫法, 這個我們會在下一章當中介紹。不過,那個 . 則需要好好的談一談喔!一般來說,如果修改完了設定檔, 通常就是 logout 後再重新 login 到 bash 內,就能夠將環境設定檔重讀了!不過, 我們可以使用底下的方式來讓該設定檔立即生效:
    [root@linux ~]# source file
    範例:
    [root@linux ~]# source ~/.bashrc
    [root@linux ~]#  .  ~/.bashrc
    
    利用 source 或小數點 (.) 都可以將設定檔的內容讀進來目前的 shell 環境中! 舉例來說,我修改了 ~/.bashrc ,那麼不需要登出,立即以 source ~/.bashrc 就可以將剛剛最新設定的內容讀進來目前的環境中!很不錯吧!此外,什麼時候會使用到不同的設定檔呢? 最常發生在一個人的工作環境分為多重的時候了!舉個例子來說,在我的大型主機中, 我常常需要負責兩到三個不同的案子,每個案子所需要處理的環境變數訂定並不相同, 那麼我就將這兩三個案子分別編寫屬於該案子的環境變數設定檔案,當我需要該環境時,就直接『 source 變數檔 』,如此一來,環境變數的設定就變的更簡便而靈活了!

  • login shell 與 non-login shell
  • 事實上,這些環境設定檔在讀取時,還是有一些差異的,這就得要談到所謂的『login shell』與 『non-login shell』的差異了。基本上,就字面上的意義來解釋的話,所謂的 loign shell 指的就是當使用者登入 Linux 系統時,所取得的那個環境設定檔稱為 login shell。 當登入後,啟動其他的 bash 時所取用的環境設定檔就稱為 non-login shell 。

    舉例來說,我以 dmtsai 這個使用者身份登入 Linux 後,然後為了要執行一些數值模擬的工作,而去執行 csh 這個 C shell , 那麼此時我就取得了 non-login shell 了。

    另外一個例子是,當我以 X Window 的環境登入 Linux 時,我們不是可以使用『終端機』來開啟 shell 嗎?當登入 Linux 的時候所取得的那個 X 的環境也可以讀入 login shell 的。因此,在 X 環境下所啟動的終端機 (shell),那些 shell 所使用的環境設定檔都是 non-login shell 喔!

    login 與 non-login shell 的差異除了取得的時機不同之外,其實他們讀取的環境設定檔也不相同。 我們上頭說過一些個人的環境設定檔案了吧?那麼這兩種類型的 shell 該讀取什麼檔案呢? 當登入 Linux ,亦即是取得 login shell 時,會讀取 ~/.bash_profile, ~/.bash_login, ~/.profile, 這三個檔案的優先順序已經在上面提過,自行參考一下。至於在取得 login shell 後繼續動作的其他 non-login shell ,讀取的就是僅有 ~/.bashrc 囉~。而大部分的 linux distributions 都會將 ~/.bash_profile 的內容指到 ~/.bashrc 去,這樣比較簡單囉~

    終端機的環境設定: stty, set

    什麼叫做『終端機環境』啊?!我們在 首次登入 Linux 時就提過,可以在 tty1 ~ tty6 這六個文字介面的終端機 (terminal) 環境中登入,那麼登入的時候我們可以取得一些字元設定的功能喔! 舉例來說,我們可以利用倒退鍵 (backspace,就是那個←符號的按鍵) 來刪除命令列上的字元, 也可以使用 [ctrl]+c 來強制終止一個指令的運行,當輸入錯誤時,就會有聲音跑出來警告。這是怎麼辦到的呢? 很簡單啊!因為登入終端機的時候,會自動的取得一些終端機的輸入環境的設定啊!

    事實上,目前我們使用的 Linux distributions 都幫我們作了最棒的使用者環境了, 所以大家可以不用擔心操作環境的問題。不過,在某些 Unix like 的機器中,還是可能需要動用一些手腳, 才能夠讓我們的輸入比較快樂~舉例來說,利用 [backspace] 刪除,要比利用 [Del] 按鍵來的順手吧! 但是某些 Unix 偏偏是以 [del] 來進行字元的刪除啊!所以,這個時候就可以動動手腳囉~

    那麼如何查閱目前的一些按鍵內容呢?可以利用 stty (setting tty 終端機的意思) 呢! stty 也可以幫助設定終端機的輸入按鍵代表意義喔!
    [root@linux ~]# stty [-a]
    參數:
    -a  :將目前所有的 stty 參數列出來;
    範例:
    範例一:列出所有的按鍵與按鍵內容
    [root@linux ~]# stty -a
    speed 38400 baud; rows 40; columns 80; line = 0;
    intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ; 
    eol2 = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase 
    = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
    -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
    -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl 
    ixon -ixoff -iuclc -ixany -imaxbel opost -olcuc -ocrnl onlcr -onocr 
    -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten 
    echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
    
    我們可以利用 stty -a 來列出目前環境中所有的按鍵列表,在上頭的列表當中,需要注意的是特殊字體那幾個, 此外,如果出現 ^ 表示 [Ctrl] 那個按鍵的意思。舉例來說, intr = ^C 表示利用 [ctrl] + c 來達成的。 幾個重要的代表意義是:
    • eof : End of file 的意思,代表『結束輸入』。
    • erase : 向後刪除字元,
    • intr : 送出一個 interrupt (中斷) 的訊號給目前正在 run 的程序;
    • kill : 刪除在目前指令列上的所有文字;
    • quit : 送出一個 quit 的訊號給目前正在 run 的程序;
    • start : 在某個程序停止後,重新啟動他的 output
    • stop : 停止目前螢幕的輸出;
    • susp : 送出一個 terminal stop 的訊號給正在 run 的程序。
    記不記得我們講過 Linux 底下的幾個熱鍵 啊?沒錯! 就是這個 stty 設定值內的 intr / eof 囉~至於刪除字元,就是 erase 那個設定值啦! 如果你想要用 [ctrl]+h 來進行字元的刪除,那麼可以下達:
    [root@linux ~]# stty erase ^h
    
    那麼從此之後,你的刪除字元就得要使用 [ctrl]+h 囉,按下 [backspace] 則會出現 ^? 字樣呢! 如果想要回復利用 [backspace] ,就下達 stty erase ^? 即可啊! 至於更多的 stty 說明,記得參考一下 man stty 的內容喔!

    除了 stty 之外,其實我們的 bash 還有自己的一些終端機設定值呢!那就是利用 set 來設定的! 我們之前提到一些變數時,可以利用 set 來顯示,除此之外,其實 set 還可以幫我們設定整個指令輸出/輸入的環境。 例如記錄歷史命令、顯示錯誤內容等等。
    [root@linux ~]# set [-uvCHhmBx]
    參數:
    -u  :預設不啟用。若啟用後,當使用未設定變數時,會顯示錯誤訊息;
    -v  :預設不啟用。若啟用後,在訊息被輸出前,會先顯示訊息的原始內容;
    -x  :預設不啟用。若啟用後,在指令被執行前,會顯示指令內容(前面有 ++ 符號)
    -h  :預設啟用。與歷史命令有關(下節介紹);
    -H  :預設啟用。與歷史命令有關(下節介紹);
    -m  :預設啟用。與工作管理有關(未來介紹);
    -B  :預設啟用。與刮號 [] 的作用有關;
    -C  :預設不啟用。若使用 >  等,則若檔案存在時,該檔案不會被覆蓋。
    範例:
    範例一:顯示目前所有的 set 設定值
    [root@linux ~]# echo $-
    himBH
    # 那個 $- 變數內容就是 set 的所有設定啦! bash 預設是 himBH 喔!
    
    範例二:設定 "若使用未定義變數時,則顯示錯誤訊息" 
    [root@linux ~]# set -u
    [root@linux ~]# echo $vbirding
    -bash: vbirding: unbound variable
    # 預設情況下,未設定/未宣告 的變數都會是『空的』,不過,若設定 -u 參數,
    # 那麼當使用未設定的變數時,就會有問題啦!很多的 shell 都預設啟用 -u 參數。
    # 若要取消這個參數,輸入 set +u 即可!
    
    範例三:執行前,顯示該指令內容。
    [root@linux ~]# set -x
    [root@linux ~]# echo $HOME
    + echo /root
    /root
    ++ echo -ne '\033]0;root@linux:~\007'
    # 看見否?要輸出的指令都會先被列印到螢幕上喔!前面會多出 + 的符號!
    
    另外,其實我們還有其他的按鍵設定功能呢!就是在 /etc/inputrc 這個檔案裡面設定。
    [root@linux ~]# cat /etc/inputrc
    # do not bell on tab-completion
    #set bell-style none
    
    set meta-flag on
    set input-meta on
    set convert-meta off
    set output-meta on
    .....以下省略.....
    
    還有例如 /etc/DIR_COLORS* 與 /etc/termcap 等,也都是與終端機有關的環境設定檔案呢! 不過,事實上,鳥哥並不建議您修改 tty 的環境呢,這是因為 bash 的環境已經設定的很親和了, 我們不需要額外的設定或者修改,否則反而會產生一些困擾。不過,寫在這裡的資料, 只是希望大家能夠清楚的知道我們的終端機是如何進行設定的喔! ^_^

    萬用字元與特殊符號:

    嘿嘿!在 bash 裡頭還支援一些萬用字元喔 (wild card) !多了這些萬用字元, 我們利用 bash 處理資料就更方便了!底下我們列出一些常用的萬用字元喔:

    符號內容
    *萬用字元,代表 0 個或多個字元(或數字)
    ?萬用字元,代表『一定有』一個字母
    #註解,這個最常被使用在 script 當中,視為說明!
    \跳脫符號,將『特殊字元或萬用字元』還原成一般字元
    |分隔兩個管線命令的界定;
    ;連續性命令的界定(注意!與管線命令並不相同)
    ~使用者的家目錄
    $亦即是變數之前需要加的變數取代值
    &將指令變成背景下工作
    !邏輯運算意義上的『非』 not 的意思!
    /路徑分隔的符號
    >, >>輸出導向,分別是『取代』與『累加』
    '單引號,不具有變數置換的功能
    "具有變數置換的功能!
    ` `兩個『 ` 』中間為可以先執行的指令!
    ( )在中間為子 shell 的起始與結束
    [ ]在中間為字元的組合
    { }在中間為命令區塊的組合!
    組合按鍵執行結果
    Ctrl + C終止目前的命令
    Ctrl + D輸入結束(EOF),例如郵件結束的時候;
    Ctrl + M就是 Enter 啦!
    Ctrl + S暫停螢幕的輸出
    Ctrl + Q恢復螢幕的輸出
    Ctrl + U在提示字元下,將整列命令刪除
    Ctrl + Z『暫停』目前的命令

    在上面的『按鍵組合』當中,有沒有發現跟上個小節很相似的內容啊!? 呵呵~沒錯啦!那些組合鍵都可以在 stty 當中來進行不同的設定的!好玩吧! 至於上面的萬用字元當中,最常用的就屬 *, ?, [] 及 ` 了!我們提幾個簡單的例子:
    [root@linux ~]# ls test*      <==那個 * 代表後面不論接幾個字元都予以接受
    [root@linux ~]# ls test?      <==那個 ? 代表後面『一定』要接『一個』字元
    [root@linux ~]# ls test???    <==那個 ??? 代表『一定要接三個』字元!
    [root@linux ~]# cp test[1-5] /tmp
    # 將 test1, test2, test3, test4, test5 若存在的話,就拷貝到 /tmp 
    [root@linux ~]# cp test[!1-5] /tmp
    # 只要不是 test1, test2, test3, test4, test5 之外的其他 test? ,
    # 若存在的話,就拷貝到 /tmp 
    [root@linux ~]# cd /lib/modules/`uname -r`/kernel/drivers
    # 被 ` ` 括起來的內容『會先執行』
    
    上面幾個例子相當的有趣!尤其是最後面兩個!需要注意的是, [1-5] 裡面『代表只有一個字元』但是範圍可以由 1-5 ,這樣來說的話,那麼我們如果允許『只要檔名裡面含有至少一個大寫字元』時,就可以將檔案 copy 出來的話,可以這樣做:
      cp *[A-Z]* /tmp
    很有趣吧?!也就是說『 [ ] 謹代表一個字元,而這個字元的定義可以是範圍(-), 可以是指定項目,也可以是兩者並存。 』舉例來說,我想要找出在 /etc/ 底下所有含有數字的檔案, 可以這樣:
      ls -lda /etc/*[0-9]*
    但如果我只想要找出含有 3 及 5 的檔名的檔案呢?就會是這樣:
      ls -lda /etc/*[35]*
    如果是『不想要』某些範圍或者是單字呢?就使用 [!] 即可!例如不想要有小寫字元為開頭的檔案:
      ls -lda /etc/[!a-z]*
    很好玩吧!至於那個 ` 是啥?在一串指令當中, `command` 內的指令會先被執行, 執行完的訊息再回傳到外部指令來處理!也就是說:
    1. 系統先執行 uname -r 找出輸出的結果;
    2. 將結果累加在目錄上面,來執行 cd 的功能!
    很棒吧!!另外,這個 quot (`) 的功能,也可以利用 $() 來取代喔!例如:
      cd /lib/modules/$(uname -r)/kernel
    這些基本的功能需要特別來瞭解一下才行呦!至於更多的使用方式, 我們會在後續的正規表示法當中在詳談的!

    資料流重導向

    資料流重導向 (redirect) 由字面上的意思來看,好像就是將『資料給他傳導到其他地方去』的樣子? 呵呵!是啊是啊!沒錯~資料流重導向就是將某個指令執行後應該要出現在螢幕上的資料, 給他傳輸到其他的地方,例如檔案或者是裝置 (例如印表機之類的!)!這玩意兒在 Linux 的文字模式底下可重要的! 尤其是如果我們想要將某些資料儲存下來時,就更有用了!

    什麼是資料流重導向

    好傢伙!什麼是資料流重導向啊?這得要由指令的執行結果談起! 一般來說,如果你要執行一個指令,通常他會是這樣的:

    指令執行過程的資料傳輸情況
    圖三、指令執行過程的資料傳輸情況

    我們執行一個指令的時候,這個指令可能會由檔案讀入資料,經過處理之後,再將資料輸出到螢幕上。 在圖三當中, standard output 與 standard error 分別代表標準輸出與標準錯誤輸出, 這兩個玩意兒預設都是輸出到螢幕上面來的啊!舉個簡單例子來說, 我們下達『 cat /etc/crontab /etc/vbirdsay 』這個指令時,cat 會由 /etc/crontab 與 /etc/vbirdsay 讀入資料, 然後再將資料輸出到螢幕上,不過,因為系統本來就不存在 /etc/vbirdsay 這個檔案, 所以就會顯示錯誤訊息,這個錯誤訊息也會輸出到螢幕上來喔!

    在這樣的過程當中,我們可以將 standard error (簡稱 stderr) 與 standard output (簡稱 stdout) 給他傳送到其他不同的地方,而不是螢幕上頭!傳送的目標處,通常是檔案或者是裝置! 而傳送的指令則是如下所示:
    1. 標準輸入(stdin) :代碼為 0 ,使用 < 或 << ;
    2. 標準輸出(stdout):代碼為 1 ,使用 > 或 >> ;
    3. 標準錯誤輸出(stderr):代碼為 2 ,使用 2> 或 2>> ;
    舉例來說,如果我想要將我目前根目錄下所有的目錄都記錄下來的話,也就是說,將 ls -l / 這個指令的輸出結果儲存下來,就可以:
    [root@linux ~]# ls -l /  >  ~/rootfile
    # 本來 ls -l / 會將根目錄的資料列出到螢幕上;
    # 現在我使用了 > ~/rootfile 後,則本來應該在螢幕上出現的資料
    # 就會被『重新導向』到 ~/rootfile 檔案內了!就可以將該資料儲存!
    
    此時,原本應該在螢幕上面出現的資料通通不見去~因為那些資料都被寫入到 ~/rootfile 去了! 當然,那個檔案的檔名隨便你取啦~如果你下達:『 cat ~/rootfile 』就可以看到原本應該在螢幕上面的資料囉。 那麼如果我再次下達:『 ls -l /home > ~/rootfile 』後,那麼那個 ~/rootfile 檔案的內容變成什麼? 呵呵!變成『僅有 ls -l /home 的資料』而已!咦!原本的 ls -l / 資料就不見了嗎?是的! 因為該檔案的建立方式是:
    1. 該檔案 (本例中是 ~/rootfile) 若不存在,系統會自動的將他建立起來,但是,
    2. 當這個檔案存在的時候,那麼系統就會先將這個檔案內容清空,然後再將資料寫入!
    3. 也就是若以 > 輸出到一個既存檔案中,呵呵,那個檔案就會被覆蓋掉囉!
    那如果我想要將資料累加,不想要將舊的資料刪除,那該如何是好? 呵呵!就利用 >> 就好啦!例如上面的例子中,就變成『ls -l / >> ~/rootfile』 如此一來,當 ~/rootfile 不存在時,系統會主動建立這個檔案,若該檔案已存在, 則資料會在該檔案的最下方累加進去!基本上,指令的下達方式:

    command>
    1>
    2>
    2>>
    <
    裝置或檔案

    當然啦,一串指令的最左邊一定是指令,而在 >,2>,< 右邊的,必須是檔案或裝置才行! 此外,那個 > 會等於 1> ,因為 standard output 代碼是 1 ,可以省略啦! 再者, 1 與 > 之間並沒有空格喔!是緊接在一起的!注意注意!我們底下來玩幾個東西好了:
    範例一:將目前目錄下的檔案資訊全部儲存到 list.txt 檔案中
    [root@linux ~]# ls -al > list.txt
    
    範例二:將根目錄下的資料也儲存到 list.txt 檔案中
    [root@linux ~]# ls -al / >> list.txt
    
    好了,對於『 > , >> 』這兩個東西有一定的概念之後,我們來深入的談一談『資料流重導向』的觀念吧! 如前所述,基本上, Linux 執行的結果中,可以約略的分成『正確輸出』與『錯誤輸出』兩種資料。 例如,當你以一般身份執行 find 這個指令時,例如執行『 find / -name testing 』時,由於你是一般身份,又有些資料夾是不允許一般身份者進入的, 所以囉,當你使用 find 時,就會有錯誤訊息發生了!但同時如果有 testing 這個檔案在你可以進入的資料夾當中,那麼螢幕也會輸出到給你看!因此, 就具有正確的與錯誤的輸出兩種囉!(分別稱為 Stdout 與 Stderror)例如下面為執行結果: 裡面的『 find: /home/root: Permission denied 』就告訴你該資料夾你沒有權限進入, 這就是錯誤的輸出了,那麼『 /home/dmtsai/tseting 』就是正確的輸出了!
    [dmtsai@linux ~]$ find /home -name testing
    find: /home/test1: Permission denied   <== Starndard error
    find: /home/root: Permission denied    <== Starndard error
    find: /home/masda: Permission denied   <== Starndard error
    /home/dmtsai/testing                   <== Starndard output
    
    好了,那麼假如我們想要將資料輸出到 list 這個檔案中呢?執行『 find / -name testing > list 』 會有什麼結果?呵呵,你會發現 list 裡面存了剛剛那個『正確』的輸出資料, 至於螢幕上還是會有錯誤的訊息出現呢!傷腦筋!如果想要將正確的與錯誤的資料分別存入不同的檔案中需要怎麼做?! 呵呵!其實在資料的重導向方面,正確的寫法應該是『 1> 』與『 2> 』才對!但是如果只有 > 則預設是以 1> 來進行資料的!那個 1> 是輸出正確資料, 2> 則是錯誤資料輸出項目。也就是說:
    • 1> :是將正確的資料輸出到指定的地方去
    • 2> :是將錯誤的資料輸出到指定的地方去
    好了,那麼上面的例子中,我們如何將資料輸出到不同的地方去呢?可以這麼寫:
    [dmtsai@linux ~]$ find /home -name testing > list_right 2> list_error
    
    這樣一來,剛剛執行的結果中,有 Permission 的那幾行錯誤資訊都會跑到 list_error 這個檔案中,至於正確的輸出資料則會存到 list_right 這個檔案中囉!這樣可以瞭解了嗎? 如果有點混亂的話,去休息一下再來看看吧!!

    再來,如果我只要正確的資料,錯誤的資訊我不要了呢?呵呵,這個時候 /dev/null 這個垃圾桶就很重要了!/dev/null 是什麼呢? 基本上,那就有點像是一個『黑洞』的垃圾桶功能!當你輸入的任何東西導向到這個虛擬的垃圾桶裝置時, 『他就會憑空消失不見了~~』,這個東西有用的很!例如上面的例子中,我們可以這麼做,來將錯誤的資訊丟掉!
    [dmtsai@linux ~]$ find /home -name testing > list_right 2> /dev/null
    
    很神奇呦! error message 就會『不見了!』呵呵!真高興!另外, 如果我要將資料都寫到同一個檔案中呢?這個時候寫法需要用到特殊寫法,請注意底下的寫法呦!
    [dmtsai@linux ~]$ find /home -name testing > list 2> list  <==錯誤寫法
    [dmtsai@linux ~]$ find /home -name testing > list 2>&1     <==正確寫法
    
    請特別留意這一點呢!同時寫入同一個檔案需要使用 2>&1 才對呦!

    OK!瞭解了 >, 2>, >> 與 /dev/null 之後,那麼那個 < 又是什麼呀!?呵呵!以最簡單的說法來說, 那就是『將原本需要由鍵盤輸入的資料,經由檔案來讀入』的意思。 舉例來說,我們可以使用 cat 在鍵盤上面輸入一些資料,然後寫入一個檔案內,例如:
    [root@linux ~]# cat > catfile
    testing
    cat file test
    <==這裡按下 [ctrl]+d 結束輸入來離開!
    
    此時就會有 catfile 這個檔案產生,而且該檔案的內容就是剛剛輸入的內容喔。 那麼,我是否可以使用其他檔案來取代鍵盤輸入呢?可以啊!這樣做!
    [root@linux ~]# cat > catfile < somefile
    
    我可以先編輯 somefile ,然後再以上述的指令來將資料輸出到 catfile 去呢!這樣可以理解了嗎? 能夠理解 < 之後,再來則是怪可怕一把的 << 這個連續兩個小於的符號了~ 他代表的是『結束的輸入字元』的意思!舉例來講:『我要用 cat 直接將輸入的訊息輸出到 catfile 中, 且當輸入 eof 時,該次輸入就結束』,那我可以這樣做:
    [root@linux ~]# cat > catfile <<eof
    > This is a test testing
    > OK now stop
    > eof  <==輸入這個玩意兒,嘿!立刻就結束了!
    
    看到了嗎?利用 << 右側的控制字元,我們可以終止一次輸入, 而不必輸入 [crtl]+d 來結束哩!這對程式寫作很有幫助喔!好了,那麼為何要使用命令輸出重導向呢? 這個問題一定會困擾你一下下的,如果你從來都沒有寫過 script 的話!好了,我們來說一說吧!
    • 當螢幕輸出的資訊很重要,而且我們需要將他存下來的時候;
    • 背景執行中的程式,不希望他干擾螢幕正常的輸出結果時;
    • 一些系統的例行命令(例如寫在 /etc/crontab 中的檔案)的執行結果,希望他可以存下來時;
    • 一些執行命令,我們已經知道他可能的錯誤訊息,所以想以『 2> /dev/null 』將他丟掉時;
    • 錯誤訊息與正確訊息需要分別輸出時。
    當然還有很多很多的功能的,最簡單的就是網友們常常問到的:『 為何我的 root 都會收到系統 crontab 寄來的錯誤訊息呢』這個咚咚是常見的錯誤, 而如果我們已經知道這個錯誤訊息是可以忽略的時候,嗯!『 2> errorfile 』這個功能就很重要了吧! 瞭解了嗎??

    命令執行的判斷依據: ; , &&, ||

    在某些時候,我們希望可以一次執行多個指令,例如關機時,希望我可以先執行兩次 sync ,然後才 shutdown 電腦,那麼可以怎麼作呢?這樣做呀:
    [root@linux ~]# sync; sync; shutdown -h now
    
    在指令與指令中間利用分號 (;) 來隔開,這樣一來,分號前的指令執行完後, 就會立刻接著執行後面的指令了。這真是方便啊~再來,換個角度來想, 萬一我想要在某個目錄底下建立一個檔案,也就是說,如果該目錄存在的話, 那我才建立這個檔案,如果不存在,那就算了~目錄是否存在可以使用一些 bash 提供的判斷式功能, 但這裡假設我不曉得那個指令,但我知道我可以使用 ls 來判斷是否有該目錄的存在, 也就是說,我可以利用 ls directoryname 判斷是否存在,然後以 touch 建立一個檔案, 這兩個指令有相關性,那該如何寫呢?呵呵!可以利用 && 來作喔!
    [root@linux ~]# ls /tmp && touch /tmp/testingagin
    
    是否記得我們在變數的章節裡面談過這個奇怪的變數『 $? 』呢? 如果指令執行結果沒有錯誤訊息,那就會回傳 $?=0 ,如果有錯誤, 那回傳值就不會是 0 啊!經由這樣的判斷,我們也可以利用 && 來決定, 當前面的指令執行結果為正確 (例如:僅有 standard output 時),就可以接著執行後續的指令, 否則就予以略過!因此,當 ls /tmp 沒有問題,那麼就會接著執行 touch /tmp/testingagin 了! 萬一是這樣:
    [root@linux ~]# ls /vbird && touch /vbird/test
    
    因為我的系統裡面根本就不可能存在 /vbird 這個目錄呢!所以,執行 ls /vbird 就會回傳錯誤, 那麼後續的 touch /vbird/test 自然就不會動作囉!瞭解嗎?

    再換個角度來想,如果我想要當某個檔案不存在時,就去建立那個檔案, 否則就略過呢?很簡單啊~可以這樣做:
    [root@linux ~]# ls /tmp/vbirding || touch /tmp/vbirding
    
    那個 || 剛好完全跟 && 相反,當前一個指令有錯誤時,在 || 後面的指令才會被執行! (要注意,那個 | 是兩個 | ,而 | 按鍵則是反斜線 \ 同一個按鍵, 因此,按下 [Shift] 加上 [\] 就會出現那個 | 囉!) 因此,簡單的來說,當 ls /tmp/vbirding 發生錯誤時,才會使用 touch /tmp/vbirding 去建立這個檔案的意思。 是否很有趣啊?這個 || 及 && 對於系統管理員在管理某些檔案權限、存在等問題時, 可是很有用的東西喔!好了,現在我們來玩比較難一點的,看看底下的例題:

    例題:以 ls 測試 /tmp/vbirding 是否存在,若存在則顯示 "exist" ,若不存在,則顯示 "not exist"!
    答:
      這又牽涉到邏輯判斷的問題,如果存在就顯示某個資料,若不存在就顯示其他資料, 那我可以這樣做:

        ls /tmp/vbirding && echo "exist" || echo "not exist"

      意思是說,當 ls /tmp/vbirding 執行後,若正確,就執行 echo "exist" ,若有問題,就執行 echo "not exist" ! 那如果我寫成:

        ls /tmp/vbirding || echo "not exist" && echo "exist"

      對不對啊?這其實是有問題的,為什麼呢?因為指令是一個一個往下執行,因此,在上面的例子當中,如果 /tmp/vbirding 不存在時,他會:

      1. 若 ls /tmp/vbirding 不存在,因此回傳一個非為 0 的數值;
      2. 接下來經過 || 的判斷,發現前一個指令回傳非為 0 的數值,因此,程式開始執行 echo "not exist" ,而 echo "not exist" 程式肯定可以執行成功,因此會回傳一個 0 值給後面的指令;
      3. 經過 && 的判斷,咦!是 0 啊!所以就開始執行 echo "exist" 。

      所以啊,嘿嘿!第二個例子裡面竟然會同時出現 not exist 與 exist 呢!真神奇~

    經過這個範例的練習,您應該會瞭解,由於指令是一個接著一個去執行的,因此,如果真要使用判斷, 那麼這個 && 與 || 的順序就不能搞錯~一般來說,判斷式最多會有三個,也就是:
      command1 && command2 || command3
    而且順序通常不會變,因為一般來說, command2 與 command3 會放置肯定可以執行成功的指令, 因此,依據上面例題的邏輯分析,您就會曉得為何要如此放置囉~這很有用的啦! 而且.....考試也很常考~

    管線命令 (pipe)

    就如同前面所說的, bash 命令執行的時候有輸出的資料會出現! 那麼如果這群資料必需要經過幾道手續之後才能得到我們所想要的格式,應該如何來設定? 這就牽涉到管線命令的問題了 (pipe) ,管線命令使用的是『 | 』這個界定符號! 另外,管線命令與『連續下達命令』是不一樣的呦! 這點底下我們會再說明。底下我們先舉一個例子來說明一下簡單的管線命令。

    假設我們想要知道 /etc/ 底下有多少檔案,那麼可以利用 ls /etc 來查閱,不過, 因為 /etc 底下的檔案太多,導致一口氣就將螢幕塞滿了~不知道前面輸出的內容是啥?此時,我們可以透過 less 指令的協助,利用:
    [root@linux ~]# ls -al /etc | less
    
    嘿嘿!如此一來,使用 ls 指令輸出後的內容,就能夠被 less 讀取, 並且利用 less 的功能,我們就能夠前後翻動相關的資訊了!很方便是吧?呵呵! 我們就來瞭解一下這個管線命令『 | 』的用途吧!

    這個管線命令『 | 』僅能處理經由前面一個指令傳來的正確資訊,也就是 standard output ( STDOUT ) 的資訊,對於 stdandard error 並沒有直接處理的能力,請記得。那麼整體的管線命令可以使用下圖表示之:

    管線命令的處理示意圖
    圖四、管線命令的處理示意圖

    在每個管線的前後部分都是『指令』呢!而後一個指令的輸入乃是由前一個指令的輸出而來的! 不過,要注意的是,在 Linux 的環境中,很多的訊息處理都是以『行』為單位~ 也就是以是否具有 [Enter] 標誌 (CR) 來作為一段處理的依據喔! 底下我們來談一談一些基本的管線命令指令介紹:

    擷取命令: cut, grep

    什麼是擷取命令啊?說穿了,就是將一段資料經過分析後,取出我們所想要的。 或者是,經由分析關鍵字,取得我們所想要的那一行! 不過,要注意的是,一般來說,擷取訊息通常是針對『一行一行』來分析的, 並不是整篇訊息分析的喔~底下我們介紹兩個很常用的訊息擷取命令:


  • cut
  • cut 不就是『切』嗎?沒錯啦!這個指令可以將一段訊息的某一段給他『切』出來~ 處理的訊息是以『行』為單位喔!底下我們就來談一談:
    [root@linux ~]# cut -d'分隔字元' -f fields
    [root@linux ~]# cut -c 字元區間
    參數:
    -d  :後面接分隔字元。與 -f 一起使用;
    -f  :依據 -d 的分隔字元將一段訊息分割成為數段,用 -f 取出第幾段的意思;
    -c  :以字元 (characters) 的單位取出固定字元區間;
    範例:
    
    範例一:將 PATH 變數取出,我要找出第五個路徑。
    [root@linux ~]# echo $PATH
    /bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:
    [root@linux ~]# echo $PATH | cut -d ':' -f 5
    # 嘿嘿!如此一來,就會出現 /usr/local/bin 這個目錄名稱!
    # 因為我們是以 : 作為分隔符號,第五個就是 /usr/local/bin 啊!
    # 那麼如果想要列出第 3 與第 5 呢?,就是這樣:
    [root@linux ~]# echo $PATH | cut -d ':' -f 3,5
    
    範例二:將 export 輸出的訊息,取得第 12 字元以後的所有字串
    [root@linux ~]# export
    declare -x HISTSIZE="1000"
    declare -x INPUTRC="/etc/inputrc"
    declare -x KDEDIR="/usr"
    declare -x LANG="zh_TW.big5"
    ......其他省略......
    [root@linux ~]# export | cut -c 12-
    HISTSIZE="1000"
    INPUTRC="/etc/inputrc"
    KDEDIR="/usr"
    LANG="zh_TW.big5"
    ......其他省略......
    # 知道怎麼回事了吧?用 -c 可以處理比較具有格式的輸出資料!
    # 我們還可以指定某個範圍的值,例如第 12-20 的字元,就是 cut -c 12-20 等等!
    
    範例三:用 last 將這個月登入者的資訊中,僅留下使用者大名
    [root@linux ~]# last
    vbird  tty1  192.168.1.28   Mon Aug 15 11:55 - 17:48  (05:53)
    vbird  tty1  192.168.1.28   Mon Aug 15 10:17 - 11:54  (01:37)
    [root@linux ~]# last | cut -d ' ' -f 1
    # 用 last 可以取得最近一個月登入主機的使用者資訊,
    # 而我們可以利用空白字元的間隔,取出第一個資訊,就是使用者帳號囉!
    # 但是因為 vbird tty1 之間空格有好幾個,並非僅有一個,所以,如果要找出 
    # tty1 其實不能以 cut -d ' ' -f 1,2 喔!輸出的結果會不是我們想要的。
    
    這個 cut 實在很好用!不過,說真的,除非你常常在分析 log 檔案,否則使用到 cut 的機會並不多!好了! cut 主要的用途在於將『同一行裡面的資料進行分解!』, 最常使用在分析一些數據或文字資料的時候!這是因為有時候我們會以某些字元當作分割的參數, 然後來將資料加以切割,以取得我們所需要的資料。我也很常使用這個功能呢!尤其是在分析 log 檔案的時候!不過, cut 在處理多空格相連的資料時,可能會比較吃力一點~


  • grep
  • 剛剛的 cut 是將一行訊息當中,取出某部分我們想要的,而 grep 則是分析一行訊息, 若當中有我們所需要的資訊,就將該行拿出來~簡單的語法是這樣的:
    [root@linux ~]# grep [-acinv] '搜尋字串' filename
    參數:
    -a :將 binary 檔案以 text 檔案的方式搜尋資料
    -c :計算找到 '搜尋字串' 的次數
    -i :忽略大小寫的不同,所以大小寫視為相同
    -n :順便輸出行號
    -v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
    範例:
    
    範例一:將 last 當中,有出現 root 的那一行就取出來;
    [root@linux ~]# last | grep 'root'
    
    範例二:與範例一相反,只要沒有 root 的就取出!
    [root@linux ~]# last | grep -v 'root'
    
    範例三:在 last 的輸出訊息中,只要有 root 就取出,並且僅取第一欄
    [root@linux ~]# last | grep 'root' |cut -d ' ' -f1
    # 在取出 root 之後,利用上個指令 cut 的處理,就能夠僅取得第一欄囉!
    
    grep 是個很棒的指令喔!他支援的語法實在是太多了~用在正規表示法裡頭, 能夠處理的資料實在是多的很~不過,我們這裡先不談正規表示法~下一章再來說明~ 您先瞭解一下, grep 可以解析一行文字,取得關鍵字,若該行有存在關鍵字, 就會整行列出來!

    排序命令: sort, wc, uniq

    很多時候,我們都會去計算一次資料裡頭的相同型態的資料總數,舉例來說, 使用 last 可以查得這個月份有登入主機者的身份。那麼我可以針對每個使用者查出他們的總登入次數嗎? 此時就得要排序與計算之類的指令來輔助了!底下我們介紹幾個好用的排序與統計指令喔!


  • sort
  • sort 是很有趣的指令,他可以幫我們進行排序,而且可以依據不同的資料型態來排序喔! 例如數字與文字的排序就不一樣。此外,排序的字元與語系的編碼有關,因此, 如果您需要排序時,建議使用 LC_ALL=C 來讓語系統一,資料排序比較好一些。
    [root@linux ~]# sort [-fbMnrtuk] [file or stdin]
    參數:
    -f  :忽略大小寫的差異,例如 A 與 a 視為編碼相同;
    -b  :忽略最前面的空白字元部分;
    -M  :以月份的名字來排序,例如 JAN, DEC 等等的排序方法;
    -n  :使用『純數字』進行排序(預設是以文字型態來排序的);
    -r  :反向排序;
    -u  :就是 uniq ,相同的資料中,僅出現一行代表;
    -t  :分隔符號,預設是 tab 鍵;
    -k  :以那個區間 (field) 來進行排序的意思,
    範例:
    
    範例一:個人帳號都記錄在 /etc/passwd 下,請將帳號進行排序。
    [root@linux ~]# cat /etc/passwd | sort
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    apache:x:48:48:Apache:/var/www:/sbin/nologin
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    # 我省略很多的輸出~由上面的資料看起來, sort 是預設『以第一個』資料來排序,
    # 而且預設是以『文字』型態來排序的喔!所以由 a 開始排到最後囉!
    
    範例二:/etc/passwd 內容是以 : 來分隔的,我想以第三欄來排序,該如何?
    [root@linux ~]# cat /etc/passwd | sort -t ':' -k 3
    root:x:0:0:root:/root:/bin/bash
    iiimd:x:100:101:IIIMF server:/usr/lib/iiim:/sbin/nologin
    uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    bin:x:1:1:bin:/bin:/sbin/nologin
    games:x:12:100:games:/usr/games:/sbin/nologin
    # 看到特殊字體的輸出部分了吧?怎麼會這樣排列啊?呵呵!沒錯啦~
    # 如果是以文字型態來排序的話,原本就會是這樣,想要使用數字排序:
    # cat /etc/passwd | sort -t ':' -k 3 -n
    # 這樣才行啊!用那個 -n 來告知 sort 以數字來排序啊!
    
    範例三:利用 last ,將輸出的資料僅取帳號,並加以排序
    [root@linux ~]# last | cut -d ' ' -f1 | sort
    
    sort 同樣是很常用的指令呢!因為我們常常需要比較一些資訊啦! 舉個上面的第二個例子來說好了!今天假設你有很多的帳號,而且你想要知道最大的使用者 ID 目前到哪一號了!呵呵!使用 sort 一下子就可以知道答案咯!當然其使用還不止此啦! 有空的話不妨玩一玩!


  • uniq
  • 如果我排序完成了,想要將重複的資料僅列出一個顯示,可以怎麼做呢?
    [root@linux ~]# uniq [-ic]
    參數:
    -i  :忽略大小寫字元的不同;
    -c  :進行計數
    範例:
    
    範例一:使用 last 將帳號列出,僅取出帳號欄,進行排序後僅取出一位;
    [root@linux ~]# last | cut -d ' ' -f1 | sort | uniq
    
    範例二:承上題,如果我還想要知道每個人的登入總次數呢?
    [root@linux ~]# last | cut -d ' ' -f1 | sort | uniq -c
    
    這個指令用來將『重複的行刪除掉只顯示一個』,舉個例子來說, 你要知道這個月份登入你主機的使用者有誰,而不在乎他的登入次數,那麼就使用上面的範例, (1)先將所有的資料列出;(2)再將人名獨立出來;(3)經過排序;(4)只顯示一個! 由於這個指令是在將重複的東西減少,所以當然需要『配合排序過的檔案』來處理囉!


  • wc
  • 如果我想要知道 /etc/man.config 這個檔案裡面有多少字?多少行?多少字元的話, 可以怎麼做呢?其實可以利用 wc 這個指令來達成喔!他可以幫我們計算輸出的訊息的整體資料!
    [root@linux ~]# wc [-lwm]
    參數:
    -l  :僅列出行;
    -w  :僅列出多少字(英文單字);
    -m  :多少字元;
    範例:
    
    範例一:那個 /etc/man.config 裡面到底有多少相關字、行、字元數?
    [root@linux ~]# cat /etc/man.config | wc 
        138     709    4506
    # 輸出的三個數字中,分別代表: 『行、字數、字元數』
    
    範例二:我知道使用 last 可以輸出登入者,但是 last 最後兩行並非帳號內容,
            那麼請問,我該如何以一行指令串取得這個月份登入系統的總人次?
    [root@linux ~]# last | grep [a-zA-Z] | grep -v 'wtmp' | wc -l 
    # 由於 last 會輸出空白行與 wtmp 字樣在最底下兩行,因此,我利用
    # grep 取出非空白行,以及去除 wtmp 那一行,在計算行數,就能夠瞭解囉!
    
    wc 也可以當作指令?呵呵!這可不是上洗手間的 WC 呢! 這是相當有用的計算檔案內容的一個工具組喔!舉個例子來說, 當你要知道目前你的帳號檔案中有多少個帳號時,就使用這個方法:『 cat /etc/passwd | wc -l 』啦!因為 /etc/passwd 裡頭一行代表一個使用者呀! 所以知道行數就曉得有多少的帳號在裡頭了!而如果要計算一個檔案裡頭有多少個字元時,呵呵!就使用 wc -c 這個參數吧!

    雙向重導向: tee

    想個簡單的東西,我們由前一節知道 > 會將資料流整個傳送給檔案或裝置, 因此我們除非去讀取該檔案或裝置,否則就無法繼續利用這個資料流。 萬一我想要將這個資料流的處理過程中,將某段訊息存下來,應該怎麼做?呵呵! 利用 tee 就可以囉~我們可以這樣簡單的看一下:

    tee 的工作流程
    圖五、tee 的工作流程

    同時將資料流分送到檔案去與螢幕 (screen);而輸出到螢幕的,其實就是 stdout ,可以讓下個指令繼續處理喔!
    [root@linux ~]# tee [-a] file
    參數:
    -a  :以累加 (append) 的方式,將資料加入 file 當中!
    範例:
    [root@linux ~]# last | tee last.list | cut -d " " -f1
    # 這個範例可以讓我們將 last 的輸出存一份到 last.list 檔案中;
    [root@linux ~]# ls -l /home | tee ~/homefile | more
    # 這個範例則是將 ls 的資料存一份到 ~/homefile ,同時螢幕也有輸出訊息!
    [root@linux ~]# ls -l / | tee -a ~/homefile | more
    # 要注意: tee 後接的檔案會被覆蓋,所以,我們要加上 -a 
    # 這個參數才能將訊息累加。
    
    有沒有發現在命令重導向的時候,如果我們要將資料送出到檔案的時候, 螢幕上就不會出現任何的資料!那麼如果我們需要將資料同時顯示在螢幕上跟檔案中呢?呵呵!這個時候就需要 tee 這個指令囉!使用 last 可以查看到這個月份的登入資料,而使用了 tee 之後,會將資料同時傳給下一個命令去執行,也會將資料寫入 last.list 這個檔案中!也是個好幫手!

    字元轉換命令: tr, col, join, paste, expand

    我們在 vi 文書處理器 章節當中,提到過 DOS 斷行字元與 Unix 斷行字元的不同, 並且可以使用 dos2unix 與 unix2dos 來完成轉換。好了,那麼思考一下,是否還有其他常用的字元替代? 舉例來說,要將大寫改成小寫,或者是 [tab] 按鍵轉成空白鍵?還有,如何將兩篇訊息整合成一篇? 底下我們就來介紹一下這些字元轉換命令在管線當中的使用方法:


  • tr
  • tr 可以用來刪除一段訊息當中的文字,或者是進行文字訊息的替換!
    [root@linux ~]# tr [-ds] SET1 ...
    參數:
    -d  :刪除訊息當中的 SET1 這個字串;
    -s  :取代掉重複的字元!
    範例:
    
    範例一:將 last 輸出的訊息中,所有的小寫變成大寫字元:
    [root@linux ~]# last | tr '[a-z]' '[A-Z]'
    
    範例二:將 /etc/passwd 輸出的訊息中,將冒號 (:) 刪除
    [root@linux ~]# cat /etc/passwd | tr -d ':'
    
    範例三:將 DOS 檔案的斷行字元 ^M 符號刪除:
    [root@linux ~]# cat /home/test/dostxt | tr -d '\r' > dostxt-noM
    # 那個 \r 指的是 DOS 的斷行字元,關於更多的字符,請參考 man tr
    
    其實這個指令也可以寫在『正規表示法』裡頭!因為他也是由正規表示法的方式來取代資料的! 以上面的例子來說,使用 [] 可以設定一串字呢! 也常常用來取代檔案中的怪異符號! 例如上面第三個例子當中,可以去除 DOS 檔案留下來的 ^M 這個斷行的符號!這東西相當的有用!相信處理 Linux & Windows 系統中的人們最麻煩的一件事就是這個事情啦!亦即是 DOS 底下會自動的在每行行尾加入 ^M 這個斷行符號!這個時候我們可以使用這個 tr 來將 ^M 去除! ^M 可以使用 \r 來代替之!


  • col
  • [root@linux ~]# col [-x]
    參數:
    -x  :將 tab 鍵轉換成對等的空白鍵
    範例:
    [root@linux ~]# cat -A /etc/man.config  <==此時會看到很多 ^I 的符號,那就是 tab
    [root@linux ~]# cat /etc/man.config | col -x | cat -A | more
    # 嘿嘿!如此一來, [tab] 按鍵會被取代成為空白鍵,輸出就美觀多了!
    
    雖然 col 有他特殊的用途,不過,很多時候,他可以用來簡單的處理將 [tab] 按鍵取代成為空白鍵! 例如上面的例子當中,如果使用 cat -A 則 [tab] 會以 ^I 來表示。 但經過 col -x 的處理,則會將 [tab] 取代成為對等的空白鍵!


  • join
  • join 看字面上的意義 (加入/參加) 就可以知道,他是在處理兩個檔案之間的資料, 而且,主要是在處理『兩個檔案當中,有 "相同資料" 的那一行,將他加在一起』的意思。我們利用底下的簡單例子來說明:
    [root@linux ~]# join [-ti12] file1 file2
    參數:
    -t  :join 預設以空白字元分隔資料,並且比對『第一個欄位』的資料,
          如果兩個檔案相同,則將兩筆資料聯成一行,且第一個欄位放在第一個!
    -i  :忽略大小寫的差異;
    -1  :這個是數字的 1 ,代表『第一個檔案要用那個欄位來分析』的意思;
    -2  :代表『第二個檔案要用那個欄位來分析』的意思。
    範例:
    
    範例一:用 root 的身份,將 /etc/passwd 與 /etc/shadow 相關資料整合成一欄
    [root@linux ~]# join -t ':' /etc/passwd /etc/shadow
    bin:x:1:1:bin:/bin:/sbin/nologin:*:12959:0:99999:7:::
    daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:12959:0:99999:7:::
    adm:x:3:4:adm:/var/adm:/sbin/nologin:*:12959:0:99999:7:::
    # 因為 /etc/shadow 的權限問題,所以這裡必須是 root 才能動作!而 /etc/passwd 
    # 與 /etc/shadow 都是以 : 來分隔欄位,所以必須要使用 -t ':' 規範欄位分隔字元。
    # 且,因為 /etc/shadow 與 /etc/passwd 剛好都是以第一個欄位為帳號名稱,所以,
    # 就可以將同一行的資料給他貼在一起了!
    # 另外,再仔細看一下 /etc/shadow 的內容與 /etc/passwd 的內容,您會發現,
    # 兩者都以帳號為開始,而上面的輸出資料中您會發現特殊字體部分,那代表
    # 第二個檔案的內容。在第二個檔案的內容部分,由於帳號(第一個欄位)與
    # 第一的檔案是相同的,所以當然就省略掉,因此就成為上面的輸出。
    
    範例二:我們知道 /etc/passwd 第四個欄位是 GID ,那個 GID 記錄在 
            /etc/group 當中的第三個欄位,請問如何將兩個檔案整合?
    [root@linux ~]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group
    0:root:x:0:root:/root:/bin/bash:root:x:
    1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:root,bin,daemon
    2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:root,bin,daemon
    4:adm:x:3:adm:/var/adm:/sbin/nologin:adm:x:root,adm,daemon
    # 這個例子就更明顯了!原本的 /etc/passwd 的第一行內容應該是:
    # root:x:0:0:root:/root:/bin/bash
    # 至於 /etc/group 第一行內容應該是:
    # root:x:0:
    # 我將第一個檔案的第四欄與第二個檔案的第三欄取出,放置到輸出的最前方,
    # 然後將剩下的資料給他加在一起!就成了上面的輸出啦!
    
    這個 join 在處理兩個相關的資料檔案時,就真的是很有幫助的啦! 例如上面的案例當中,我的 /etc/passwd, /etc/shadow, /etc/group 都是有相關性的, 其中 /etc/passwd, /etc/shadow 以帳號為相關性,至於 /etc/passwd, /etc/group 則以所謂的 GID (帳號的數字定義) 來作為他的相關性。根據這個相關性, 我們可以將有關係的資料放置在一起!這在處理資料可是相當有幫助的! 但是上面的例子有點難,希望您可以靜下心好好的看一看原因喔!

    此外,需要特別注意的是,在使用 join 之前,你所需要處理的檔案應該要事先經過排序 (sort) 處理! 否則有些比對的項目會被略過呢!特別注意了!


  • paste
  • 這個 paste 就要比 join 簡單多了!相對於 join 必須要比對兩個檔案的資料相關性, paste 就直接『將兩行貼在一起,且中間以 [tab] 鍵隔開』而已!簡單的使用方法:
    [root@linux ~]# paste [-d] file1 file2
    參數:
    -d  :後面可以接分隔字元。預設是以 [tab] 來分隔的!
    -   :如果 file 部分寫成 - ,表示來自 standard input 的資料的意思。
    範例:
    
    範例一:將 /etc/passwd 與 /etc/shadow 同一行貼在一起
    [root@linux ~]# paste /etc/passwd /etc/shadow
    bin:x:1:1:bin:/bin:/sbin/nologin        bin:*:12959:0:99999:7:::
    daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:12959:0:99999:7:::
    adm:x:3:4:adm:/var/adm:/sbin/nologin    adm:*:12959:0:99999:7:::
    # 注意喔!同一行中間是以 [tab] 按鍵隔開的!
    
    範例二:先將 /etc/group 讀出(用 cat),然後與範例一貼上一起!且僅取出前三行
    [root@linux ~]# cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3
    # 這個例子的重點在那個 - 的使用!那玩意兒常常代表 stdin 喔!
    


  • expand
  • 這玩意兒就是在將 [tab] 按鍵轉成空白鍵啦~可以這樣玩:
    [root@linux ~]# expand [-t] file
    參數:
    -t  :後面可以接數字。一般來說,一個 tab 按鍵可以用 8 個空白鍵取代。
          我們也可以自行定義一個 [tab] 按鍵代表多少個字元呢!
    範例:
    
    範例一:將 /etc/man.config 內行首為 MANPATH 的字樣就取出;僅取前三行;
    [root@linux ~]# grep '^MANPATH' /etc/man.config | head -n 3
    MANPATH /usr/man
    MANPATH /usr/share/man
    MANPATH /usr/local/man
    # 行首的代表標誌為 ^ ,這個我們留待下節介紹!先有概念即可!
    
    範例二:承上,如果我想要將所有的符號都列出來?(用 cat)
    [root@linux ~]# grep '^MANPATH' /etc/man.config | head -n 3 |cat -A
    MANPATH^I/usr/man$
    MANPATH^I/usr/share/man$
    MANPATH^I/usr/local/man$
    # 發現差別了嗎?沒錯~ [tab] 按鍵可以被 cat -A 顯示成為 ^I 
    
    範例三:承上,我將 [tab] 按鍵設定成 6 個字元的話?
    [root@linux ~]# grep '^MANPATH' /etc/man.config | head -n 3 | \
    >  expand -t 6 - | cat -A
    MANPATH     /usr/man$
    MANPATH     /usr/share/man$
    MANPATH     /usr/local/man$
    123456123456123456.....
    # 仔細看一下上面的數字說明,因為我是以 6 個字元來代表一個 [tab] 的長度,所以,
    # MAN... 到 /usr 之間會隔 12 (兩個 [tab]) 個字元喔!如果 tab 改成 9 的話,
    # 情況就又不同了!這裡也不好理解~您可以多設定幾個數字來查閱就曉得!
    
    expand 也是挺好玩的~他會自動將 [tab] 轉成空白鍵~所以,以上面的例子來說, 使用 cat -A 就會查不到 ^I 的字符囉~此外,因為 [tab] 最大的功能就是格式排列整齊! 我們轉成空白鍵後,這個空白鍵也會依據我們自己的定義來增加大小~ 所以,並不是一個 ^I 就會換成 8 個空白喔!這個地方要特別注意的哩! 此外,您也可以參考一下 unexpand 這個將空白轉成 [tab] 的指令功能啊! ^_^

    分割命令: split

    如果你有檔案太大,導致一些攜帶式裝置無法複製的問題,嘿嘿!找 split 就對了! 他可以幫你將一個大檔案,依據檔案大小或行數來分割,就可以將大檔案分割成為小檔案了! 快速又有效啊!真不錯~
    [root@linux ~]# split [-bl] file PREFIX
    參數:
    -b  :後面可接欲分割成的檔案大小,可加單位,例如 b, k, m 等;
    -l  :以行數來進行分割。
    範例:
    
    範例一:我的 /etc/termcap 有七百多K,若想要分成 300K 一個檔案時?
    [root@linux ~]# cd /tmp; split -b 300k /etc/termcap termcap
    [root@linux tmp]# ls -l termcap*
    -rw-rw-r--  1 root root  307200  8月 17 00:25 termcapaa
    -rw-rw-r--  1 root root  307200  8月 17 00:25 termcapab
    -rw-rw-r--  1 root root  184848  8月 17 00:25 termcapac
    # 那個檔名可以隨意取的啦!我們只要寫上前導文字,小檔案就會以
    # xxxaa, xxxab, xxxac 等方式來建立小檔案的!
    
    範例二:如何將上面的三個小檔案合成一個檔案,檔名為 termcapback
    [root@linux tmp]# cat termcap* >> termcapback
    # 很簡單吧?就用資料流重導向就好啦!簡單!
    
    範例三:使用 ls -al / 輸出的資訊中,每十行記錄成一個檔案
    [root@linux tmp]# ls -al / | split -l 10 - lsroot
    # 重點在那個 - 啦!一般來說,如果需要 stdout/stdin 時,但偏偏又沒有檔案,
    # 有的只是 - 時,那麼那個 - 就會被當成 stdin 或 stdout ~
    
    在 Windows 的情況下,你要將檔案分割需要如何作?!傷腦筋吧!呵呵!在 Linux 底下就簡單的多了!你要將檔案分割的話,那麼就使用 -b size 來將一個分割的檔案限制其大小,如果是行數的話,那麼就使用 -l line 來分割!好用的很!如此一來,你就可以輕易的將你的檔案分割成 floppy 的大小,方便你 copy 囉!

    參數代換: xargs

    xargs 是在做什麼的呢?就以字面上的意義來看, x 是加減乘除的乘號,args 則是 arguments (參數) 的意思,所以說,這個玩意兒就是在產生某個指令的參數的意思! xargs 可以讀入 stdin 的資料,並且以空白字元或斷行字元作為分辨,將 stdin 的資料分隔成為 arguments 。 因為是以空白字元作為分隔,所以,如果有一些檔名或者是其他意義的名詞內含有空白字元的時候, xargs 可能就會誤判了~他的用法其實也還滿簡單的!就來看一看先!
    [root@linux ~]# xargs [-0epn] command
    參數:
    -0  :如果輸入的 stdin 含有特殊字元,例如 `, \, 空白鍵等等字元時,這個 -0 參數
          可以將他還原成一般字元。這個參數可以用於特殊狀態喔!
    -e  :這個是 EOF (end of file) 的意思。後面可以接一個字串,當 xargs 分析到
          這個字串時,就會停止繼續工作!
    -p  :在執行每個指令的 argument 時,都會詢問使用者的意思;
    -n  :後面接次數,每次 command 指令執行時,要使用幾個參數的意思。看範例三。
    當 xargs 後面沒有接任何的指令時,預設是以 echo 來進行輸出喔!
    範例:
    
    範例一:將 /etc/passwd 內的第一欄取出,僅取三行,使用 finger 這個指令將每個
            帳號內容秀出來
    [root@linux ~]# cut -d':' -f1 < /etc/passwd |head -n 3| xargs finger
    Login: root                             Name: root
    Directory: /root                        Shell: /bin/bash
    Never logged in.
    No mail.
    No Plan.
    ......底下省略.....
    # 由 finger account 可以取得該帳號的相關說明內容,例如上面的輸出就是 finger root
    # 後的結果。在這個例子當中,我們利用 cut 取出帳號名稱,用 head 取出三個帳號,
    # 最後則是由 xargs 將三個帳號的名稱變成 finger 後面需要的參數!
    
    範例二:同上,但是每次執行 finger 時,都要詢問使用者是否動作?
    [root@linux ~]# cut -d':' -f1 < /etc/passwd |head -n 3| xargs -p finger
    finger root bin daemon ?...y
    ......底下省略.....
    # 呵呵!這個 -p 的參數有趣了吧?!他可以讓使用者的使用過程中,被詢問到每個
    # 指令是否執行!
    
    範例三:將所有的 /etc/passwd 內的帳號都以 finger 查閱,但一次僅查閱五個帳號
    [root@linux ~]# cut -d':' -f1 < /etc/passwd | xargs -p -n 5 finger
    finger root bin daemon adm lp ?...y
    ......底下省略.....
    # 在這裡鳥哥使用了 -p 這個參數來讓您對於 -n 更有概念。一般來說,某些指令後面
    # 可以接的 arguments 是有限制的,不能無限制的累加,此時,我們可以利用 -n
    # 來幫助我們將參數分成數個部分,每個部分分別再以指令來執行!這樣就 OK 啦!^_^
    [root@linux ~]# 
    
    範例四:同上,但是當分析到 lp 就結束這串指令?
    [root@linux ~]# cut -d':' -f1 < /etc/passwd | xargs -p -e'lp' finger
    finger root bin daemon adm ?...
    # 仔細與上面的案例做比較。也同時注意,那個 -e'lp' 是連在一起的,中間沒有空白鍵。
    # 上個例子當中,第五個參數是 lp 啊,那麼我們下達 -e'lp' 後,則分析到 lp
    # 這個字串時,後面的其他 stdin 的內容就會被 xargs 捨棄掉了!
    
    其實,在 man xargs 裡面就有三四個小範例,您可以自行參考一下內容。 此外, xargs 真的是很好用的一個玩意兒!您真的需要好好的參詳參詳!

    關於減號 - 的用途

    管線命令在 bash 的連續的處理程序中是相當重要的!另外,在 log file 的分析當中也是相當重要的一環, 所以請特別留意!另外,在管線命令當中,常常會使用到前一個指令的 stdout 作為這次的 stdin , 某些指令需要用到檔案名稱 (例如 tar) 來進行處理時,該 stdin 與 stdout 可以利用減號 "-" 來替代, 舉例來說:
    [root@linux ~]# tar -cvf - /home | tar -xvf -
    
    上面這個例子是說:『我將 /home 裡面的檔案給他打包,但打包的資料不是紀錄到檔案,而是傳送到 stdout; 經過管線後,將 tar -cvf - /home 傳送給後面的 tar -xvf - 』。後面的這個 - 則是取用前一個指令的 stdout, 因此,我們就不需要使用 file 了!這是很常見的例子喔!注意注意!

    本章習題練習

    ( 要看答案請將滑鼠移動到『答:』底下的空白處,按下左鍵圈選空白處即可察看 )
    • 在 Linux 上可以找到哪些 shell(舉出三個) ?那個檔案記錄可用的 shell ?而 Linux 預設的 shell 是?
    • 1) /bin/bash, /bin/tcsh, /bin/csh
      2) /etc/shells
      3) bash ,亦即是 /bin/bash。
    • 在 shell 環境下,有個提示字元 (prompt),他可以修改嗎?要改什麼?預設的提示字元內容是?
    • 可以修改的,改 PS1 這個變數,這個 PS1 變數的預設內容為:『[\u@\h \W]\$』
    • 如何顯示 HOME 這個環境變數?
    • echo $HOME
    • 如何得知目前的所有變數與環境變數的設定值?
    • 環境變數用 env 而所有變數用 set 即可顯示
    • 我是否可以設定一個變數名稱為 3myhome ?
    • 不行!變數不能以數字做為開頭,參考變數設定規則的內容
    • 在這樣的練習中『A=B』且『B=C』,若我下達『unset $A』,則取消的變數是 A 還是 B?
    • 被取消的是 B 喔,因為 unset $A 相當於 unset B 所以取消的是 B ,A 會繼續存在!
    • 如何取消變數與命令別名的內容?
    • 使用 unset 及 unalias 即可
    • 如何設定一個變數名稱為 name 內容為 It's my name ?
    • name=It\'s\ my\ name 或 name="It's my name"
    • 環境變數檔案的載入順序?
    • 先由 /etc/passwd 取得 bash 這個 shell ,再到 /etc/profile 讀取主要的環境變數,同時亦會將 /etc/inputrc 及 /etc/profile.d 內容均讀入。之後,再到個人的家目錄讀取 ~/.bash_profile 及 ~/.bashrc 等檔案!
    • man page 的路徑設定檔案?
    • /etc/man.config 或 /etc/man.conf
    • 試說明 ', ", 與 ` 這些符號在變數定義中的用途?
    • 參考變數規則那一章節,其中, " 可以具有變數的內容屬性, ' 則僅有一般字元,至於 ` 之內則是可先被執行的指令。
    • 跳脫符號 \ 有什麼用途?
    • 可以用來跳脫特殊字元,例如 Enter, $ 等等,使成為一般字元!
    • 連續命令中, ;, &&, || 有何不同?
    • 分號可以讓兩個 command 連續運作,不考慮 command1 的輸出狀態, && 則前一個指令必需要沒有錯誤訊息,亦即回傳值需為 0 則 command2 才會被執行, || 則與 && 相反!
    • 如何將 last 的結果中,獨立出帳號,並且印出本月份曾經登入過的帳號?
    •  last | cut –d “ “ –f1 | sort | uniq
    • 請問 foo1 && foo2 | foo3 > foo4 ,這個指令串當中, foo1/foo2/foo3/foo4 是指令還是檔案? 整串指令的意義為?
    • foo1/foo2 與 foo3 都是指令, foo4 是裝置或檔案。整串指令意義為:
      1. 當 foo1 執行結果有錯誤時,則該指令串結束;
      2. 若 foo1 執行結果沒有錯誤時,則執行 foo2 | foo3 > foo4 ;
        1. foo2 將 stdout 輸出的結果傳給 foo3 處理;
        2. foo3 將來自 foo2 的 stdout 當成 stdin ,處理完後將資料流重新導向 foo4 這個裝置/檔案
    • 如何秀出在 /bin 底下任何以 a 為開頭的檔案檔名的詳細資料?
    • ls -l /bin/a*
    • 如何秀出 /bin 底下,檔名為四個字元的檔案?
    • ls -l /bin/????
    • 如何秀出 /bin 底下,檔名開頭不是 a-d 的檔案?
    • ls -l /bin/[!a-d]*
    • 當我離開 bash 後,希望系統可以幫我將最近工作的:1.)工作日期; 2.)100 個歷史命令獨立 記錄到 ~/.bash_localcom 檔案中,該如何設定?
    • 我可以編輯 ~/.bash_logout ,將這個檔案內容變成:
      # ~/.bash_logout
      date >> ~/.bash_localcom
      history 100 >> ~/.bash_localcom
      clear
    • 我想要讓終端機介面的登入提示字元修改成我自己喜好的模樣,應該要改哪裡?(filename)
      /etc/issue
    • 承上題,如果我是想要讓使用者登入後,才顯示歡迎訊息,又應該要改哪裡?
      /etc/motd
    修改歷史:
    • 2002/06/27:第一次完成
    • 2003/02/10:重新編排與加入 FAQ
    • 2005/08/17:將舊的資料放置到 這裡
    • 2005/08/17:終於稍微搞定了~花了半個多月不眠不休~呼~補充了較多的管線命令與資料流重導向!
    • 2005/08/18:加入額外的變數設定部分!
    • 2005/08/30:加入了 login 與 non-login shell 的簡單說明!
    • 2006/03/19:原先在 col 的說明當中,原本指令『cat -A /etc/man.config | col -x | cat -A | more』不該有 -A!
    • 2006/10/05:感謝小州兄的告知,修正了原本 ~/.bashrc 說明當中的錯誤。
    • 2007/04/05:原本的 cut 範例說明有誤,原本是『我要找出第三個』應該改為『我要找出第五個』才對!
    • 2007/04/11:原本的 join 說明沒有加上排序,應該需要排序後再處理才對!
    • 2007/07/15:原本的額外的變數功能表格有誤,在 var=${str+expr} 與var=${str:+expr} 需要修改,請參考 此處
    伺服器篇文件
    各版本彙整說明
    CentOS 6.x