认识与学习Bash

什么是SHELL

我們必須要透過『 Shell 』將我們輸入的指令(e.g. ls, cp) Linux Kernel 溝通。Linux 是多人多工的環境,每個人登入系統都能取得一個 bash

 

SHELL的好处

文字介面的 shell 是很不好學的,而且x windows还提供了界面来管理系统,但为什么还要学shell

l  幾乎各家 distributions 使用的 bash 都是一樣的!如此一來, 你就能夠輕輕鬆鬆的轉換不同的 distributions

l  遠端管理:文字介面就是比較快!

l  如果你真的有心想要將您的主機管理的好,那麼良好的 shell 程式編寫是一定需要的

 

 

系統的合法 shell /etc/shells 功能

目前我們的 Linux ( CentOS 5.x 為例) 有多少我們可以使用的 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 的功能都差不多。Linux 預設就是使用 bash

 

為什麼我們系統上合法的 shell 要寫入 /etc/shells 這個檔案啊?

這是因為系統某些服務在運作過程中,會去檢查使用者能夠使用的 shells ,而這些 shell 的查詢就是藉由 /etc/shells 這個檔案囉

 

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

那麼,再想一想,我這個使用者什麼時候可以取得 shell 來工作呢?還有, 我這個使用者預設會取得哪一個 shell 當我登入的時候,系統就會給我一個 shell 讓我來工作了。 而這個登入取得的 shell 就記錄在 /etc/passwd 這個檔案內!

[root@www ~]# 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 的功能

l  命令編修能力 (history)

bash能記憶使用過的指令。因為我只要在指令列按『上下鍵』就可以找到前/後一個輸入的指令

指令記錄在哪裡呢?在你的家目錄內的 .bash_history 不過,需要留意的是,~/.bash_history 記錄的是前一次登入以前所執行過的指令, 而至於這一次登入所執行的指令都被暫存在記憶體中,當你成功的登出系統後,該指令記憶才會記錄到 .bash_history 當中

 

l  命令與檔案補全功能: ([tab] 按鍵的好處)

 

l  命令別名設定功能: (alias)

例如:>alias lm='ls -al'

这样执行lm就等于执行ls -al

 

l  工作控制、前景背景控制: (job control, foreground, background)

 

l  程式化腳本(即批命令): (shell scripts)

 

l  萬用字元 [*] (Wildcard)

 

Bash shell 的內建命令: type

透過 type 這個指令我們可以知道每個指令是否為 bash 的內建指令

 

[root@www ~]# type [-tpa] name
選項與參數:
    :不加任何選項與參數時,type 會顯示出 name 是外部指令還是 bash 內建指令
-t  :當加入 -t 參數時,type 會將 name 以底下這些字眼顯示出他的意義:
      file    :表示為外部指令;
      alias   :表示該指令為命令別名所設定的名稱;
      builtin :表示該指令為 bash 內建的指令功能;
-p  :如果後面接的 name 為外部指令時,才會顯示完整檔名;
-a  :會由 PATH 變數定義的路徑中,將所有含 name 的指令都列出來,包含 alias
 
範例一:查詢一下 ls 這個指令是否為 bash 內建?
[root@www ~]# type ls
ls is aliased to `ls --color=tty' <==未加任何參數,列出 ls 的最主要使用情況
[root@www ~]# type -t ls
alias                             <==僅列出 ls 執行時的依據
[root@www ~]# type -a ls
ls is aliased to `ls --color=tty' <==最先使用 aliase
ls is /bin/ls                     <==還有找到外部指令在 /bin/ls
 
範例二:那麼 cd 呢?
[root@www ~]# type cd
cd is a shell builtin             <==看到了嗎? cd  shell 內建指令

 

 

指令的下達过程中反斜線 (/)的使用

 
範例:如果指令串太長的話,如何使用兩行來輸出?
[vbird@www ~]# cp /var/spool/mail/root /etc/crontab /
> /etc/fstab /root

上面這個指令用途是將三個檔案複製到 /root 這個目錄下而已。不過,因為指令太長, 於是就利用『 /[Enter] 』來將 [Enter] 這個按鍵『跳脫!』開來,讓 [Enter] 按鍵不再具有『開始執行』的功能好讓指令可以繼續在下一行輸入。 需要特別留意, [Enter] 按鍵是緊接著反斜線 (/) ,兩者中間沒有其他字元。

如果順利跳脫 [Enter] 後,下一行最前面就會主動出現 > 的符號,你可以繼續輸入指令囉

 

Shell 的變數

不用多讲,非常重要!!例如变数PATH。。。

 

變數的可變性與方便性

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

 

影響 bash 環境操作的變數

某些特定變數會影響到 bash 的環境喔!舉例來說,我們前面已經提到過很多次的那個 PATH 變數 你能不能在任何目錄下執行某個指令,與 PATH 這個變數有很大的關係。例如你下達 ls 這個指令時,系統就是透過 PATH 這個變數裡面的內容所記錄的路徑順序來搜尋指令的呢!如果在搜尋完 PATH 變數內的路徑還找不到 ls 這個指令時, 就會在螢幕上顯示『 command not found 』的錯誤訊息了。

 

在進入 shell 之前,也正如同上面提到的,由於系統需要一些變數來提供他資料的存取 (或者是一些環境的設定參數值, 例如是否要顯示彩色等等的) ,所以就有一些所謂的『環境變數 需要來讀入系統中了!這些環境變數例如 PATHHOMEMAILSHELL 等等,都是很重要的, 為了區別與自訂變數的不同,環境變數通常以大寫字元來表示

 

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

變數在被取用時,前面必須要加上錢字號『 $ 』才行

 

[root@www ~]# echo $variable
[root@www ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/bin:/root/bin
[root@www ~]# echo ${PATH}

 

變數的取用就如同上面的範例,利用 echo 就能夠讀出,只是需要在變數名稱前面加上 $ 或者是以 ${變數} 的方式來取用都可以 bash 當中,當一個變數名稱尚未被設定時,預設的內容是『空』的

 

那麼如何『設定』或者是『修改』 某個變數的內容用『等號(=)』連接變數與他的內容就好啦。例如

[root@www ~]# echo $myname
       <==這裡並沒有任何資料~因為這個變數尚未被設定!是空的!
[root@www ~]# myname=VBird
[root@www ~]# echo $myname
VBird  <==出現了!因為這個變數已經被設定了!

 

變數的設定規則

  1. 變數與變數內容以一個等號『=』來連結,如下所示:
    myname=VBird
  2. 等號兩邊不能直接接空白字元,如下所示為錯誤:
    myname = VBird』或『myname=VBird Tsai
  3. 變數名稱只能是英文字母與數字,但是開頭字元不能是數字,如下為錯誤:
    2myname=VBird
  4. 變數內容若有空白字元可使用雙引號『"』或單引號『'』將變數內容結合起來,但
    • 雙引號內的特殊字元如 $ 等,可以保有原本的特性,如下所示:
      var="lang is $LANG"』則『echo $var』可得『lang is en_US
    • 單引號內的特殊字元則僅為一般字元 (純文字),如下所示:
      var='lang is $LANG'』則『echo $var』可得『lang is $LANG
    • 單引號與雙引號的最大不同在於雙引號仍然可以保有變數的內容,但單引號內僅能是一般字元 ,而不會有特殊符號
  5. 可用跳脫字元『 / 』將特殊符號( [Enter], $, /, 空白字元, ')變成一般字元;
  6. 在一串指令中,還需要藉由其他的指令提供的資訊,可以使用反單引號『`指令`』或 『$(指令)。特別注意,那個 ` 是鍵盤上方的數字鍵 1 左邊那個按鍵,而不是單引號! 例如想要取得核心版本的設定:
    version=$(uname -r)』再『echo $version』可得『2.6.18-128.el5
  7. 若該變數為擴增變數內容時,則可用 "$變數名稱" ${變數} 累加內容,如下所示:
    PATH="$PATH":/home/bin
  8. 若變數需要在其他子程序執行,需要以 export 來使變數變成環境變數:
    export PATH
  9. 通常大寫字元為系統預設變數,自行設定變數可以使用小寫字元,方便判斷 (純粹依照使用者興趣與嗜好)
  10. 取消變數的方法為使用 unset unset 變數名稱』例如取消 myname 的設定:
    unset myname

底下舉幾個例子來讓你試看看,就知道怎麼設定好你的變數囉!

 

範例一:設定一變數 name ,且內容為 VBird
[root@www ~]# 12name=VBird
-bash: 12name=VBird: command not found  <==顯示錯誤!不能以數字開頭!
[root@www ~]# name = VBird            <==還是錯誤!因為有空白!
[root@www ~]# name=VBird              <==OK 的啦!
 
範例二:承上題,若變數內容為 VBird's name ,就是變數內容含有特殊符號時:
[root@www ~]# name=VBird's name  
# 單引號與雙引號必須要成對,在上面的設定中僅有一個單引號,因此當你按下 enter 後,
# 你還可以繼續輸入變數內容。這與我們所需要的功能不同,失敗啦!
# 記得,失敗後要復原請按下 [ctrl]-c 結束!
[root@www ~]# name="VBird's name"    <==OK 的啦!
# 指令是由左邊向右找,先遇到的引號先有用,因此如上所示,單引號會失效!
[root@www ~]# name='VBird's name'    <==失敗的啦!
# 因為前兩個單引號已成對,後面就多了一個不成對的單引號!因此也就失敗了!
[root@www ~]# name=VBird/'s/ name     <==OK 的啦!
# 利用反斜線 (/) 跳脫特殊字元,例如單引號與空白鍵,這也是 OK 的啦!
 
範例三:我要在 PATH 這個變數當中『累加』:/home/dmtsai/bin 這個目錄
[root@www ~]# PATH=$PATH:/home/dmtsai/bin
[root@www ~]# PATH="$PATH":/home/dmtsai/bin
[root@www ~]# PATH=${PATH}:/home/dmtsai/bin
# 上面三種格式在 PATH 裡頭的設定都是 OK 的!但是底下的例子就不見得囉!
 
範例四:承範例三,我要將 name 的內容多出 "yes" 呢?
[root@www ~]# name=$nameyes  
# 如果沒有雙引號,那麼變數成了啥?name 的內容是 $nameyes 這個變數!
# 呵呵!我們可沒有設定過 nameyes 這個變數吶!所以,應該是底下這樣才對!
[root@www ~]# name="$name"yes
[root@www ~]# name=${name}yes  <==以此例較佳!
 
範例五:如何讓我剛剛設定的 name=VBird 可以用在下個 shell 的程序?
[root@www ~]# name=VBird
[root@www ~]# bash        <==進入到所謂的子程序
[root@www ~]# echo $name  <==子程序:再次的 echo 一下;
       <==嘿嘿!並沒有剛剛設定的內容喔!
[root@www ~]# exit        <==子程序:離開這個子程序
[root@www ~]# export name
[root@www ~]# bash        <==進入到所謂的子程序
[root@www ~]# echo $name  <==子程序:在此執行!
VBird  <==看吧!出現設定值了!
[root@www ~]# exit        <==子程序:離開這個子程序

 

『子程序』就是說在我目前這個 shell 的情況下,去啟用另一個新的 shell ,新的那個 shell 就是子程序啦!在一般的狀態下,父程序的自訂變數是無法在子程序內使用的。但是透過 export 將變數變成環境變數後,就能夠在子程序底下應用了

 

範例六:如何進入到您目前核心的模組目錄?
[root@www ~]# cd /lib/modules/`uname -r`/kernel
[root@www ~]# cd /lib/modules/$(uname -r)/kernel

每個 Linux 都能夠擁有多個核心版本,且幾乎 distribution 的核心版本都不相同。以 CentOS 5.3 (未更新前) 為例,他的預設核心版本是 2.6.18-128.el5 ,所以核心模組目錄在 /lib/modules/2.6.18-128.el5/kernel/ 內。 也由於每個 distributions 的這個值都不相同,但是我們卻可以利用 uname -r 這個指令先取得版本資訊。所以囉,就可以透過上面指令當中的內含指令 `uname -r` 先取得版本輸出到 cd ... 那個指令當中,就能夠順利的進入目前核心的驅動程式所放置的目錄囉!很方便吧!

其實上面的指令可以說是作了兩次動作,亦即是:

  1. 先進行反單引號內的動作『uname -r』並得到核心版本為 2.6.18-128.el5
  2. 將上述的結果帶入原指令,故得指令為:『cd /lib/modules/2.6.18-128.el5/kernel/

 

範例七:取消剛剛設定的 name 這個變數內容
[root@www ~]# unset name

 

范例八: 又一个变量的用法

[root@www ~]# work="/cluster/server/work/taiwan_2005/003/"
[root@www ~]# cd $work

 

 

env 觀察環境變數與常見環境變數說明

範例一:列出目前的 shell 環境下的所有環境變數與其內容。

[root@www ~]# env

HOSTNAME=www.vbird.tsai    <== 這部主機的主機名稱

TERM=xterm                 <== 這個終端機使用的環境是什麼類型

SHELL=/bin/bash      <== 目前這個環境下,使用的 Shell 是哪一個程式?

HISTSIZE=1000        <== 『記錄指令的筆數』在 CentOS 預設可記錄 1000

USER=root            <== 使用者的名稱啊!

LS_COLORS=no=00:fi=00:di=00;34:... <== 一些顏色顯示

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                 <== 這個與語系有關,底下會再介紹!

HOME=/root                 <== 這個使用者的家目錄啊!

_=/bin/env            <== 上一次使用的指令的最後一個參數(或指令本身)

 

·  HOME
代表使用者的家目錄。

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

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

·  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@www ~]# declare -i number=$RANDOM*10/32768 ; echo $number

8   <== 此時會隨機取出 0~9 之間的數值喔!

 

set 觀察所有變數 (含環境變數與自訂變數)

bash 可不只有環境變數喔,還有一些與 bash 操作介面有關的變數,以及使用者自己定義的變數存在的 那麼這些變數如何觀察呢?這個時候就得要使用 set 這個指令了

[root@www ~]# set

BASH=/bin/bash           <== bash 的主程式放置路徑

BASH_VERSINFO=([0]="3" [1]="2" [2]="25" [3]="1" [4]="release"

[5]="i686-redhat-linux-gnu")      <== bash 的版本啊!

BASH_VERSION='3.2.25(1)-release'  <== 也是 bash 的版本啊!

COLORS=/etc/DIR_COLORS.xterm      <== 使用的顏色紀錄檔案

COLUMNS=115         <== 在目前的終端機環境下,使用的欄位有幾個字元長度

HISTFILE=/root/.bash_history      <== 歷史命令記錄的放置檔案,隱藏檔

HISTFILESIZE=1000   <== 存起來(與上個變數有關)的檔案指令最大紀錄筆數。

HISTSIZE=1000            <== 目前環境下,可記錄的歷史命令最大筆數。

HOSTTYPE=i686   <== 主機安裝的軟體主要類型。

IFS=$' /t/n'             <== 預設的分隔符號

LINES=35                 <== 目前的終端機下的最大行數

MACHTYPE=i686-redhat-linux-gnu    <== 安裝的機器類型

MAILCHECK=60         <== 與郵件有關。每 60 秒去掃瞄一次信箱有無新信!

OLDPWD=/home        <== 上個工作目錄。我們可以用 cd - 來取用這個變數。

OSTYPE=linux-gnu         <== 作業系統的類型!

PPID=20025               <== 父程序的 PID (會在後續章節才介紹)

PS1='[/u@/h /W]/$ '  <== PS1很重要。這個是命令提示字元,也就是我們常見的 [root@www ~]# [dmtsai ~]$ 的設定值啦!可以更動的!

PS2='> '           <== 如果你使用跳脫符號 (/) 第二行以後的提示字元也

name=VBird         <== 剛剛設定的自訂變數也可以被列出來喔!

$                        <== 目前這個 shell 所使用的 PID

?                        <== 剛剛執行完指令的回傳值。

 

基本上,在 Linux 預設的情況中,使用{大寫的字母}來設定的變數一般為系統內定需要的變數

 

* PS1(提示字元的設定)

這是 PS1 (數字的 1 不是英文字母),這個東西就是我們的『命令提示字元』喔!當我們每次按下 [Enter] 按鍵去執行某個指令後,最後要再次出現提示字元時, 就會主動去讀取這個變數值了。上頭 PS1 內顯示的是一些特殊符號,這些特殊符號可以顯示不同的資訊, 每個 distributions bash 預設的 PS1 變數內容可能有些許的差異,不要緊,『習慣你自己的習慣』就好了。你可以用 man bash (註3)去查詢一下 PS1 的相關說明,以理解底下的一些符號意義。

  • /d :可顯示出『星期 月 日』的日期格式,如:"Mon Feb 2"
  • /H :完整的主機名稱。舉例來說,鳥哥的練習機為『www.vbird.tsai
  • /h :僅取主機名稱在第一個小數點之前的名字,如鳥哥主機則為『www』後面省略
  • /t :顯示時間,為 24 小時格式的『HH:MM:SS
  • /T :顯示時間,為 12 小時格式的『HH:MM:SS
  • /A :顯示時間,為 24 小時格式的『HH:MM
  • /@ :顯示時間,為 12 小時格式的『am/pm』樣式
  • /u :目前使用者的帳號名稱,如『root』;
  • /v BASH 的版本資訊,如鳥哥的測試主機板本為 3.2.25(1),僅取『3.2』顯示
  • /w :完整的工作目錄名稱,由根目錄寫起的目錄名稱。但家目錄會以 ~ 取代;
  • /W :利用 basename 函數取得工作目錄名稱,所以僅會列出最後一個目錄名。
  • /# :下達的第幾個指令。
  • /$ :提示字元,如果是 root 時,提示字元為 # ,否則就是 $ 囉~

好了,讓我們來看看 CentOS 預設的 PS1 內容吧:『[/u@/h /W]/$ 』,現在你知道那些反斜線後的資料意義了吧? 要注意喔!那個反斜線後的資料為 PS1 的特殊功能,與 bash 的變數設定沒關係啦!不要搞混了喔! 那你現在知道為何你的命令提示字元是:『 [root@www ~]# 』了吧? 好了,那麼假設我想要有類似底下的提示字元:

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

那個 # 代表第 12 次下達的指令。那麼應該如何設定 PS1 呢?可以這樣啊:

[root@www ~ ]# cd /home

[root@www home]# PS1='[/u@/h /w /A #/#]/$ '

[root@www /home 17:02 #85]#

# 看到了嗎?提示字元變了!變的很有趣吧!其中,那個 #85 比較有趣,

# 如果您再隨便輸入幾次 ls 後,該數字就會增加喔!為啥?上面有說明滴!

 

* $(關於本 shell PID)
錢字號本身也是個變數喔!這個咚咚代表的是『目前這個 Shell 的執行緒代號』,亦即是所謂的 PID (Process ID)。 更多的程序觀念,我們會在第四篇的時候提及。想要知道我們的 shell PID ,就可以用:『 echo $$ 』即可!出現的數字就是你的 PID 號碼。

* ?(關於上個執行指令的回傳值)

bash 裡面這個變數可重要的很! 這個變數是:『上一個執行的指令所回傳的值』,上面這句話的重點是『上一個指令』與『回傳值』兩個地方。當我們執行某些指令時, 這些指令都會回傳一個執行後的代碼。一般來說,如果成功的執行該指令, 則會回傳一個 0 值,如果執行過程發生錯誤,就會回傳『錯誤代碼』才對!一般就是以非為 0 的數值來取代。 我們以底下的例子來看看:

[root@www ~]# echo $SHELL

/bin/bash                                  <==可順利顯示!沒有錯誤!

[root@www ~]# echo $?

0                                        <==因為沒問題,所以回傳值為 0

[root@www ~]# 12name=VBird

-bash: 12name=VBird: command not found  <==發生錯誤了!bash回報有問題

[root@www ~]# echo $?

127                              <==因為有問題,回傳錯誤代碼(非為0)

# 錯誤代碼回傳值依據軟體而有不同,可以利用這個代碼來搜尋錯誤的原因喔!

[root@www ~]# echo $?

0

# 咦!怎麼又變成正確了?這是因為 "?" 只與『上一個執行指令』有關,

# 所以,我們上一個指令是執行『 echo $? 』,當然沒有錯誤,所以是 0 沒錯

 

 

export 自訂變數轉成環境變數

環境變數與自訂變數,這兩者的差異在於『 該變數是否會被子程序所繼續引用』啦!唔!那麼啥是父程序?子程序?這就得要瞭解一下指令的下達行為了。

當你登入 Linux 並取得一個 bash 之後,你的 bash 就是一個獨立的程序,被稱為 PID 的就是子程序就是在当前这个shell的情况下,去启动一个新的shell

在原本的 bash 底下執行另一個 bash ,結果操作的環境介面會跑到第二個 bash (就是子程序), 那原本的 bash 就會在暫停的情況 (睡著了,就是 sleep)。整個指令運作的環境是實線的部分!若要回到原本的 bash 去,就只有將第二個 bash 結束掉 (下達 exit logout) 才行

因為子程序僅會繼承父程序的環境變數, 子程序不會繼承父程序的自訂變數啦!所以你在原本 bash 的自訂變數在進入了子程序後就會消失不見, 一直到你離開子程序並回到原本的父程序後,這個變數才會又出現

換個角度來想,也就是說,如果我能將自訂變數變成環境變數的話,那不就可以讓該變數值繼續存在於子程序了?呵呵!沒錯!此時,那個 export 指令就很有用啦!如你想要讓該變數內容繼續的在子程序中使用,那麼就請執行

[root@www ~]# export 變數名稱

 

若逐个逐个对变量进行export,会很麻烦,你可以通过shell script来使用export 如果僅下達 export 而沒有接變數時,那麼此時將會把所有的『環境變數』秀出來喔!例如

[root@www ~]# export

declare -x HISTSIZE="1000"

declare -x HOME="/root"

。。。。

 

影響顯示結果的語系變數 (locale)

當我們使用 man command 的方式去查詢某個資料的說明檔時,該說明檔的內容可能會因為我們使用的語系不同而產生亂碼。另外,利用 ls 查詢檔案的時間時,也可能會有亂碼出現在時間的部分。那個問題其實就是語系的問題啦。

目前大多數的 Linux distributions 已經都是支援日漸流行的萬國碼了,也都支援大部分的國家語系。那麼我們的 Linux 到底支援了多少的語系呢?這可以由 locale 這個指令來查詢到喔! 注:語系檔案都放置在: /usr/lib/locale/ 這個目錄中。

[root@www ~]# locale -a

....(前面省略)....

zh_TW

zh_TW.big5     <==大五碼的中文編碼

zh_TW.euctw

zh_TW.utf8     <==萬國碼的中文編碼

zu_ZA

zu_ZA.iso88591

zu_ZA.utf8

 

如何设置你的系统使用哪些編碼呢?其實可以透過”locale”查询到相关变量,然后修改这些变量即可

[root@www ~]# locale  <==後面不加任何選項與參數即可!

LANG=en_US                   <==主語言的環境

LC_CTYPE="en_US"             <==字元(文字)辨識的編碼

LC_NUMERIC="en_US"           <==數字系統的顯示訊息

LC_TIME="en_US"              <==時間系統的顯示資料

LC_COLLATE="en_US"           <==字串的比較與排序等

LC_MONETARY="en_US"          <==幣值格式的顯示等

LC_MESSAGES="en_US"          <==訊息顯示的內容,如功能表、錯誤訊息等

LC_ALL=                      <==整體語系的環境

....(後面省略)....

 

基本上,你可以逐一設定每個與語系有關的變數資料,但事實上,如果其他的語系變數都未設定, 且你有設定 LANG 或者是 LC_ALL 時,則其他的語系變數就會被這兩個變數所取代! 好了,那麼你應該要覺得奇怪的是,為什麼在 Linux 主機的終端機介面 (tty1 ~ tty6) 的環境下,如果設定『 LANG=zh_TW.big5 』這個設定值生效後,使用 man 或者其他訊息輸出時, 都會有一堆亂碼,尤其是使用 ls -l 這個參數時

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

 

你當然可以讓每個使用者自己去調整自己喜好的語系,但是整體系統預設的語系定義在哪裡呢? 其實就是在 /etc/sysconfig/i18n 囉!這個檔案在 CentOS 5.x 的內容有點像這樣

[root@www ~]# cat /etc/sysconfig/i18n

LANG="zh_TW.UTF-8"

 

變數鍵盤讀取 (read)

讀取來自鍵盤輸入的东东,就是用 read 這個指令了。這個指令最常被用在 shell script 的撰寫當中, 想要跟使用者對談?

 

[root@www ~]# read [-pt] variable

選項與參數:

-p  :後面可以接提示字元!

-t  :後面可以接等待的『秒數!』這個比較有趣~不會一直等待使用者啦!

 

範例一:讓使用者由鍵盤輸入一內容,將該內容變成名為 atest 的變數

[root@www ~]# read atest

This is a test        <==此時游標會等待你輸入!請輸入左側文字看看

[root@www ~]# echo $atest

This is a test          <==你剛剛輸入的資料已經變成一個變數內容!

 

範例二:提示使用者 30 秒內輸入自己的大名,將該輸入字串作為名為 named 的變數內容

[root@www ~]# read -p "Please keyin your name: " -t 30 named

Please keyin your name: VBird Tsai   <==注意看,會有提示字元喔!

[root@www ~]# echo $named

VBird Tsai        <==輸入的資料又變成一個變數的內容了!

 

 

宣告變數的類型 declare / typeset

declare typeset 是一樣的功能,就是在『宣告變數的類型』。如果使用 declare 後面並沒有接任何參數,那麼 bash 就會主動的將所有的變數名稱與內容通通叫出來,就好像使用 set 一樣

 

[root@www ~]# declare [-aixr] variable

選項與參數:

-a  :將後面名為 variable 的變數定義成為陣列 (array) 類型

-i  :將後面名為 variable 的變數定義成為整數數字 (integer) 類型

-x  :用法與 export 一樣,就是將後面的 variable 變成環境變數

-r  :將變數設定成為 readonly 類型,該變數不可被更改內容,也不能 unset

 

範例一:讓變數 sum 進行 100+300+50 的加總結果

[root@www ~]# sum=100+300+50

[root@www ~]# echo $sum

100+300+50  <==怎麼沒有幫我計算加總?因為這是文字型態的變數屬性啊!

[root@www ~]# declare -i sum=100+300+50

[root@www ~]# echo $sum

450         <==瞭乎??

 

bash 對於變數有幾個基本的定義:

  • 變數類型預設為『字串』,所以若不指定變數類型,則 1+2 為一個『字串』而不是『計算式』;
  • bash 環境中的數值運算,預設最多僅能到達整數形態,所以 1/3 結果是 0
  • 如果需要非字串類型的變數,那就得要進行變數的宣告才行啦

範例二:將 sum 變成環境變數

[root@www ~]# declare -x sum

[root@www ~]# export | grep sum

declare -ix sum="450"  <==果然出現了!包括有 i x 的宣告!

範例三:讓 sum 變成唯讀屬性,不可更動!

[root@www ~]# declare -r sum

[root@www ~]# sum=tesgting

-bash: sum: readonly variable  <==老天爺~不能改這個變數了!

範例四:讓 sum 變成非環境變數的自訂變數吧!

[root@www ~]# declare +x sum  <== - 變成 + 可以進行『取消』動作

[root@www ~]# declare -p sum  <== -p 可以單獨列出變數的類型

declare -ir sum="450" <== 看吧!只剩下 i, r 的類型,不具有 x 囉!

 

 

陣列 (array) 變數類型

陣列的設定方式是:var[index]=content

 

範例:設定上面提到的 var[1] var[3] 的變數。

[root@www ~]# var[1]="small min"

[root@www ~]# var[2]="big min"

[root@www ~]# var[3]="nice min"

[root@www ~]# echo "${var[1]}, ${var[2]}, ${var[3]}"

small min, big min, nice min

 

 

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

我們的 bash 是可以『限制使用者的某些系統資源』的,包括可以開啟的檔案數量, 可以使用的 CPU 時間,可以使用的記憶體總量等等。如何設定?用 ulimit

 

[root@www ~]# ulimit [-SHacdfltu] [配額]

選項與參數:

-H  hard limit ,嚴格的設定,必定不能超過這個設定的數值;

-S  soft limit ,警告的設定,可以超過這個設定值,但是若超過則有警告訊息。

      在設定上,通常 soft hard 小,舉例來說,soft 可設定為 80 hard

      設定為 100,你可以使用到 90 (因為沒有超過 100),但介於 80~100 之間時,系統會有警告訊息通知你!

-a  :後面不接任何選項與參數,可列出所有的限制額度;

-c  :當某些程式發生錯誤時,系統可能會將該程式在記憶體中的資訊寫成檔案(除錯用)

      這種檔案就被稱為核心檔案(core file)。此為限制每個核心檔案的最大容量。

-f  :此 shell 可以建立的最大檔案容量(一般可能設定為 2GB)單位為 Kbytes

-d  :程序可使用的最大斷裂記憶體(segment)容量;

-l  :可用於鎖定 (lock) 的記憶體量

-t  :可使用的最大 CPU 時間 (單位為秒)

-u  :單一使用者可以使用的最大程序(process)數量。

 

範例一:列出你目前身份(假設為root)的所有限制資料數值

[root@www ~]# ulimit -a

core file size        (blocks, -c) 0          <==只要是 0 就代表沒限制

data seg size           (kbytes, -d) unlimited

scheduling priority             (-e) 0

file size            (blocks, -f) unlimited  <==可建立的單一檔案的大小

pending signals                 (-i) 11774

max locked memory       (kbytes, -l) 32

max memory size         (kbytes, -m) unlimited

open files                    (-n) 1024       <==同時可開啟的檔案數量

pipe size            (512 bytes, -p) 8

POSIX message queues     (bytes, -q) 819200

real-time priority              (-r) 0

stack size              (kbytes, -s) 10240

cpu time               (seconds, -t) unlimited

max user processes              (-u) 11774

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

 

範例二:限制使用者僅能建立 10MBytes 以下的容量的檔案

[root@www ~]# ulimit -f 10240

[root@www ~]# ulimit -a

file size       (blocks, -f) 10240 <==最大量為10240Kbyes,相當10Mbytes

[root@www ~]# dd if=/dev/zero of=123 bs=1M count=20

File size limit exceeded <==嘗試建立 20MB 的檔案,結果失敗了!

 

我們可以用 ulimit 來限制使用者可以建立的檔案大小喔! 利用 ulimit -f 就可以來設定了!例如上面的範例二,要注意單位喔!單位是 Kbytes

 

想要復原 ulimit 的設定最簡單的方法就是登出再登入,否則就是得要重新以 ulimit 設定才行! 不過,要注意的是,一般身份使用者如果以 ulimit 設定了 -f 的檔案大小, 那麼他『只能繼續減小檔案容量,不能增加檔案容量喔!』

 

 

變數內容的刪除、取代與替換

變數除了可以直接設定來修改原本的內容之外,有沒有辦法透過簡單的動作來將變數的內容進行微調呢 例如進行變數內容的刪除、取代與替換等!是可以的。这里略,详见

http://linux.vbird.org/linux_basic/0320bash.php#variable_other

 

 

命令別名設定: alias, unalias

命令別名是一個很有趣的東西,特別是你的慣用指令特別長的時候!

alias 的定義規則是在 alias 後面加上你的 {『別名』='指令 選項...' }

[root@www ~]# alias lm='ls -al | more'

 

如果要取消命令別名的話,就使用 unalias

[root@www ~]# unalias lm

 

歷史命令:history

bash 有提供指令歷史的服務!那麼如何查詢我們曾經下達過的指令呢?就使用 history

 

[root@www ~]# history [n]

[root@www ~]# history [-c]

[root@www ~]# history [-raw] histfiles

選項與參數:

n   :數字,意思是『要列出最近的 n 筆命令列表』的意思!

-c  :將目前的 shell 中的所有 history 內容全部消除

-a  :將目前新增的 history 指令新增入 histfiles 中,若沒有加 histfiles

      則預設寫入 ~/.bash_history

-r  :將 histfiles 的內容讀到目前這個 shell history 記憶中;

-w  :將目前的 history 記憶內容寫入 histfiles 中!

範例一:列出目前記憶體內的所有 history 記憶

[root@www ~]# history

# 前面省略

 1017  man bash

 1018  ll

 1019  history

 1020  history

# 列出的資訊當中,共分兩欄,第一欄為該指令在這個 shell 當中的代碼

# 另一個則是指令本身的內容!至於會秀出幾筆指令記錄,與 HISTSIZE 有關!

 

範例二:列出目前最近的 3 筆資料

[root@www ~]# history 3

 1019  history

 1020  history

 1021  history 3

 

範例三:立刻將目前的資料寫入 histfile 當中

[root@www ~]# history -w

# 在預設的情況下,會將歷史紀錄寫入 ~/.bash_history 當中!

[root@www ~]# echo $HISTSIZE

1000

在正常的情況下,歷史命令的讀取與記錄是這樣的:

  • 當我們以 bash 登入 Linux 主機之後,系統會主動的由家目錄的 ~/.bash_history 讀取以前曾經下過的指令,那麼 ~/.bash_history 會記錄幾筆資料呢?這就與你 bash HISTFILESIZE 這個變數設定值有關了!
  • 假設我這次登入主機後,共下達過 100 次指令,『等我登出時, 系統就會將 101~1100 這總共 1000 筆歷史命令更新 ~/.bash_history 當中。』 也就是說,歷史命令在我登出時,會將最近的 HISTFILESIZE 筆記錄到我的紀錄檔當中啦!
  • 當然,也可以用 history -w 強制立刻寫入的!那為何用『更新』兩個字呢? 因為 ~/.bash_history 記錄的筆數永遠都是 HISTFILESIZE 那麼多,舊的訊息會被主動的拿掉! 僅保留最新的!

 history 這個歷史命令不仅仅是用来查询历史命令而已,还可以用来執行命令!舉例來說囉:

[root@www ~]# !number

[root@www ~]# !command

[root@www ~]# !!

選項與參數:

number  :執行第幾筆指令的意思;

command :由最近的指令向前搜尋『指令串開頭為command』的指令,並執行;

!!      :就是執行上一個指令(相當於按按鍵後,按 Enter)

[root@www ~]# history

   66  man rm

   67  alias

   68  man history

   69  history

[root@www ~]# !66  <==執行第 66 筆指令

[root@www ~]# !!   <==執行上一個指令,本例中亦即 !66

[root@www ~]# !al  <==執行最近以 al 為開頭的指令(上頭列出的第 67 )

有些朋友在練習 linux 的時候喜歡同時開好幾個 bash 介面,這些 bash 的身份都是 root 這樣會有 ~/.bash_history 的寫入問題嗎?想一想,因為這些 bash 在同時以 root 的身份登入, 因此所有的 bash 都有自己的 1000 筆記錄在記憶體中。因為等到登出時才會更新記錄檔,所以囉, 最後登出的那個 bash 才會是最後寫入的資料。唔!如此一來其他 bash 的指令操作就不會被記錄下來了 (其實有被記錄,只是被後來的最後一個 bash 所覆蓋更新了)

由於多重登入有這樣的問題,所以很多朋友都習慣單一 bash 登入,再用“工作控制” 來切換不同工作

 

路徑與指令搜尋順序

我們知道系統裡面其實有不少的 ls 指令, 或者是包括內建的 echo 指令,那麼來想一想,如果一個指令 (例如 ls) 被下達時,到底是哪一個 ls 被拿來運作?基本上,指令運作的順序可以這樣看:

  1. 以相對/絕對路徑執行指令,例如『 /bin/ls 』或『 ./ls 』;
  2. alias 找到該指令來執行;
  3. bash 內建的 (builtin) 指令來執行;
  4. 透過 $PATH 這個變數的順序搜尋到的第一個指令來執行。

舉例來說,你可以下達 /bin/ls 及單純的 ls 看看,會發現使用 ls 有顏色但是 /bin/ls 則沒有顏色。因為 /bin/ls 是直接取用該指令來下達,而 ls 會因為『 alias ls='ls --color=tty' 』這個命令別名而先使用! 如果想要瞭解指令搜尋的順序,其實透過 type -a ls 也可以查詢的到啦!上述的順序最好先瞭解喔!

例:設定 echo 的命令別名成為 echo -n ,然後再觀察 echo 執行的順序

[root@www ~]# alias echo='echo -n'

[root@www ~]# type -a echo

echo is aliased to `echo -n'

echo is a shell builtin

echo is /bin/echo

很清楚吧!先 alias builtin 再由 $PATH 找到 /bin/echo 囉!

 

 

bash 的進站與歡迎訊息: /etc/issue, /etc/motd

在終端機介面 (tty1 ~ tty6) 登入的時候,會有幾行提示的字串嗎?那就是進站畫面啊! 那個字串寫在 /etc/issue 裡面

[root@www ~]# cat /etc/issue

CentOS release 5.3 (Final)

Kernel /r on an /m

修改进站字串就是修改/etc/issue 這個檔案的內容。issue 這個檔案的內容也是可以使用反斜線作為變數取用喔!

issue 內的各代碼意義

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

 

例題:如果你在 tty3 的進站畫面看到如下顯示,該如何設定才能得到如下畫面?

CentOS release 5.3 (Final) (terminal: tty3)
Date: 2009-02-05 17:29:19
Kernel 2.6.18-128.el5 on an i686
Welcome!

注意,tty3 在不同的 tty 有不同顯示,日期則是再按下 [enter] 後就會所有不同。

參考上述的反斜線功能去修改 /etc/issue 成為如下模樣即可(共五行)

CentOS release 5.3 (Final) (terminal: /l)

Date: /d /t

Kernel /r on an /m

Welcome!

 

你要注意的是,除了 /etc/issue 之外還有個 /etc/issue.net 呢!這是啥?這個是提供給 telnet 這個遠端登入程式用的。 當我們使用 telnet 連接到主機時,主機的登入畫面就會顯示 /etc/issue.net 而不是 /etc/issue

 

 

如果您想要讓使用者登入後取得一些訊息,例如您想要讓大家都知道的訊息, 那麼可以將訊息加入 /etc/motd 裡面去例如:當登入後,告訴登入者, 系統將會在某個固定時間進行維護工作,可以這樣做

[root@www ~]# vi /etc/motd

Hello everyone,

Our server will be maintained at 2009/02/28 0:00 ~ 24:00.

Please don't login server at that time. ^_^

 

 

bash 的環境設定檔

你是否會覺得奇怪,怎麼我們什麼動作都沒有進行,但是一進入 bash 就取得一堆有用的變數了? 這是因為系統有一些環境設定檔案的存在,讓 bash 在啟動時直接讀取這些設定檔,以規劃好 bash 的操作環境啦! 而這些設定檔又可以分為全體系統的設定檔以及使用者個人偏好設定檔要注意的是, 我們前幾個小節談到的命令別名啦、自訂的變數啦,在你登出 bash 後就會失效,所以你想要保留你的設定, 就得要將這些設定寫入設定檔才行。继续往下看!

 

login non-login shell

在開始介紹 bash 的設定檔前,我們一定要先知道的就是 login shell non-login shell! 重點在於有沒有登入 (login) 啦!

  • login shell取得 bash 時需要完整的登入流程的,就稱為 login shell。舉例來說,你要由 tty1 ~ tty6 登入,需要輸入使用者的帳號與密碼,此時取得的 bash 就稱為『 login shell 囉;
  • non-login shell取得 bash 介面的方法不需要重複登入的舉動,舉例來說,(1) X window 登入 Linux 再以 X 的圖形化介面啟動終端機,此時那個終端介面並沒有需要再次的輸入帳號與密碼,那個 bash 的環境就稱為 non-login shell(2)在原本的 bash 環境下再次下達 bash 這個指令,同樣的也沒有輸入帳號密碼, 那第二個 bash (子程序) 也是 non-login shell

為什麼要介紹 login, non-login shell 呢?這是因為這兩個取得 bash 的情況中,讀取的設定檔資料並不一樣所致。由於我們需要登入系統,所以先談談 login shell 會讀取哪些設定檔?一般來說,login shell 其實只會讀取這兩個設定檔

  1. /etc/profile:這是系統整體的設定,你最好不要修改這個檔案;
  2. ~/.bash_profile ~/.bash_login ~/.profile屬於使用者個人設定你要改自己的資料,就寫入這裡

 

/etc/profile (login shell 才會讀)

這個設定檔可以利用使用者的識別碼 (UID) 來決定很多重要的變數資料, 這也是每個使用者登入取得 bash 時一定會讀取的設定檔! 所以如果你想要幫所有使用者設定整體環境,那就是改這裡囉!不過,沒事還是不要隨便改這個檔案喔 這個檔案設定的變數主要有

  • PATH:會依據 UID 決定 PATH 變數要不要含有 sbin 的系統指令目錄;
  • MAIL:依據帳號設定好使用者的 mailbox /var/spool/mail/帳號名;
  • USER:根據使用者的帳號設定此一變數內容;
  • HOSTNAME:依據主機的 hostname 指令決定此一變數內容;
  • HISTSIZE:歷史命令記錄筆數。CentOS 5.x 設定為 1000

/etc/profile還會去呼叫外部的設定資料!在 CentOS 5.x 預設的情況下, 底下這些資料會依序的被呼叫進來

  • /etc/inputrc

其實這個檔案並沒有被執行啦!/etc/profile 會主動的判斷使用者有沒有自訂輸入的按鍵功能,如果沒有的話, /etc/profile 就會決定設定『INPUTRC=/etc/inputrc』這個變數!此一檔案內容為 bash 的熱鍵啦、[tab]要不要有聲音啦等等的資料! 因為鳥哥覺得 bash 預設的環境已經很棒了,所以不建議修改這個檔案!

  • /etc/profile.d/*.sh

其實這是個目錄內的眾多檔案!只要在 /etc/profile.d/ 這個目錄內且副檔名為 .sh ,另外,使用者能夠具有 r 的權限, 那麼該檔案就會被 /etc/profile 呼叫進來。在 CentOS 5.x 中,這個目錄底下的檔案規範了 bash 操作介面的顏色、語系、ll ls 指令的命令別名、vi 的命令別名、which 的命令別名等等。如果你需要幫所有使用者設定一些共用的命令別名時,可以在這個目錄底下自行建立副檔名為 .sh 的檔案,並將所需要的資料寫入即可喔!

  • /etc/sysconfig/i18n

這個檔案是由 /etc/profile.d/lang.sh 呼叫進來的!這也是我們決定 bash 預設使用何種語系的重要設定檔!檔案裡最重要的就是 LANG 這個變數的設定啦!我們在前面的 locale 討論過這個檔案囉! 自行回去瞧瞧先!

反正你只要記得,bash login shell 情況下所讀取的整體環境設定檔其實只有 /etc/profile,但是 /etc/profile 還會呼叫出其他的設定檔,所以讓我們的 bash 操作介面變的非常的友善啦! 接下來,讓我們來瞧瞧,那麼個人偏好的設定檔又是怎麼回事

 

~/.bash_profile (login shell 才會讀)

bash 在讀完了整體環境設定的 /etc/profile 並藉此呼叫其他設定檔後,接下來則是會讀取使用者的個人設定檔。在 login shell bash 環境中,所讀取的個人偏好設定檔其實主要有三個,依序分別是

  1. ~/.bash_profile
  2. ~/.bash_login
  3. ~/.profile

其實 bash login shell 設定只會讀取上面三個檔案的其中一個, 而讀取的順序則是依照上面的順序。也就是說,如果 ~/.bash_profile 存在,那麼其他兩個檔案不論有無存在,都不會被讀取。 如果 ~/.bash_profile 不存在才會去讀取 ~/.bash_login,而前兩者都不存在才會讀取 ~/.profile 的意思。 會有這麼多的檔案,其實是因應其他 shell 轉換過來的使用者的習慣而已。先讓我們來看一下 root /root/.bash_profile 的內容是怎樣呢?

[root@www ~]# cat ~/.bash_profile

# .bash_profile

# Get the aliases and functions

if [ -f ~/.bashrc ]; then   <==底下這三行在判斷並讀取 ~/.bashrc

        . ~/.bashrc

fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin        <==底下這幾行在處理個人化設定

export PATH

unset USERNAME

 

从上看出,~/.bash_profile 其實會再呼叫 ~/.bashrc 的設定內容喔!

 

 

source :讀入環境設定檔的指令

由於 /etc/profile ~/.bash_profile 都是在取得 login shell 的時候才會讀取的設定檔,所以, 如果你將自己的偏好設定寫入上述的檔案後,通常都是得登出再登入後,該設定才會生效那麼,能不能直接讀取設定檔而不登出登入呢? 可以的!那就得要利用 source 這個指令了!

[root@www ~]# source 設定檔檔名

範例:將家目錄的 ~/.bashrc 的設定讀入目前的 bash 環境中

[root@www ~]# source ~/.bashrc  <==底下這兩個指令是一樣的!

[root@www ~]#  .  ~/.bashrc

利用 source 或小數點 (.) 都可以將設定檔的內容讀進來目前的 shell 環境中

 

舉例來說,我修改了 ~/.bashrc ,那麼不需要登出,立即以 source ~/.bashrc 就可以將剛剛最新設定的內容讀進來目前的環境中

 

 

~/.bashrc (non-login shell 會讀)

non-login shell 這種非登入情況取得 bash 操作介面的環境設定檔又是什麼? 當你取得 non-login shell 時,該 bash 設定檔僅會讀取 ~/.bashrc 而已啦!那麼預設的 ~/.bashrc 內容是如何?

[root@www ~]# cat ~/.bashrc

# .bashrc

# User specific aliases and functions

alias rm='rm -i'             <==使用者的個人設定

alias cp='cp -i'

alias mv='mv -i'

 

# Source global definitions

if [ -f /etc/bashrc ]; then  <==整體的環境設定

        . /etc/bashrc

fi

可以看到,~/.bashrc会呼叫/etc/bashrc。因為 /etc/bashrc 幫我們的 bash 定義出底下的資料

  • 依據不同的 UID 規範出umask的值;
  • 依據不同的 UID 規範出提示字元 (就是 PS1 變數)
  • 呼叫 /etc/profile.d/*.sh 的設定

其他相關設定檔

事實上還有一些設定檔可能會影響到你的 bash 操作的:

  • /etc/man.config

這個檔案規定了下達 man 的時候,該去哪裡查看資料的路徑設定

那麼什麼時候要來修改這個檔案呢?如果你是以 tarball 的方式來安裝你的資料,那麼你的 man page 可能會放置在 /usr/local/softpackage/man 裡頭,那個 softpackage 是你的套件名稱, 這個時候你就得以手動的方式將該路徑加到 /etc/man.config 裡頭,否則使用 man 的時候就會找不到相關的說明檔囉。

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

  • ~/.bash_history
  • ~/.bash_logout

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

 

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

[root@www ~]# stty [-a]

選項與參數:

-a  :將目前所有的 stty 參數列出來;

範例一:列出所有的按鍵與按鍵內容

[root@www ~]# stty -a

speed 38400 baud; rows 24; columns 80; line = 0;

intr = ^C; quit = ^/; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;

eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z;

rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;

....(以下省略)....

 

可以利用 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 的程序。

至於刪除字元,就是 erase 那個設定值啦! 如果你想要用 [ctrl]+h 來進行字元的刪除,那麼可以下達

[root@www ~]# stty erase ^h

從此之後,你的刪除字元就得要使用 [ctrl]+h 囉,按下 [backspace] 則會出現 ^? 字樣呢! 如果想要回復利用 [backspace] ,就下達 stty erase ^? 即可啊

 

不过不建議您修改 tty 的環境,bash 預設的組合鍵給他彙整如下:

組合按鍵

執行結果

Ctrl + C

終止目前的命令

Ctrl + D

輸入結束 (EOF),例如郵件結束的時候;

Ctrl + M

就是 Enter 啦!

Ctrl + S

暫停螢幕的輸出

Ctrl + Q

恢復螢幕的輸出

Ctrl + U

在提示字元下,將整列命令刪除

Ctrl + Z

『暫停』目前的命令

 

 

指令set 可以幫我們設定整個指令輸出/輸入的環境

[root@www ~]# set [-uvCHhmBx]

選項與參數:

-u  :預設不啟用。若啟用後,當使用未設定變數時,會顯示錯誤訊息;

-v  :預設不啟用。若啟用後,在訊息被輸出前,會先顯示訊息的原始內容;

-x  :預設不啟用。若啟用後,在指令被執行前,會顯示指令內容(前面有 ++ 符號)

-h  :預設啟用。與歷史命令有關;

-H  :預設啟用。與歷史命令有關;

-m  :預設啟用。與工作管理有關;

-B  :預設啟用。與刮號 [] 的作用有關;

-C  :預設不啟用。若使用 > 等,則若檔案存在時,該檔案不會被覆蓋。

 

範例一:顯示目前所有的 set 設定值

[root@www ~]# echo $-

himBH

# 那個 $- 變數內容就是 set 的所有設定啦! bash 預設是 himBH 喔!

 

範例二:設定 "若使用未定義變數時,則顯示錯誤訊息"

[root@www ~]# set -u

[root@www ~]# echo $vbirding

-bash: vbirding: unbound variable

# 預設情況下,未設定/未宣告 的變數都會是『空的』,不過,若設定 -u 參數,

# 那麼當使用未設定的變數時,就會有問題!很多shell 都預設啟用 -u 參數。

# 若要取消這個參數,輸入 set +u 即可!

 

範例三:執行前,顯示該指令內容。

[root@www ~]# set -x

[root@www ~]# echo $HOME

+ echo /root

/root

++ echo -ne '/033]0;root@www:~'

# 看見否?要輸出的指令都會先被列印到螢幕上喔!前面會多出 + 的符號!

 

 

萬用字元與特殊符號

常用的萬用字元:

符號

意義

*

代表『 0 個到無窮多個』任意字元

?

代表『一定有一個』任意字元

[ ]

同樣代表『一定有一個在括號內』的字元(非任意字元)。例如 [abcd] 代表『一定有一個字元, 可能是 a, b, c, d 這四個任何一個』

[ - ]

若有減號在中括號內時,代表『在編碼順序內的所有字元』。例如 [0-9] 代表 0 9 之間的所有數字,因為數字的語系編碼是連續的!

[^ ]

若中括號內的第一個字元為指數符號 (^) ,那表示『反向選擇』,例如 [^abc] 代表 一定有一個字元,只要是非 a, b, c 的其他字元就接受的意思。

举例,利用萬用字元配合 ls 找檔名看看:

[root@www ~]# LANG=C              <==由於與編碼有關,先設定語系一下

範例一:找出 /etc/ 底下以 cron 為開頭的檔名

[root@www ~]# ll -d /etc/cron*    <==加上 -d 是為了僅顯示目錄而已

範例二:找出 /etc/ 底下檔名『剛好是五個字母』的檔名

[root@www ~]# ll -d /etc/?????    <==由於?一定有一個,所以五個?就對了

範例三:找出 /etc/ 底下檔名含有數字的檔名

[root@www ~]# ll -d /etc/*[0-9]*  <==記得中括號左右兩邊均需 *

範例四:找出 /etc/ 底下,檔名開頭非為小寫字母的檔名:

[root@www ~]# ll -d /etc/[^a-z]*  <==注意中括號左邊沒有 *

範例五:將範例四找到的檔案複製到 /tmp

[root@www ~]# cp -a /etc/[^a-z]* /tmp

除了萬用字元之外,bash 環境中还有些特殊符號:

符號

內容

#

註解符號:這個最常被使用在 script 當中,視為說明!在後的資料均不執行

/

跳脫符號:將『特殊字元或萬用字元』還原成一般字元

|

管線 (pipe):分隔兩個管線命令的界定(後兩節介紹)

;

連續指令下達分隔符號:連續性命令的界定 (注意!與管線命令並不相同)

~

使用者的家目錄

$

取用變數前置字元:亦即是變數之前需要加的變數取代值

&

工作控制 (job control):將指令變成背景下工作

!

邏輯運算意義上的『非』 not 的意思!

/

目錄符號:路徑分隔的符號

>, >>

資料流重導向:輸出導向,分別是『取代』與『累加』

<, <<

資料流重導向:輸入導向 (這兩個留待下節介紹)

' '

單引號,不具有變數置換的功能

" "

具有變數置換的功能!

` `

兩個『 ` 』中間為可以先執行的指令,亦可使用 $( )

( )

在中間為子 shell 的起始與結束

{ }

在中間為命令區塊的組合!

 

資料流重導向

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

 

輸出到螢幕上面來的包含2种信息,standard output 『標準輸出』與 standard error output 『標準錯誤輸出』簡單的說,標準輸出指的是『指令執行所回傳的正確的訊息』,而標準錯誤輸出可理解為『 指令執行失敗後,所回傳的錯誤訊息』

 

資料流重導向可以將 standard output (簡稱 stdout) standard error output (簡稱 stderr) 分別傳送到其他的檔案或裝置去,而分別傳送所用的特殊字元則如下所示:

  1. 標準輸入  (stdin) 代碼為 0 ,使用 < <<
  2. 標準輸出  (stdout)代碼為 1 ,使用 > >>
  3. 標準錯誤輸出(stderr)代碼為 2 ,使用 2> 2>>

 

範例一:觀察你的系統根目錄 (/) 下各目錄的檔名、權限與屬性,並記錄下來

[root@www ~]# ll /  <==此時螢幕會顯示出檔名資訊

[root@www ~]# ll / > ~/rootfile <==螢幕並無任何資訊

[root@www ~]# ll  ~/rootfile <==有個新檔被建立了!

-rw-r--r-- 1 root root 1089 Feb  6 17:00 /root/rootfile

 

上述命令把原本『 ll / 』所顯示的資料已經被重新導向到 ~/rootfile 檔案中了!如果我再次下達:『 ll /home > ~/rootfile 』後,那個 ~/rootfile 檔案的內容變成什麼? 他將變成『僅有 ll /home 的資料』而已!咦!原本的『 ll / 』資料就不見了嗎?是的!因為該檔案的建立方式是:

  1. 該檔案 (本例中是~/rootfile) 若不存在,系統會自動將他建立起來,但是
  2. 當這個檔案存在的時候,那麼系統就會先將這個檔案內容清空,然後再將資料寫入!
  3. 也就是若以 > 輸出到一個已存在的檔案中,那個檔案就會被覆蓋掉囉

那如果我想要將資料累加而不想要將舊的資料刪除,那該如何是好?利用兩個大於的符號 (>>) 就好啦!以上面的範例來說,你應該要改成『 ll / >> ~/rootfile 』即可。 如此一來, (1) ~/rootfile 不存在時系統會主動建立這個檔案;(2)若該檔案已存在, 則資料會在該檔案的最下方累加進去!

 

範例二:利用一般身份帳號搜尋 /home 底下是否有名為 .bashrc 的檔案存在

[root@www ~]# su - dmtsai  <==假設我的系統有名為 dmtsai 的帳號

[dmtsai@www ~]$ find /home -name .bashrc <==身份是 dmtsai 喔!

find: /home/lost+found: Permission denied  <== Standard error

find: /home/alex: Permission denied        <== Standard error

find: /home/arod: Permission denied        <== Standard error

/home/dmtsai/.bashrc                       <== Standard output

 

如果想要將上面正確的與錯誤的資料分別存入不同的檔案中需要怎麼做?

範例三:承範例二,將 stdout stderr 分存到不同的檔案去

[dmtsai@www ~]$ find /home -name .bashrc > list_right 2> list_error

 

/dev/null 垃圾桶黑洞裝置與特殊寫法

如果我知道錯誤訊息會發生,所以要將錯誤訊息忽略掉而不顯示或儲存呢? 這個時候黑洞裝置 /dev/null 就很重要了!這個 /dev/null 可以吃掉任何導向這個裝置的資訊

 

範例四:承範例三,將錯誤的資料丟棄,螢幕上顯示正確的資料

[dmtsai@www ~]$ find /home -name .bashrc 2> /dev/null

/home/dmtsai/.bashrc  <==只有 stdout 會顯示到螢幕上, stderr 被丟棄了

 

如果我要將正確與錯誤資料通通寫入同一個檔案去呢?這個時候就得要使用特殊的寫法了! 我們同樣用底下的案例來說明

範例五:將指令的資料全部寫入名為 list 的檔案中

[dmtsai@www ~]$ find /home -name .bashrc > list 2> list  <==錯誤

[dmtsai@www ~]$ find /home -name .bashrc > list 2>&1     <==正確

[dmtsai@www ~]$ find /home -name .bashrc &> list         <==正確

 

standard input < <<

<”就是『將原本需要由鍵盤輸入的資料,改由檔案內容來取代』的意思

範例六:利用 cat 指令來建立一個檔案的簡單流程

[root@www ~]# cat > catfile

testing

cat file test

<==這裡按下 [ctrl]+d 來離開

[root@www ~]# cat catfile

testing

cat file test

由於加入 > cat 後,所以那個 catfile 會被主動的建立,而內容就是剛剛鍵盤上面輸入的那兩行資料了 唔!那我能不能用純文字檔取代鍵盤的輸入,也就是說,用某個檔案的內容來取代鍵盤的敲擊呢? 可以的!

範例七:用 stdin 取代鍵盤的輸入以建立新檔案的簡單流程

[root@www ~]# cat > catfile < ~/.bashrc

[root@www ~]# ll catfile ~/.bashrc

-rw-r--r-- 1 root root 194 Sep 26 13:36 /root/.bashrc

-rw-r--r-- 1 root root 194 Feb  6 18:29 catfile

# 注意看,這兩個檔案的大小會一模一樣!幾乎像是使用 cp 來複製一般!

 

<< 這個連續兩個小於的符號了。 他代表的是『結束的輸入字元』的意思!舉例來講:『我要用 cat 直接將輸入的訊息輸出到 catfile 中, 且當由鍵盤輸入 eof 時,該次輸入就結束』,那我可以這樣做:

 

[root@www ~]# cat > catfile << "eof"

> This is a test.

> OK now stop

> eof  <==輸入這關鍵字,立刻就結束而不需要輸入 [ctrl]+d

[root@www ~]# cat catfile

This is a test.

OK now stop     <==只有這兩行,不會存在關鍵字那一行!

利用 << 右側的控制字元,我們可以終止一次輸入, 而不必輸入 [crtl]+d 來結束哩

 

 

輸出重導向的用处有:

  • 螢幕輸出的資訊很重要,而且我們需要將他存下來的時候;
  • 背景執行中的程式,不希望他干擾螢幕正常的輸出結果時;
  • 一些系統的例行命令 (例如寫在 /etc/crontab 中的檔案) 的執行結果,希望他可以存下來時;
  • 一些執行命令的可能已知錯誤訊息時,想以『 2> /dev/null 』將他丟掉時;
  • 錯誤訊息與正確訊息需要分別輸出時。

當然還有很多的功能的,最簡單的就是網友們常常問到的:『為何我的 root 都會收到系統 crontab 寄來的錯誤訊息呢』這個咚咚是常見的錯誤, 而如果我們已經知道這個錯誤訊息是可以忽略的時候,嗯!『 2> errorfile 』這個功能就很重要了吧!

 

cmd ; cmd (不考慮指令相關性的連續指令下達)

在某些時候,我們希望可以一次執行多個指令,例如在關機的時候我希望可以先執行兩次 sync 同步化寫入磁碟後才 shutdown 電腦,那麼可以怎麼作呢?這樣做呀:

 
[root@www ~]# sync; sync; shutdown -h now

在指令與指令中間利用分號 (;) 來隔開,這樣一來,分號前的指令執行完後就會立刻接著執行後面的指令了。分号(;)的指令之间没有相关性。如果前一個指令是否成功的執行與後一個指令是否要執行有關!那就得動用到 && ||

 

$? (指令回傳值) && ||

如同上面談到的,兩個指令之間有相依性,而這個相依性主要判斷的地方就在於前一個指令執行的結果是否正確 還記得本章之前我們曾介紹過指令回傳值吧!嘿嘿!沒錯,您真聰明!就是透過這個回傳值啦! 再複習一次『若前一個指令執行的結果為正確,在 Linux 底下會回傳一個 $? = 0 的值』。 那麼我們怎麼透過這個回傳值來判斷後續的指令是否要執行呢?這就得要藉由『 && 』及『 || 』的幫忙了! 注意喔,兩個 & 之間是沒有空格的!那個 | 則是 [Shift]+[/] 的按鍵結果

指令下達情況

說明

cmd1 && cmd2

1. cmd1 執行完畢且正確執行($?=0),則開始執行 cmd2
2.
cmd1 執行完畢且為錯誤 ($?≠0),則 cmd2 不執行。

cmd1 || cmd2

1. cmd1 執行完畢且正確執行($?=0),則 cmd2 不執行。
2.
cmd1 執行完畢且為錯誤 ($?≠0),則開始執行 cmd2

例子: (1)先判斷一個目錄是否存在; (2)若存在才在該目錄底下建立一個檔案。

範例一:使用ls check /tmp/abc是否存在,存在則用touch建立 /tmp/abc/hehe 
[root@www ~]# ls /tmp/abc && touch /tmp/abc/hehe
ls: /tmp/abc: No such file or directory 
# 說明找不到該目錄,但並沒有 touch 的錯誤,表示 touch 並沒有執行
[root@www ~]# mkdir /tmp/abc
[root@www ~]# ls /tmp/abc && touch /tmp/abc/hehe
[root@www ~]# ll /tmp/abc
-rw-r--r-- 1 root root 0 Feb  7 12:43 hehe

 

範例二:測試/tmp/abc是否存在,不存在則予以建立,若存在就不作任何事情
[root@www ~]# rm -r /tmp/abc                <==先刪除此目錄以方便測試
[root@www ~]# ls /tmp/abc || mkdir /tmp/abc
ls: /tmp/abc: No such file or directory <==真的不存在喔!
[root@www ~]# ll /tmp/abc                  
total 0                                 <==結果出現了!有進行 mkdir

 

範例三:我不清楚 /tmp/abc 是否存在,但就是要建立 /tmp/abc/hehe 檔案
[root@www ~]# ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe

 

上面這個範例三總是會建立 /tmp/abc/hehe 的喔!不論 /tmp/abc 是否存在。那麼範例三應該如何解釋呢? 由於Linux 底下的指令都是由左往右執行的,所以範例三有幾種結果我們來分析一下:

  • (1) /tmp/abc 不存在故回傳 $?≠0,則 (2)因為 || 遇到非為 0 $? 故開始 mkdir /tmp/abc,由於 mkdir /tmp/abc 會成功進行,所以回傳 $?=0 (3)因為 && 遇到 $?=0 故會執行 touch /tmp/abc/hehe,最終 hehe 就被建立了;
  • (1) /tmp/abc 存在故回傳 $?=0,則 (2)因為 || 遇到 0 $? 不會進行,此時 $?=0 繼續向後傳,故 (3)因為 && 遇到 $?=0 就開始建立 /tmp/abc/hehe 了!最終 /tmp/abc/hehe 被建立起來。

由於指令是一個接著一個去執行的,因此,如果真要使用判斷, 那麼這個 && || 的順序就不能搞錯

 

 

管線命令 (pipe)

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

假設我們想要知道 /etc/ 底下有多少檔案,那麼可以利用 ls /etc 來查閱,不過, 因為 /etc 底下的檔案太多,導致一口氣就將螢幕塞滿了~不知道前面輸出的內容是啥?此時,我們可以透過 less 指令的協助,利用:

[root@www ~]# ls -al /etc | less

 

如此一來,使用 ls 指令輸出後的內容,就能夠被 less 讀取,並且利用 less 的功能,我們就能夠前後翻動相關的資訊了

管線命令僅會處理 standard output,對於 standard error output 會予以忽略

每個管線後面接的第一個資料必定是『指令』喔!而且這個指令必須要能夠接受 standard input 的資料才行這樣的指令才可以是為『管線命令』,例如 less, more, head, tail 等都是可以接受 standard input 的管線命令啦至於例如 ls, cp, mv 等就不是管線命令了!管線命令主要有兩個比較需要注意的地方:

下面介绍一些常用的管線命令吧!底下的咚咚對系統管理非常有幫助喔!

擷取命令: cut, grep

擷取命令会將一段資料經過分析後,取出我們所想要的cut)。或者是經由分析關鍵字,取得我們所想要的那一行(grep)! 不過,要注意的是,一般來說,它通常是針對『一行一行』來分析的 並不是整篇訊息分析的喔

 

l  cut

cut這個指令可以將一段訊息的某一段給他『切』出來~ 處理的訊息是以『行』為單位喔

 
[root@www ~]# cut -d'分隔字元' -f fields <==用於有特定分隔字元
[root@www ~]# cut -c 字元區間            <==用於排列整齊的訊息
選項與參數:
-d  :後面接分隔字元。與 -f 一起使用;
-f  :依據 -d 的分隔字元將一段訊息分割成為數段,用 -f 取出第幾段的意思;
-c  :以字元 (characters) 的單位取出固定字元區間;
 
範例一:將 PATH 變數取出,我要找出第五個路徑。
[root@www ~]# echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:
# 1 |    2   |  3  |    4    |       5      |     6        |    7
[root@www ~]# echo $PATH | cut -d ':' -f 5
# 如同上面的數字顯示,我們是以『 : 』作為分隔,因此會出現 /usr/local/bin 
# 那麼如果想要列出第 3 與第 5 呢?,就是這樣:
[root@www ~]# echo $PATH | cut -d ':' -f 3,5
 
範例二:將 export 輸出的訊息,取得第 12 字元以後的所有字串
[root@www ~]# export
declare -x HISTSIZE="1000"
declare -x INPUTRC="/etc/inputrc"
declare -x KDEDIR="/usr"
declare -x LANG="zh_TW.big5"
.....(其他省略).....
# 注意看,每個資料都是排列整齊的輸出!如果我們不想要『 declare -x 』時,
# 就得這麼做:
[root@www ~]# export | cut -c 12-
HISTSIZE="1000"
INPUTRC="/etc/inputrc"
KDEDIR="/usr"
LANG="zh_TW.big5"
.....(其他省略).....
# 知道怎麼回事了吧?用 -c 可以處理比較具有格式的輸出資料!
# 我們還可以指定某個範圍的值,例如第 12-20 的字元,就是 cut -c 12-20 等等!
 
範例三:用 last 將顯示的登入者的資訊中,僅留下使用者大名
[root@www ~]# last
root   pts/1    192.168.201.101  Sat Feb  7 12:35   still logged in
root   pts/1    192.168.201.101  Fri Feb  6 12:13 - 18:46  (06:33)
root   pts/1    192.168.201.254  Thu Feb  5 22:37 - 23:53  (01:16)
# last 可以輸出『帳號/終端機/來源/日期時間』的資料,並且是排列整齊的
[root@www ~]# last | cut -d ' ' -f 1
# 由輸出結果可以發現第一個空白分隔的欄位代表帳號,所以使用如上指令:
# 但是因為root   pts/1之間空格有好幾個,並非僅有一個,所以,若要找出 
# pts/1 其實不能以 cut -d ' ' -f 1,2 喔!輸出的結果會不是我們想要的。

 

cut 主要的用途在於將『同一行裡面的資料進行分解!』最常使用在分析一些數據或文字資料的時候! 這是因為有時候我們會以某些字元當作分割的參數,然後來將資料加以切割,以取得我們所需要的資料。 鳥哥也很常使用這個功能呢!尤其是在分析 log 檔案的時候!不過,cut 在處理多空格相連的資料時,可能會比較吃力一點

 

l  grep

剛剛的 cut 是將一行訊息當中,取出某部分我們想要的,而 grep 則是分析一行訊息, 若當中有我們所需要的資訊,就將該行拿出來~簡單的語法是這樣的:

 

[root@www ~]# grep [-acinv] [--color=auto] '搜尋字串' filename
選項與參數:
-a :將 binary 檔案以 text 檔案的方式搜尋資料
-c :計算找到 '搜尋字串' 的次數
-i :忽略大小寫的不同,所以大小寫視為相同
-n :順便輸出行號
-v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
--color=auto :可以將找到的關鍵字部分加上顏色的顯示喔!
 
範例一:將 last 當中,有出現 root 的那一行就取出來;
[root@www ~]# last | grep 'root'
 
範例二:與範例一相反,只要沒有 root 的就取出!
[root@www ~]# last | grep -v 'root'
 
範例三:在 last 的輸出訊息中,只要有 root 就取出,並且僅取第一欄
[root@www ~]# last | grep 'root' |cut -d ' ' -f1
# 在取出 root 之後,利用上個指令 cut 的處理,就能夠僅取得第一欄囉!
 
範例四:取出 /etc/man.config 內含 MANPATH 的那幾行
[root@www ~]# grep --color=auto 'MANPATH' /etc/man.config
....(前面省略)....
MANPATH_MAP     /usr/X11R6/bin          /usr/X11R6/man
MANPATH_MAP     /usr/bin/X11            /usr/X11R6/man
MANPATH_MAP     /usr/bin/mh             /usr/share/man
# 神奇的是,如果加上 --color=auto 的選項,找到的關鍵字部分會用特殊顏色顯示喔!

 

排序命令: sort, wc, uniq

l  sort

sort 指令可以幫我們進行排序。此外,排序的字元與語系的編碼有關,因此,如果您需要排序時,建議使用 LANG=C 來讓語系統一,資料排序比較好一些。

 

[root@www ~]# sort [-fbMnrtuk] [file or stdin]
選項與參數:
-f  :忽略大小寫的差異,例如 A  a 視為編碼相同;
-b  :忽略最前面的空白字元部分;
-M  :以月份的名字來排序,例如 JAN, DEC 等等的排序方法;
-n  :使用『純數字』進行排序(預設是以文字型態來排序的)
-r  :反向排序;
-u  :就是 uniq ,相同的資料中,僅出現一行代表;
-t  :分隔符號,預設是用 [tab] 鍵來分隔;
-k  :以那個區間 (field) 來進行排序的意思
 
範例一:個人帳號都記錄在 /etc/passwd 下,請將帳號進行排序。
[root@www ~]# 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@www ~]# cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
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@www ~]# last | cut -d ' ' -f1 | sort

 

l  uniq

如果我排序完成了,想要將重複的資料僅列出一個顯示,可以怎麼做呢?

[root@www ~]# uniq [-ic]
選項與參數:
-i  :忽略大小寫字元的不同;
-c  :進行計數
 
範例一:使用 last 將帳號列出,僅取出帳號欄,進行排序後僅取出一位;
[root@www ~]# last | cut -d ' ' -f1 | sort | uniq
 
範例二:承上題,如果我還想要知道每個人的登入總次數呢?
[root@www ~]# last | cut -d ' ' -f1 | sort | uniq -c
      1
     12 reboot
     41 root
      1 wtmp
# 從上面的結果可以發現 reboot  12 次, root 登入則有 41 次!
# wtmp 與第一行的空白都是 last 的預設字元,那兩個可以忽略的!

 

這個指令用來將『重複的行刪除掉只顯示一個』,舉個例子來說, 你要知道這個月份登入你主機的使用者有誰,而不在乎他的登入次數,那麼就使用上面的範例, (1)先將所有的資料列出;(2)再將人名獨立出來;(3)經過排序;(4)只顯示一個! 由於這個指令是在將重複的東西減少,所以當然需要『配合排序過的檔案』來處理囉!

 

 

l  wc

如果我想要知道 /etc/man.config 這個檔案裡面有多少字?多少行?多少字元的話可以利用 wc 這個指令來達成喔!他可以幫我們計算輸出的訊息的整體資料!

[root@www ~]# wc [-lwm]
選項與參數:
-l  :僅列出行;
-w  :僅列出多少字(英文單字)
-m  :多少字元;
 
範例一:那個 /etc/man.config 裡面到底有多少相關字、行、字元數?
[root@www ~]# cat /etc/man.config | wc 
    141     722    4617
# 輸出的三個數字中,分別代表: 『行、字數、字元數』
 
範例二:我知道使用 last 可以輸出登入者,但是 last 最後兩行並非帳號內容,
        那麼請問,我該如何以一行指令串取得這個月份登入系統的總人次?
[root@www ~]# last | grep [a-zA-Z] | grep -v 'wtmp' | wc -l 
# 由於 last 會輸出空白行與 wtmp 字樣在最底下兩行,因此,我利用
# grep 取出非空白行,以及去除 wtmp 那一行,在計算行數,就能夠瞭解囉!

 

當你要知道目前你的帳號檔案中有多少個帳號時,就使用這個方法:『 cat /etc/passwd | wc -l 』啦!因為 /etc/passwd 裡頭一行代表一個使用者呀! 所以知道行數就曉得有多少的帳號在裡頭了!而如果要計算一個檔案裡頭有多少個字元時,就使用 wc -m 這個選項吧

 

雙向重導向: tee

> 會將資料流整個傳送給檔案或裝置,而tee 會同時將資料流分送到檔案去與螢幕 (screen);而輸出到螢幕的,其實就是 stdout ,可以讓下個指令繼續處理喔!

 
[root@www ~]# tee [-a] file
選項與參數:
-a  :以累加 (append) 的方式,將資料加入 file 當中!
 
[root@www ~]# last | tee last.list | cut -d " " -f1
# 這個範例可以讓我們將 last 的輸出存一份到 last.list 檔案中;
 
[root@www ~]# ls -l /home | tee ~/homefile | more
# 這個範例則是將 ls 的資料存一份到 ~/homefile ,同時螢幕也有輸出訊息!
 
[root@www ~]# ls -l / | tee -a ~/homefile | more
# 要注意! tee 後接的檔案會被覆蓋,若加上 -a 這個選項則能將訊息累加。

 

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

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

 

l  tr

tr 可以用來刪除一段訊息當中的文字,或者是進行文字訊息的替換!

 

[root@www ~]# tr [-ds] SET1 ...
選項與參數:
-d  :刪除訊息當中的 SET1 這個字串;
-s  :取代掉重複的字元!
 
範例一:將 last 輸出的訊息中,所有的小寫變成大寫字元:
[root@www ~]# last | tr '[a-z]' '[A-Z]'
# 事實上,沒有加上單引號也是可以執行的,如:『 last | tr [a-z] [A-Z] 
 
範例二:將 /etc/passwd 輸出的訊息中,將冒號 (:) 刪除
[root@www ~]# cat /etc/passwd | tr -d ':'
 
範例三:將/etc/passwd轉存成dos斷行到/root/passwd中,再將 ^M 符號刪除
[root@www ~]# cp /etc/passwd /root/passwd && unix2dos /root/passwd
[root@www ~]# file /etc/passwd /root/passwd
/etc/passwd:  ASCII text
/root/passwd: ASCII text, with CRLF line terminators <==就是 DOS 斷行
[root@www ~]# cat /root/passwd | tr -d '/r' > /root/passwd.linux
# 那個 /r 指的是 DOS 的斷行字元,關於更多的字符,請參考 man tr
[root@www ~]# ll /etc/passwd /root/passwd*
-rw-r--r-- 1 root root 1986 Feb  6 17:55 /etc/passwd
-rw-r--r-- 1 root root 2030 Feb  7 15:55 /root/passwd
-rw-r--r-- 1 root root 1986 Feb  7 15:57 /root/passwd.linux
# 處理過後,發現檔案大小與原本的 /etc/passwd 就一致了!

 

l  col

雖然 col 有他特殊的用途,不過,很多時候,他可以用來簡單的處理將 [tab] 按鍵取代成為空白鍵例如下面的例子當中,如果使用 cat -A [tab] 會以 ^I 來表示。 但經過 col -x 的處理,則會將 [tab] 取代成為對等的空白鍵!此外, col 經常被利用於將 man page 轉存為純文字檔以方便查閱的功能!如上述的範例二

 

[root@www ~]# col [-xb]
選項與參數:
-x  :將 tab 鍵轉換成對等的空白鍵
-b  :在文字內有反斜線 (/) 時,僅保留反斜線最後接的那個字元
 
範例一:利用 cat -A 顯示出所有特殊按鍵,最後以 col  [tab] 轉成空白
[root@www ~]# cat -A /etc/man.config <==此時會看到很多^I的符號,那就是 tab
[root@www ~]# cat /etc/man.config | col -x | cat -A | more
# 嘿嘿!如此一來, [tab] 按鍵會被取代成為空白鍵,輸出就美觀多了!
 
範例二:將 col  man page 轉存成為 /root/col.man 的純文字檔
[root@www ~]# man col > /root/col.man
[root@www ~]# vi /root/col.man
COL(1)          BSD General Commands Manual               COL(1)
 
N^HNA^HAM^HME^HE
     c^Hco^Hol^Hl - filter reverse line feeds from input
 
S^HSY^HYN^HNO^HOP^HPS^HSI^HIS^HS
     c^Hco^Hol^Hl [-^H-b^Hbf^Hfp^Hpx^Hx] [-^H-l^Hl _^Hn_^Hu_^Hm]
# 你沒看錯!由於 man page 內有些特殊按鈕會用來作為類似特殊按鍵與顏色顯示,
# 所以這個檔案內就會出現如上所示的一堆怪異字元( ^ )
[root@www ~]# man col | col -b > /root/col.man

 

l  join

join 是處理兩個檔案之間的資料, 而且,主要是在處理『兩個檔案當中,有 "相同資料" 的那一行,才將他加在一起的意思。我們利用底下的簡單例子來說明:

 

[root@www ~]# join [-ti12] file1 file2
選項與參數:
-t  join 預設以空白字元分隔資料,並且比對『第一個欄位』的資料,
      如果兩個檔案相同,則將兩筆資料聯成一行,且第一個欄位放在第一個!
-i  :忽略大小寫的差異;
-1  :這個是數字的 1 ,代表『第一個檔案要用那個欄位來分析』的意思;
-2  :代表『第二個檔案要用那個欄位來分析』的意思。
 
範例一:用root的身份,將/etc/passwd  /etc/shadow 相關資料整合成一欄
[root@www ~]# head -n 3 /etc/passwd /etc/shadow
==> /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
 
==> /etc/shadow <==
root:$1$/3AQpE5e$y9A/D0bh6rElAs:14120:0:99999:7:::
bin:*:14126:0:99999:7:::
daemon:*:14126:0:99999:7:::
# 由輸出的資料可以發現這兩個檔案的最左邊欄位都是帳號!且以 : 分隔
 
[root@www ~]# join -t ':' /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash:$1$/3AQpE5e$y9A/D0bh6rElAs:14120:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:14126:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:14126:0:99999:7:::
# 透過上面這個動作,我們可以將兩個檔案第一欄位相同者整合成一行!
# 第二個檔案的相同欄位並不會顯示(因為已經在第一行了嘛!)
 
範例二:我們知道 /etc/passwd 第四個欄位是 GID ,那個 GID 記錄在 
        /etc/group 當中的第三個欄位,請問如何將兩個檔案整合?
[root@www ~]# head -n 3 /etc/passwd /etc/group
==> /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
 
==> /etc/group <==
root:x:0:root
bin:x:1:root,bin,daemon
daemon:x:2:root,bin,daemon
# 從上面可以看到,確實有相同的部分喔!趕緊來整合一下!
 
[root@www ~]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group
0:root:x:0:root:/root:/bin/bash:root:x:root
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
# 同樣的,相同的欄位部分被移動到最前面!所以第二個檔案的內容就沒再顯示。
# 請讀者們配合上述顯示兩個檔案的實際內容來比對!

 

這個 join 在處理兩個相關的資料檔案時,就真的是很有幫助的啦!例如上面的案例當中,我的 /etc/passwd, /etc/shadow, /etc/group 都是有相關性的, 其中 /etc/passwd, /etc/shadow 以帳號為相關性,至於 /etc/passwd, /etc/group 則以所謂的 GID (帳號的數字定義) 來作為他的相關性。根據這個相關性, 我們可以將有關係的資料放置在一起!這在處理資料可是相當有幫助的! 但是上面的例子有點難,希望您可以靜下心好好的看一看原因喔!

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

 

l  paste

paste 就要比 join 簡單多了!相對於 join 必須要比對兩個檔案的資料相關性, paste 就直接『將兩行貼在一起,且中間以 [tab] 鍵隔開』而已!簡單的使用方法:

 

[root@www ~]# paste [-d] file1 file2
選項與參數:
-d  :後面可以接分隔字元。預設是以 [tab] 來分隔的!
-   :如果 file 部分寫成 - ,表示來自 standard input 的資料的意思。
 
範例一:將 /etc/passwd  /etc/shadow 同一行貼在一起
[root@www ~]# paste /etc/passwd /etc/shadow
bin:x:1:1:bin:/bin:/sbin/nologin        bin:*:14126:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:14126:0:99999:7:::
adm:x:3:4:adm:/var/adm:/sbin/nologin    adm:*:14126:0:99999:7:::
# 注意喔!同一行中間是以 [tab] 按鍵隔開的!
 
範例二:先將 /etc/group 讀出( cat),然後與範例一貼上一起!且僅取出前三行
[root@www ~]# cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3
# 這個例子的重點在那個 - 的使用!那玩意兒常常代表 stdin 喔!

 

l  expand

這玩意兒就是在將 [tab] 按鍵轉成空白鍵

 
[root@www ~]# expand [-t] file
選項與參數:
-t  :後面可以接數字。一般來說,一個 tab 按鍵可以用 8 個空白鍵取代。
      我們也可以自行定義一個 [tab] 按鍵代表多少個字元呢!
 
範例一:將 /etc/man.config 內行首為 MANPATH 的字樣就取出;僅取前三行;
[root@www ~]# grep '^MANPATH' /etc/man.config | head -n 3
MANPATH /usr/man
MANPATH /usr/share/man
MANPATH /usr/local/man
# 行首的代表標誌為 ^ ,這個我們留待下節介紹!先有概念即可!
 
範例二:承上,如果我想要將所有的符號都列出來?( cat)
[root@www ~]# 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@www ~]# 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 的話,
# 情況就又不同了!這裡也不好理解~您可以多設定幾個數字來查閱就曉得!

 

 

分割命令: split

如果你有檔案太大,導致一些攜帶式裝置無法複製的問題,split可以幫你將一個大檔案,依據檔案大小或行數來分割,就可以將大檔案分割成為小檔案了 快速又有效啊!真不錯~

 
[root@www ~]# split [-bl] file PREFIX
選項與參數:
-b  :後面可接欲分割成的檔案大小,可加單位,例如 b, k, m 等;
-l  :以行數來進行分割。
PREFIX :代表前置字元的意思,可作為分割檔案的前導文字。
 
範例一:我的 /etc/termcap 有七百多K,若想要分成 300K 一個檔案時?
[root@www ~]# cd /tmp; split -b 300k /etc/termcap termcap
[root@www tmp]# ll -k termcap*
-rw-r--r-- 1 root root 300 Feb  7 16:39 termcapaa
-rw-r--r-- 1 root root 300 Feb  7 16:39 termcapab
-rw-r--r-- 1 root root 189 Feb  7 16:39 termcapac
# 那個檔名可以隨意取的啦!我們只要寫上前導文字,小檔案就會以
# xxxaa, xxxab, xxxac 等方式來建立小檔案的!
 
範例二:如何將上面的三個小檔案合成一個檔案,檔名為 termcapback
[root@www tmp]# cat termcap* >> termcapback
# 很簡單吧?就用資料流重導向就好啦!簡單!
 
範例三:使用 ls -al / 輸出的資訊中,每十行記錄成一個檔案
[root@www tmp]# ls -al / | split -l 10 - lsroot
[root@www tmp]# wc -l lsroot*
  10 lsrootaa
  10 lsrootab
   6 lsrootac
  26 total
# 重點在那個“-”,一般來說,若需要 stdout/stdin 時,但偏偏又沒有檔案,
# 有的只是“-”時,那麼那個 - 就會被當成 stdin  stdout 

 

你要將檔案分割的話,那麼就使用 -b size 來將一個分割的檔案限制其大小,如果是行數的話,那麼就使用 -l line 來分割!好用的很

 

 

參數代換: xargs (略)

 

關於減號 - 的用途

管線命令在 bash 的連續的處理程序中是相當重要的!另外,在 log file 的分析當中也是相當重要的一環, 所以請特別留意!另外,在管線命令當中,常常會使用到前一個指令的 stdout 作為這次的 stdin 某些指令需要用到檔案名稱 (例如 tar) 來進行處理時,該 stdin stdout 可以利用減號 "-" 來替代, 舉例來說

 
[root@www ~]# tar -cvf - /home | tar -xvf -

 

上面這個例子是說:『我將 /home 裡面的檔案給他打包,但打包的資料不是紀錄到檔案,而是傳送到 stdout 經過管線後,將 tar -cvf - /home 傳送給後面的 tar -xvf - 』。後面的這個 - 則是取用前一個指令的 stdout 因此,我們就不需要使用 file 了!這是很常見的例子喔!注意注意!

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值