終端機工作環境

author:Yung-Yu Chen (yungyuc) http://blog.seety.org/everydaywork/ <yyc@seety.org>
copyright:© 2006, all rights reserved

目錄

1   緒論

本文將說明如何建構一個 Debian (基於 sarge) 終端機工作站 (Terminal Workstation)。所謂的終端機工作站,這裡定義成只具有 CLI (Command Line Interface) 介面,提供使用者以遠端連線或控制台方式使用的計算機系統。

2   檔案的檢視與編輯

Un*x/Debian 是 File-Based 的作業系統,對系統的所有組態與操作,在形式上就是對檔案系統的操作。所以,在開始調整系統之前的第一課,就是要了解如何檢視與編輯檔案。

在剛裝好一個最基本的 Debian 時,我們只有 more 這個檔案檢視工具1。我們可以另外安裝 less

[1]當然我們可以用 cat 來把檔案的內容傾印到螢幕上,但會一次從頭捲到尾,以人類的眼力而言是沒辦法看得那麼快的。
closet:~# apt-get install less
Reading Package Lists... Done
Building Dependency Tree... Done
The following NEW packages will be installed:
  less
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 0B/102kB of archives.
After unpacking 262kB of additional disk space will be used.
Selecting previously deselected package less.
(Reading database ... 11792 files and directories currently installed.)
Unpacking less (from .../archives/less_381-3_i386.deb) ...
Setting up less (381-3) ...

less 的功能比 more 更多 (這是一個挺吊詭的句子,programmer 的幽默吧,我想)。more 只能往下捲頁,也就是說,它不能回到之前曾經看過的上一頁,或是上一行;less 除了可以往上捲頁之外,還有許多額外的功能 (例如上下搜尋、存檔等),請參考 less(1)

關於檔案的編輯,我們通常會使用 vi,vi 是 Unix 作業系統內最早出現的全螢幕編輯器2。建立完畢 Debian Bare System 之後,系統內只有 nvi 這個套件,它是一個只支援原始 vi 指令的 vi clone。一般來說,我們會再安裝另一個具有許多先進功能的 vi clone -- vim:

[2]在 vi 出現以前,我們最多只有行編輯器。活在現在這個 Word 當道的時代,很難想像一次只能編輯一行,而且沒有任何格式的文字編輯器,但行編輯器已經比打卡片要強上太多了。
[3]值得一提的是,vim 有原始碼可以下載,所以它當然是 OSS (Open Source Software),不過它同時也是 Charityware。如果你喜歡這個軟體,並且願意支持它,除了可以直接捐助/註冊 (都一樣,如果你的老闆不喜歡你「捐助」vim,那就「註冊」它,最低 10 塊歐元) vim 之外,他們也希望你可以捐款幫助烏干達 (Uganda) 的難民小朋友。
closet:~# apt-get install vim
Reading Package Lists... Done
Building Dependency Tree... Done
The following extra packages will be installed:
  libgpmg1 vim-common
Suggested packages:
  gpm ctags vim-doc
The following NEW packages will be installed:
  libgpmg1 vim vim-common
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
Need to get 3955kB of archives.
After unpacking 11.4MB of additional disk space will be used.
Do you want to continue? [Y/n] y
.
.
.

Debian 會把 /usr/bin/vi 對應到 vim,所以以後使用的 vi,事實上就會是 vim,而非原始的 nvi 了。如果你完全沒有接觸過 vi/vim 的話,vim 提供了 vimtutor(1) 這個簡介模式。相關的使用資訊請參考 vim(1),以及 vim 的網站[VIM]

在 Debian 裡還有一個常用的檔案內容搜尋工具 -- grep (Base System 內已安裝),它可以把文字檔案的內容「抓」出來:

closet:~# grep grep /usr/share/doc/grep/README
This is GNU grep, the "fastest grep in the west" (we hope).  All
GNU grep is provided "as is" with no warranty.  The exact terms
GNU grep is based on a fast lazy-state deterministic matcher (about
twice as fast as stock Unix egrep) hybridized with a Boyer-Moore-Gosper
than Unix grep or egrep.  (Regular expressions containing backreferencing
See the file TODO for ideas on how you could help us improve grep.
include the word "grep" in your Subject: header field.

/usr/share/doc 是 Debian 用來放置所有與套件相關的文件檔的位置。以上的例子會把 grep 這個套件的 README 裡面,包含了 "grep" 字樣的部分印到螢幕上,你可以自己再 less /usr/share/doc/grep/README 檢查一下看看 grep 作得對不對。grep -r 參數會遞迴地在目錄結構下搜尋。相關的資訊請參考 grep(1)

3   基本組態

3.1   語言與地區組態

在全球化的浪潮之下,大部份的軟體都已經不能如以往僅僅提供單一語言版本,而必須能夠允許執行在不同語言的環境之下。在 Linux 中,GNU 標準 C 程式庫提供了 gettext 設施,允許程式設計師在統一的架構下進行國際化 (i18n, Internationalization) 與本地化 (l10n, Localization) 的工作。

而與系統組態相關的工具就是 locale。我們可以把 locale 當成某個語言所使用的計算機內碼,以及與相關本地化之後系統訊息的集合。在 Debian 下,由 locales 這個套件提供所有 locale 的相關資訊:

closet:~# apt-get install locales
Reading Package Lists... Done
Building Dependency Tree... Done
locales is already the newest version.
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
1 not fully installed or removed.
Need to get 0B of archives.
After unpacking 0B of additional disk space will be used.
Setting up locales (2.3.2.ds1-12) ...

apt-get (dpkg) 進行到這裡,就會開始組態 locales 套件:

  1. 首先會出現如圖 1的畫面3,按上下或換頁鍵就可以移動游標,再以空白鍵選擇想要產生的 locale (方框中會出現 * 號),然後用跳格鍵跳到 <OK>

    [4]

    這是 dialog 式的組態畫面,我們也可以把 dpkg 設定為使用其它套件組態方式,例如純文字。

    以正體中文環境來說,可以選擇 zh_TW.UTF-8,也就是 Unicode 環境;另外我們會看到 zh_TW.Big5 這個 locale,如果你的工作環境很依靠 Big5 這種不方便的內碼,可能得一起選起來產生。在將 Debian 與 Windows (NT) 介接使用時,我們會發現 Unicode 環境可以省下我們不少處理中文問題時的力氣。

  2. 接著 dpkg 會讓我們選擇系統預設的 locale (如圖 2),我們最好先不要設定任何預設值 (故選 None)。

  3. 然後 dpkg 會呼叫 locale-gen 來產生相關的 locale 檔案:

    Generating locales...
      zh_TW.UTF-8... done
    Generation complete.
    

組態完畢之後,系統上就產生好了我們剛剛所選擇的 locale 資料。

TerminalWorkStation_pix/locales010-select-UTF8.png

圖 1: 選擇要產生的 locales}

TerminalWorkStation_pix/locales020-select-default.none.png

圖 2: 選擇預設的 locale

shell 會查詢 LC_* 系列的環境變數,來決定應該如何回應使用者的輸入,這些環境變數有:

  • LC_CTYPE:字元的類別。
  • LC_COLLATE:字元的比較順序。
  • LC_TIME:日期與時間格式。
  • LC_NUMBER:非財務的數字格式。
  • LC_MONETARY:財務上的數字格式。
  • LC_MESSAGES:系統訊息的格式。
  • LC_PAPER:紙張格式。
  • LC_NAME:姓名格式。
  • LC_ADDRESS:地址與所在位置的格式。
  • LC_TELEPHONE:電話號碼格式。
  • LC_MEASUREMENT:度量衡格式。
  • LC_IDENTIFICATION:locale 資訊的元資料 (Metadata)。

另外還有兩個特別的環境變數:

  • LANG:系統語言。
  • LC_ALL:代表所有的 LC_* 設定。

請注意,要在程式開始執行之前就先作這些環境設定,所以我們必須在 bash 的 profile 檔裡設定這些環境變數,才能在 login shell 裡馬上套用 locale。

一般來說,如果終端機不支援非 ASCII 字元的話,在 profile 裡設定 "C", "POSIX" 之外的 locale,螢幕上的字可能就會出不來,所以預設的 locale 通常會用 "POSIX" (亦即 None)。然後系統上的使用者可以在自己 bash 的 profile 裡設定相關的變數來啟用 locale。通常我們只需要

export LC_CTYPE=zh_TW.UTF-8

讓 shell 知道我們是輸入 Unicode 字元就可以了,系統訊息和其它資訊的本地化並不是那麼重要4。另外,如果我們使用連到工作站進行操作,則如圖 3,應該也要在「Windows :: Translation :: Received data assumed to be in which character set」設定正確的字元集 (Character Set, Charset),才能使用 Unicode (UTF-8)。

[5]以現階段而言,RedHat, Mandrake 和 SuSE 所包裝的一些程式,可能有比較好的正體/簡體中文本地化,Debian 就作得不是那麼多。
TerminalWorkStation_pix/locales030-PuTTY-UTF8.png

圖 3: PuTTY 的字元集設定

3.2   bash 下的常用功能

讓我們用一個 .bashrc 檔的內容來說明 bash 下各種功能的組態與使用:

# ~/.bashrc: executed by bash(1) for non-login shells.

# let the coredump file sized 0
ulimit -c 0

# let the umask set to rwx------
umask 077

# let locale be Unicode for Tranditional Chinese
export LC_CTYPE="zh_TW.UTF-8"

# enable color support of ls
eval `dircolors -b ~/.dir_colors`

# add handy aliases
alias j='jobs -l'
alias f=fg
alias b=bg
alias ls='ls -F --color=auto'
alias ll='ls -l'
alias la='ls -a'
alias lla='ls -al'
alias telnet='telnet -8'
alias t=telnet
alias scr=screen
alias s="screen -r"
alias rm=rm
alias mv=mv
alias cp=cp
alias vi=vim
alias px='ps x'
alias dfh='df -h'

# set a fancy prompt
PS1='\h[\u]\w\n\$ '

# set up application settings
export LESS='-r -MM'
export EDITOR=vim
export PAGER=less
export BLOCKSIZE=K

每個使用者在他的家目錄裡可以放一個 .bashrc 檔案,作為 bash 的設定檔5。這個檔案裡面通常會放一些全域的設定指令,讓我們一進入系統就可以存取這些功能。

[6]記得要在 ~/.profile 裡加一行 . ~/.bashrc,不然 login shell 和用其它方法叫起來的 shell,設定會不一致喔。

3.2.1   ulimit

首先是 ulimit (第 4 行),這是一個 shell 指令 (亦即你不能直接 man 到,請改 man bash (1)),用來設定 shell 所提供資源的上限。對使用者而言,只有 ulimit -culimit -s 比較常用:

  • ulimit -c:core dump 的檔案大小。

    如果你像我一樣不在乎程式是怎麼當掉的,就可以設成 0,免得程式當掉之後還要收拾 core dump 檔案。

  • ulimit -s:堆疊的大小。

    堆疊如果設太小,有些程式會無法執行,合適的設定得參考使用者所會執行的應用程式。

其它的資源設定,請參考 bash 的 manpage。

3.2.2   umask

其次是 umask shell 指令 (第 7 行),它用來指定我們在新建檔案時,所會套用的預設權限。關於 Debian 下檔案系統的權限設定,我們到「檔案系統的權限設定」裡再作一個完整的說明。目前我們只要知道,如果希望新建的檔案與目錄:

  • 只有自己能讀寫;設成 077
  • 只有自己能寫,但其它人都能讀;設成 022
  • 所有人都可以讀寫;設定 000

3.2.3   locale

我們也常在 ~/.bashrc 裡設定自己要用的 locale,如第 10 行。關於 locale 應該怎麼設,請參考「語言與地區組態」。

3.2.4   dircolors

神奇的第 13 行,則是為了讓 ls 可以用顏色來分辨不同副檔名型態的檔案,而預先準備好副檔名與顏色的對應資料。dircolors 這個程式會讀取顏色與副檔名型態的對應設定檔 (即 ~/.dir_colors 參數),然後把資料整理好之後,輸出成「設定 $LS_COLORS 環境變數用的指令字串」 (ls 的顏色設定,基本上會根據這個環境變數),而其 -b 參數則是用來指定輸出成 bash 的指令格式,所以我們會在第 13 行裡看到這個部分:

dircolors -b ~/.dir_colors

如果我們想知道 dircolors 的對應檔該怎麼寫,就執行

$ dircolors -p

它會把預設的對應檔內容列出來。因為這些預設值應該會蠻長的,所以我們可以把結果先輸出到檔案之後再慢慢看:

$ dircolors -p > ~/.dir_colors_test

看完之後,其實我們可以直接就用它來修改成我們喜歡的版本,然後再

$ cp ~/.dir_colors_test ~/.dir_colors

把檔案改成比較合適的檔名。

繼續看神奇的第 13 行。eval shell 指令會把接在其後的字串接成一行之後,當成指令執行,而被 ` 包起來的字串,則會傳回被執行之後的結果。所以要在 ~/.bashrc 裡正確設定 $LS_COLORS 環境變數,一定要照第 13 行的寫法,不然,如果只寫成

dircolors -b ~/.dir_colors

的話,那麼一進入系統,就會看見螢幕上列出來

LS_COLORS='......';
export LS_COLORS

這類的字串,完全不會去設定環境變數的。

有了第 13 行,下

$ ls --color=auto

的時候,根據我們的對應檔內容,不同副檔名的檔案就會以不同的顏色顯示了。

3.2.5   alias

第 15 到 32 行是我們在 Debian 中會常常用到的 alias (別名),有些是 shell 指令 (如 jobs, fg, bg 等),有些是公用程式 (如 rm, mv, cp 等)。其中某些相關指令與工具的用途,我們之後會作說明,沒有特別說明的,可以自行查詢 manpage。

如果在進入了系統之後,想要取消掉已設定的 alias,可以用 unalias shell 指令。

3.2.6   指令提示符號

~/.bashrc 的第 35 行設定了 PS1 shell 變數。這個變數的字串值會用來指定 shell 命令列的提示符號。當我們剛安裝好一個 Debian 環境,進入 shell 的時候,通常只會看到簡單的提示符號:

hostname:~#

它的格式是 <hostname>:<cwd>#,亦即主機名稱,中間隔一個冒號,然後接目前的工作目錄 (~ 符號代表目前使用者的家目錄)。

在 bash 的設定檔裡指定 PS1 shell 變數,就可以改變 shell 的提示符號。第 35 行中這個字串值中各種元素所代表的意義分別是:

  • \h:本機名稱。
  • \u:目前使用者名稱。
  • \w:目前工作目錄,若位於家目錄,則顯示 ~
  • \n:換行。

這樣子設定之後,當重新登入系統之後,提示符號會變成:

hostname[username]~
$

變成兩行了。把提示符號變成兩行有一個好處:不會因為目前工作目錄太深[#]而讓指令的輸入位置跑到螢幕的太右邊去,而一定能從左邊數來的第三個字元開始輸入指令。

[7]也就是指目前工作目錄的完整路徑太長。

我們可以參考 bash 的 manpage,或是示範的這些元素自行組合喜歡的提示符號。設計出來的提示符號若能提供足夠的執行資訊,又不會太複雜的話,那就算是很理想了。

3.2.7   指令與路徑自動補齊

Debian 下的 bash 預設會打開自動補齊 (auto completion) 的功能,可以套用在指令以及路徑上。

自動補齊用法簡單,而且威力強大,我們先以指令的自動補齊進行說明。假設我們在提示符號之後輸入了 cp 這兩個字元,然後馬上連按兩次 Tab (跳格) 鍵,會看到:

$ cp
cp       cpan     cpio     cpp      cpp-3.3

bash 把在目前搜尋路徑裡找得到的程式或是 shell 指令列都了出來。假設我們要執行的是 cpio,那麼再多輸入一個 i

$ cpi

然後只要再按一下 Tab 鍵,bash 會幫我們自動補齊成

$ cpio

這是因為 bash 知道,目前唯一找得到是由 cpi 開頭的指令只有 cpio,們就是要執行它 (如果沒打錯的話)。

路徑自動補齊的用法和指令的類似。舉例來講,如果我們想找 /var/log/lastlog 這個檔案,先輸入

$ ll /va

然後按一下 Tab,就會自動補成

$ ll /var/

因為根目錄裡只有 /var/ 是以 /va 開頭的,所以補齊起來不會錯,一定是我們要的 (前提當然還是沒有打錯字)。再接著加上 l

$ ll /var/

按一下 Tab 卻沒有補東西,表示以此開頭的路徑不唯一。多按兩下 Tab,就會列出符合條件的路徑

$ ll /var/l
lib    local  lock   log

我們要進的是 /var/log,所以加上 og,再按 Tab 之後會補成

$ ll /var/log/

此時如果連按兩下 Tab,bash 會把這個目錄之下的所有路徑都列出來 (這招以後會常常用到):

$ ls /var/log/
apache2            exim               messages           samba
auth.log           faillog            messages.0         syslog
auth.log.0         fontconfig.log     messages.1.gz      syslog.0
auth.log.1.gz      installer.log      messages.2.gz      syslog.1.gz
auth.log.2.gz      installer.timings  mysql              syslog.2.gz
btmp               kern.log           mysql.log          syslog.3.gz
daemon.log         kern.log.0         mysql.log.1.gz     syslog.4.gz
daemon.log.0       kern.log.1.gz      mysql.log.2.gz     syslog.5.gz
daemon.log.1.gz    ksymoops           mysql.log.3.gz     syslog.6.gz
daemon.log.2.gz    lastlog            mysql.log.4.gz     user.log
debug              lpr.log            mysql.log.5.gz     user.log.0
debug.0            mail.err           mysql.log.6.gz     user.log.1.gz
debug.1.gz         mail.info          mysql.log.7.gz     uucp.log
debug.2.gz         mail.log           news               wtmp
dmesg              mail.warn          postgresql

我們一看就知道,只有我們要找的 lastlog 是以 la 開頭的,為了好好發揮偷懶的人性,我們只加打 la

$ ll /var/log/la

然後按下 Tab,就補齊了:

$ ll /var/log/lastlog

OK,按下 Enter:

$ ll /var/log/lastlog
-rw-rw-r--    1 root     utmp       293460 Oct 21 16:36 /var/log/lastlog

利用這個方法,我們在執行指令的時候,就可以搜尋用作參數的檔名是否存在,而且 bash 會幫我們確定檔名的正確性。如果沒有自動補齊的功能,要記得那麼多指令、檔案路徑的名稱幾乎是不可能的事情,所以,對於使用文字模式介面的工作者來說,自動補齊不可或缺。

將來,Tab 鍵會是我們的好朋友,你會常常按它的。

3.3   hdparm

hdparm 這個工具提供一個管道,讓系統管理員存取 Linux 核心的 ATA/IDE 驅動程式的設定。現代的 ATA 裝置愈往高速化發展,與 PC 最早架構下的 IDE 已經有了很大的不同,裝置的多樣化也讓組態的部分變得複雜,於是需要使用像 hdparm 這樣的工具來對 ATA 裝置的驅動參數進行調整。

hdparm 不屬於 Debian base system 的一部分,所以我們要手動安裝它:

$ apt-get install hdparm

執行時需要 superuser 權限。

我們可以用以下的語法來檢視 ATA 裝置的狀態:

$ hdparm /dev/hda

/dev/hda:
 multcount    = 16 (on)
 IO_support   =  1 (32-bit)
 unmaskirq    =  0 (off)
 using_dma    =  1 (on)
 keepsettings =  0 (off)
 readonly     =  0 (off)
 readahead    =  8 (on)
 geometry     = 9729/255/63, sectors = 156301488, start = 0

對 ATA 裝置,尤其是硬碟效能最有影響的兩個參數是 IO_support 和 using_dma,分別要用 -c-d 來設定:

$ hdparm -c0 -d0 /dev/hda

/dev/hda:
 setting 32-bit IO_support flag to 0
 setting using_dma to 0 (off)
 IO_support   =  0 (default 16-bit)
 using_dma    =  0 (off)

以上會關掉 32 位元 IO_support 和 using_dma,這兩個功能如果被關掉,磁碟的存取效率會大幅降低,我們來測試看看 (參數 -t 會直接存取裝置進行讀取測試;參數 -T 會使用裝置上的快取進行讀取測試)[#]:

[8]用 hdparm 設定了裝置參數之後,最好再執行一次 hdparm /dev/hda 來檢查一下狀態是否已確實更新,然後才進行測試。這是因為設定裝置需要花一點時間,執行完設定指令之後如果馬上進行測試,裝置的設定動作可能還沒有完成,所以會測試到舊的設定資料。
$ hdparm -tT /dev/hda

/dev/hda:
 Timing buffer-cache reads:   1980 MB in  2.00 seconds = 990.00 MB/sec
 Timing buffered disk reads:   12 MB in  3.64 seconds =   3.30 MB/sec

以下的參數會把這兩個選項開回去:

$ hdparm -c 1 -d 1 /dev/hda

/dev/hda:
 setting 32-bit IO_support flag to 1
 setting using_dma to 1 (on)
 IO_support   =  1 (32-bit)
 using_dma    =  1 (on)

然後進行測試:

$ hdparm -tT /dev/hda

/dev/hda:
 Timing buffer-cache reads:   1964 MB in  2.00 seconds = 982.00 MB/sec
 Timing buffered disk reads:  138 MB in  3.02 seconds =  45.70 MB/sec

我們會發現,這兩個選項不管有沒有打開,有快取的測試值都差不多,但如果看直接存取裝置的讀取速度,就會發現速度差了 13 倍多。

現在的新硬碟幾乎都支援 32 位元 IO 和 DMA 通道,就算磁碟舊到不支援這兩種存取模式,驅動程式也會幫我們處理好相容性的問題。不管在任何一個系統上,建議最好都把 32 位元 IO 和 DMA 通道打開,如果一來,不只可以提高磁碟的存取效率,也能大量地減少磁碟在存取時耗用的 CPU 時間。

在使用 hdparm 時有一點要特別注意。那就是除非磁碟中的資料不太重要,損壞也沒有關係,"hdparm (8)`` 裡面有標上 (DANGEROUS) 字樣的參數,就不要隨便設定了。這些參數中有一部分,可能會對磁碟造成永久性的傷害。

3.3.1   /etc/hdparm.conf

在 Debian 的 hdparm 套件內,同時包含了存取 Linux 核心驅動程式的程式 hdparm,還有用來在開機時自動進行設定的 init.d script (/etc/init.d/hdparm)6,及其設定檔 /etc/hdparm.conf``。在用作伺服器的 Debian 系統上,其磁碟機通常是不會常常地新增移除,那麼我們可以把磁碟機的設定直接寫在 on-boot 設定檔 (/etc/hdparm.conf``) 內,每次重新開機之後,就不必手動重新設定了7

[9]Debian GNU/Linux 採用 SysV 形式的啟動指令稿 (script),所以 hdparm 在 /etc/init.d/etc/rcX.d 裡會有相關的項目,用符合 Debian 慣例的方法,在開機時自動啟動。至於所謂的 SysV 啟動架構是怎麼一回事,我們之後會在「SysV Init 啟動程序」裡說明。
[10]雖然 hdparm-k-K 參數可以設定 keep_settings_over_reset 和 keep_features_over_reset,但問題是第一,有些設定不能用 -k-K 來設,第二,有些 ATA 裝置不吃 keep over reset 信號。所以還是寫在 hdparm.conf 裡面比較保險。

Debian hdparm 套件裡預設的 /etc/hdparm.conf 檔案,已經把所有指令與參數的對應都說明清楚了,這裡我們不用再重複列一遍所有的指令,舉個例子說明就行了。前面提過,我們可以設定磁碟機的 32 位元 IO 和 DMA 通道,假設系統上只有一個 ATA 磁碟,那麼它在 Debian 中的裝置代號會是 /dev/hda,而設定它的 hdparm.conf 可以寫成:

quiet
/dev/hda {
        dma = on
        io32_support = 1
}

如此一來,每次開機的時候,就等於會執行一次

$ hdparm -d 1 -c 1 -q /dev/hda

指令。如果我們有兩顆磁碟,而且都接在 primary IDE channel 上,也都想在開機時設定 32 位元 IO 和 DMA 通道,那麼 hdparm.conf 要寫成:

quiet
/dev/hda {
        dma = on
        io32_support = 1
}
/dev/hdb {
        dma = on
        io32_support = 1
}

這樣就等於在開機時執行

$ hdparm -d 1 -c 1 -q /dev/hda
$ hdparm -d 1 -c 1 -q /dev/hdb

其它的磁碟組態或參數設定,請參考 hdparm (8) 以及 /etc/hdparm.conf。照著這些參考資料照貓畫虎就不會錯了。

4   使用者與權限

4.1   使用者及群組的管理

Debian 裡的使用者 (user) 與群組 (group) 資料,可以直接從本地的密碼檔裡取得 (本地使用者),也可以從外部取得8。這裡我們只討論本地使用者的管理,至於外部的使用者資料來源,則留待討論相關主題的時候再處理。

[11]NIS、Samba (NT Domain/Active Directory)、LDAP 等都可以成為 Debian 的使用者與群組資料來源。

我們可以用 adduser 這個指令來新增使用者,並且設定使用者的相關資料,在其中,最重要的是使用者的登入密碼。要刪除使用者,可以使用 deluser 指令,不過除非特別指定,否則它不會移除使用者的家目錄 (以便萬一是誤殺,雖然使用者的基本資訊不見了,但還可以把使用者自己存的資料救回來)。adduserdeluser 都有許多參數,請參考 adduser(8)deluser(8)。另外,Debian 還有一組管理使用者帳號的指令:useradd(8)userdel(8),在此不予贅述,請自行查閱 manpage9

[12]adduser/deluser 由 adduser 套件所提供;useradd/userdel 則是由 passwd 套件所提供的。

使用者的資料會存到 /etc/passwd 裡面,包括使用者的登入名稱 (就是一般我們講的使用者名稱)、使用者識別碼 (uid)、使用者所屬群組識別碼 (gid)、使用者的家目錄路徑、使用者的 shell,以及其它並非必要的相關資訊。

至於密碼,則會經過某種加密程序之後,另外存在 /etc/shadow 這個檔案裡面。當系統需要核對使用者密碼的時候,會把使用者輸入的字串經過同樣的程序加密,然後加以比對。如果兩者相同,表示密碼驗證成功;如果不同,則表示輸入的字串不是正確的密碼。

因為密碼並不是以明碼 (plaintext) 儲存的,所以 shadow 檔本身就具備一定的安全性。不過我們最好還是將密碼本身和其它資料分檔儲存,減少密碼資料被不當存取的機會。這是因為,如果某個系統的密碼檔被惡意的第三者竊取,即使密碼本身不能直接被反向解碼成原本的字串10,但仍可以透過試誤法或基於試誤法的密文攻擊法來「猜」出正確的密碼11,那麼,惡意的第三者就可以進入你的系統。我們很難知道惡意的第三者在我們的系統裡幹了些什麼事,也許我們所有的資料都被竊取,而且所有的操作都被紀錄起來,回傳到他那邊去。最快速而完整的解決方法通常就是重建系統。

[13]密碼的加密過程,理論上是使用密碼學上的單向函數。舉例來講,假設 f(x)=y 是一個單向函數,則可以把 x 輸入 f(x) 得到 y,但找不到另一個 g(y)=x 來從 y 反算回 x。現有的加密法並未使用完全的單向函數,但使 g(y)=x 的運算量極大於 f(x)=y,讓攻擊者不能在合理的時間 (利用試誤法所需使用的時間) 內反算回密碼。
[14]配合字典攻擊法,猜中的機會還不低。因為一般使用者會使用的密碼來來去去就是那幾種規則,真正照規定設密碼的使用者從古到今都是少之又少。

我們可以這樣說,儲存密碼的 shadow 檔非常重要,絕對不能落到別人的手上。

superuser 可以用 passwd 這個指令修改任何一個本地使用者的密碼,而一般使用者則只能用 passwd 來修改自己的密碼。要修改整個密碼檔的內容 (當然只有 superuser 需要這麼作),請使用 vipw 指令,不要直接編輯 /etc/passwd 檔。

每個使用者都必須屬於某個群組。``adduser`` 指令在建立使用者的時候,預設就會建立一個與該使用者同名的群組,並將其指定給該使用者。群組不能屬於另一個群組,但同一個使用者除了預設群組之外,還可以屬於其它多個群組。

儲存本地群組資訊 (群組資訊當然也可以從外部取得) 的檔案是 /etc/group,可以用 vigr 指令來修改它 (當然也只有 superuser 可以這樣作),而要指定使用者的預設群組,則要用 vipw 修改使用者資料,不能用 vigr/etc/group 檔案的格式寫法,基本上是先列出所有的本地群組 (包含其 gid),然後指定屬於該群組的成員使用者名稱。它不會是空的,所以一樣可以照貓畫虎。

與 Windows (NT) 系統相比,Debian GNU/Linux 的使用者與群組設定簡單了很多,也比較不能作那麼結構化的設定。不過,適當地組合使用者與群組的關係,還是可以達到大部分的管理要求。相關的作法,我們等到有需要實際組態的時候,再好好地來討論討論,才會弄得比較清楚。

4.2   檔案系統的權限設定

4.2.1   權限的意涵

Debian 下的每個檔案系統項目 (包含檔案、目錄與符號連結) 都具有使用者與群組這兩個屬性,也就是這個項目的擁有者,所以權限屬性就會分成 u (使用者本身)、g (群組本身) 與 o (其它) 三種,每一種類別則可以設定 r (可讀取)、w (可寫入) 及 x (可執行) 基本權限。舉個例子,假設我們有個檔案叫 thepaper,它的權限是:

$ ls -al thepaper
-rw-r-----    1 userme   groupme         0 Oct 22 10:44 thepaper

ls -al 的結果所列出的權限就是照 ugo (使用者、群組、其它) 所排的,所以 thepaper 這個檔案的權限設定是:

  • userme 使用者可以讀取、寫入檔案,不能執行。
  • groupme 群組的成員可以讀取檔案,不能寫入也不能執行。
  • 既不是 userme 也非 groupme 群組成員的使用者,不能讀取檔案,也不能寫入和執行;什麼也不能作。

如果這個檔案是一個程式檔 (或 script),則我們需要它的執行權限,才能執行它。

另外,對一個目錄而言,執行權限代表是否可以進入或列出該目錄的內容。所以,要使用某個目錄,使用者不只需要它的讀取權限,也必須具有執行的權限。

4.2.2   權限的設定

檔案的權限 (有時候也稱為模式,mode),我們可以用 chmod 指令來設定,語法是「chmod <權限> <檔名>」,其中 <權限> 的描述方式有兩種,讓我們來看一下範例:

$ ls -al thepaper
-rw-r-----    1 userme   groupme         0 Oct 22 10:44 thepaper
$ chmod g-r thepaper ; ls -al thepaper
-rw-------    1 userme   groupme         0 Oct 22 10:44 thepaper
$ chmod og+w thepaper ; ls -al thepaper
-rw---w--w    1 userme   groupme         0 Oct 22 10:44 thepaper
$ chmod u-rw thepaper ; ls -al thepaper
------w--w    1 userme   groupme         0 Oct 22 10:44 thepaper
$ chmod ugo+rwx thepaper ; ls -al thepaper
-rwxrwxrwx    1 userme   groupme         0 Oct 22 10:44 thepaper*
$ chmod 644 thepaper ; ls -al thepaper
-rw-r--r--    1 userme   groupme         0 Oct 22 10:44 thepaper
  • 第一種描述權限的方法是使用「``[主體符號][+-][權限符號]``」的格式;主體符號有:

    • u 代表使用者本身、
    • g 代表群組、
    • o 代表其它人,

    而權限符號有:

    • r 代表讀取權限、
    • w 代表寫入權限、
    • x 代表執行權限。

    各符號可以加以排列組合,如上面例子裡的

    • 第 3 行,取消掉 thepaper 檔案,群組的讀取權限,
    • 第 5 行,設定擁有群組與其它使用者有寫入 thepaper 檔案的權限,
    • 第 7 行,取消擁有者對 thepaper 檔案的讀取與寫入權限,
    • 第 9 行,把讀取、寫入與執行 thepaper 檔案的權限設定給擁有者、擁有群組及其它使用者。

    另外,要指定所有的使用者時,如果我們不想寫 ugo 這麼冗長的一串符號,也可以用 a 來代替;a 這個符號的意思就是指所有的使用者,亦即 ugo (當然事實上 ugo 也沒多長,不過一個字母總是比三個字母短得多)。

  • 第二種方法,則是把每一個 rwx 權限看成一個八進位的數字,而用三個八進位數字來連續設定擁有者、擁有群組與其它使用者的權限。

    如果把 rwx 中任一個權限的已設定情況當作 1,未設定情況當作 0,則 --x (不可讀取、不可寫入、可以執行) 的二進位值應該是 001-w- (不可讀取、可以寫入、不可執行) 則是 010;``r--`` (可以讀取、不可寫入、不可執行) 是 100。再把這些二進位值換底到八進位,則

    • --x = 1
    • -w- = 2
    • r-- = 4

    如果依照 ugo 的順序,如 rw-r--r-- 這樣的權限就會變成 644,例如第 11 行。

    常用的八進位權限有 644 (rw-r--r--) 和 755 (rwxr-xr-x),前者可用於一般的檔案,後者則用於可執行檔、目錄或 script。

4.2.3   權限遮罩

還記得之前在「umask__` 」的時候提到了 umask 的設定嗎?umask 是在建立檔案與目錄的時候,指定權限用的遮罩 (mask)。這個遮罩會與檔案和目錄的權限進行布林代數裡的 XOR (eXclusive OR) 運算,假設 umask 022:

  • 如果對象是檔案,新建檔案的權限會設定為

    110 110 110 XOR 000 010 010 = 110 100 100
    

    即 644。

  • 如果對象是目錄,新建目錄的權限會設定為

    111 111 111 XOR 000 010 010 = 111 101 101
    

    即 755。

XOR 是一種二元運算子,當兩個數字進行 XOR 運算時,我們要先把這兩個數字的底換到二進位。然後,利用以下的真值表來一位一位地比較這兩個數字:

     
0 0 1
1 1 0

你可以用我們在上面進行的兩次 XOR 運算,來驗證看看是不是照這個規則作出來的。

4.2.4   管理檔案與目錄的擁有者

如果我們想改變檔案與目錄的擁有者或擁有群組,則要使用 chown 這個指令,它的語法是「chown <使用者[.群組]> <檔案>\ 」,不想修改擁有群組的話,群組的部分可以忽略。如果我們只想改變擁有群組的話,也可以改用 ``chgrp 指令,語法一樣是 chown <群組> <檔案>,但不能改變擁有者。

4.2.5   遞迴處理與注意事項

如果我們想以遞迴的方式改變一整個目錄下的檔案權限,或是擁有者和擁有群組,chmod, chown, chgrp 都支援以 -R 參數,執行遞迴作業。不過,因為一般的檔案不需要執行權限就可以正常運作,但目錄卻需要執行權限才能進入,所以用 chmod -R 遞迴地修改檔案權限時,常常會出現一些超乎想像的結果,多半是不小心取消掉目錄的執行權限,或是把所有檔案都變成可讀取的 (如果我們有使用 ls --color 的話,就會看到一大片綠綠的檔案)。這種情況可以配合 find 及其 -type-exec 參數來克服,詳細的用法,請參考 find(1)

另外,superuser 可以修改檔案的擁有者與擁有群組,不受限制。但一般使用者基本上只能修改檔案的擁有群組,並且自己必須是該被「修改至」群組的成員。

4.3   su/sudo 的基本應用

su 這個工具程式很重要,它可以用來「化身」成其它使用者。不過,前提是你得知道要變成的使用者的密碼。

通常,我們用 su 來變身成 superuser:

thehost[theuser]~
$ su
password:
thehost[root]/home/theuser
$

su 後面不接任何使用者名稱,表示要變成 root (superuser)。打密碼的時候不會顯示出來 (否則不就被人看光光了),只要密碼正確,我們就會變成 root,可以看見提示符號裡顯示我們變成 root,而且本來是家目錄的 /home/theuser 路徑,也因為換了一個身份而變成不是家目錄了 (才從簡寫符號 ~ 變回原路徑顯示)。這樣進入的 root shell,就會是 non-login interactive shell,省下了一道重新登入的步驟,不過 bash 啟動時的設定檔也會不一樣12

[15]所以,這就是為什麼我們希望在 profile 檔裡不作設定只引入 rc 檔的內容,來讓 login interactive shell 和 non-login interactive shell 的設定檔內容一致的原因之一。當然,偶爾也會有一些特別的需求,得讓 login 或 non-login shell 的設定檔與對方不同。

su+ 可以接 ``-c <command> 參數,其中的 <command> 是化身後要執行的指令。

su 有時候不太方便,而且缺乏彈性;要化身成對方,一定要知道對方的密碼。因此有了 sudo 這個工具的出現。我們可以把 sudo 當成是不需要知道對方密碼的 su -c,因為 sudo 一次只能化身執行一個指令,而不會進入 shell,而且執行時只需要輸入自己的密碼 (而非對方)。

sudo 有一個設定檔 /etc/sudoers,帳號名稱有列在其中的使用者才能使用 sudo,並且受該檔案內的設定控制。/etc/sudoers 要用 visudo 這個指令來管理,最好不要直接修改它的內容。sudoers 可以接受相當精密的設定 (請參見 sudoers(5)),但如果我們只是要賦予讓某個使用者 superuser 的權限,這樣寫就可以了:

theuser ALL=(ALL) ALL

表示不管 theuser 從哪裡進入系統,都讓它能以任何使用者與群組的身分執行任何程式。

同樣地,在不加上 -u <other user> 的情況下,sudo 預設是化身成 root 執行指令的。

5   系統的標準啟動機制

5.1   SysV Init 啟動程序

在 Debian 系統開機的過程中,當核心啟動完畢之後第一個被執行起來的行程 (process) 是 init。之後的所有其它程式,都要透過 init 這個程式,或是被 init 所啟動的程式來啟動,所以它是系統內所有運作中的程式,最遠的那一位祖先。

整個 Debian GNU/Linux 系統的啟動組態,用不負責任的講法,可以說都靠 init 的設定檔來指定。不過啟動一個系統並非易事,基於便於管理的理由,全部的設定其實是沒辦法寫在單一設定檔裡面的。init 的設定檔是最主要的,但為了配合 init 的動作,我們還需要一整組的設定檔和工具程式,共同組成一個完整的框架。

Debian (或者應該講大部分的 Linux distribution) 採用 SysV 風格的組態模式,整個系統啟動的結構是由 sysvinit (包裝了 init 程式), initscriptssysv-rc 等套件所組成的,這些套件是 Debian Base System 的一部分,不必擔心會沒有安裝。

init 啟動的時候,會去讀 /etc/inittab 這個設定檔。在 inittab 裡有 7 種 runlevel:0, 1, 2, 3, 4, 5, 6,一般 Debian 預設的 runlevel 是 213。我們可以把 runlevel 當成是不同的「啟動模式設定」,因為不同的 runlevel 可以組態不同的程式啟動順序與設定。其中,runlevel 0 保留給系統結束之用,它會依序關閉所有執行中的程式;runlevel 6 則是保留給重新啟動之用;runlevel 1 是單使用者模式,而 runlevel 2$sim$5 則是一般使用的多使用者模式。

[16]見該檔內 id:2:initdefault: 的這一行。

如果我們詳細檢閱 inittab 檔案的內容,會發現根據這個設定檔所設定的 init 程序基本上是

  1. 先指定預設的 runlevel,

  2. 接著執行 /etc/init.d/rcS 這個 shell script,

  3. 然後根據 runlevel 的值,執行 /etc/init.d/rc (這也是一個 shell script)。

    假設 X 代表 runlevel 的值,則 /etc/init.d/rc 會依照 /etc/rcX.d 目錄內各 script 檔的順序,一個一個地執行這些 script。

再配合一些其它的設定,有興趣的話請參考 inittab(5)

以上我們簡單地描述了一下 Debian GNU/Linux 的系統啟動程序,其實在啟動的時候,系統作了許多事情,不過這裡我們沒有篇幅去詳細說明。如果想要進一步了解關於 Debian 系統啟動的資訊,可以參考相關套件的說明與 manpage,以及 Linux Kernel Documentation。

5.2   runlevel 管理

sysv-rc 套件提供了 update-rc.d 工具,幫助我們處理 /etc/rcX.d 這些目錄的管理工作。

系統啟動時要執行的各種服務 (daemon) 與程式,在 SysV Init 的架構下,都會放一個至少接受 startstop 參數的 shell script 在 /etc/init.d 下面。當我們要啟動這個服務14時,只要執行

[17]通常是服務才需要在系統啟動的同時也啟動,應用程式一般不需要這麼處理。不過在「hdparm」提到的 hdparm 這個工具算是一個特例。
$ /etc/init.d/servicename start

即可;相對地,要停止該服務,就下

$ /etc/init.d/servicename stop

不過我們的 init 要看的目錄是 /etc/rcX.d,而非 /etc/init.d,不是嗎?事實上,``/etc/rcX.d`` 裡面所放的檔案,都是連到 ../init.d/servicename 的符號連結:

$ ls -o /etc/rc2.d
lrwxrwxrwx  1 root 18 Jun 23 20:57 S10sysklogd -> ../init.d/sysklogd*
lrwxrwxrwx  1 root 15 Jun 23 20:57 S11klogd -> ../init.d/klogd*
lrwxrwxrwx  1 root 13 Jun 23 20:57 S14ppp -> ../init.d/ppp*
lrwxrwxrwx  1 root 15 Jun 24 10:09 S15bind9 -> ../init.d/bind9*
.
.
.

利用這個技巧,可以自由地排列服務的啟動 (或關閉) 順序,而且不會干擾到這些 script 檔真正的擺放位置 (檔名)。

如果我們想改變 /etc/rcX.d 裡的符號連結設定,千萬不要自己下 ln -s,而要用 update-rc.d。舉例來講,假設我們不希望名稱伺服器在開機的時候自動啟動:

$ update-rc.d bind9 remove
update-rc.d: /etc/init.d/bind9 exists during rc.d purge (continuing)
 Removing any system startup links for /etc/init.d/bind9 ...
   /etc/rc0.d/K85bind9
   /etc/rc1.d/K85bind9
   /etc/rc2.d/S15bind9
   /etc/rc3.d/S15bind9
   /etc/rc4.d/S15bind9
   /etc/rc5.d/S15bind9
   /etc/rc6.d/K85bind9
$ ls -o /etc/rc2.d
lrwxrwxrwx  1 root 18 Jun 23 20:57 S10sysklogd -> ../init.d/sysklogd*
lrwxrwxrwx  1 root 15 Jun 23 20:57 S11klogd -> ../init.d/klogd*
lrwxrwxrwx  1 root 13 Jun 23 20:57 S14ppp -> ../init.d/ppp*
.
.
.

rc2.d 裡的 bind9 不見了 (其它 runlevel 裡的也一起清掉了)。我們加了 -f 參數,是因為如果 /etc/init.d 的原 script 還在的話,在不加 -f 的情況下,update-rc.d 會禁止清除符號連結。如果想設回來,可以這樣作:

$ update-rc.d bind9 defaults
 Adding system startup for /etc/init.d/bind9 ...
   /etc/rc0.d/K20bind9 -> ../init.d/bind9
   /etc/rc1.d/K20bind9 -> ../init.d/bind9
   /etc/rc6.d/K20bind9 -> ../init.d/bind9
   /etc/rc2.d/S20bind9 -> ../init.d/bind9
   /etc/rc3.d/S20bind9 -> ../init.d/bind9
   /etc/rc4.d/S20bind9 -> ../init.d/bind9
   /etc/rc5.d/S20bind9 -> ../init.d/bind9
$ ls -o /etc/rc2.d
lrwxrwxrwx  1 root 18 Jun 23 20:57 S10sysklogd -> ../init.d/sysklogd*
lrwxrwxrwx  1 root 15 Jun 23 20:57 S11klogd -> ../init.d/klogd*
lrwxrwxrwx  1 root 13 Jun 23 20:57 S14ppp -> ../init.d/ppp*
.
.
lrwxrwxrwx  1 root 15 Oct 27 11:14 S20bind9 -> ../init.d/bind9*
.
.
.

哇,不過順序和原本的不太一樣,啟動和關閉都變成 20 了,原本各是 15 和 85 的。 讓我們重作一次:

$ update-rc.d bind9 defaults 15 85
 Adding system startup for /etc/init.d/bind9 ...
   /etc/rc0.d/K85bind9 -> ../init.d/bind9
   /etc/rc1.d/K85bind9 -> ../init.d/bind9
   /etc/rc6.d/K85bind9 -> ../init.d/bind9
   /etc/rc2.d/S15bind9 -> ../init.d/bind9
   /etc/rc3.d/S15bind9 -> ../init.d/bind9
   /etc/rc4.d/S15bind9 -> ../init.d/bind9
   /etc/rc5.d/S15bind9 -> ../init.d/bind9
$ ls -o /etc/rc2.d
lrwxrwxrwx  1 root 18 Jun 23 20:57 S10sysklogd -> ../init.d/sysklogd*
lrwxrwxrwx  1 root 15 Jun 23 20:57 S11klogd -> ../init.d/klogd*
lrwxrwxrwx  1 root 13 Jun 23 20:57 S14ppp -> ../init.d/ppp*
lrwxrwxrwx  1 root 15 Oct 27 11:21 S15bind9 -> ../init.d/bind9*
.
.
.

這麼一來就和原來的設定一模一樣了。

剛剛的例子裡面有 update-rc.d 的兩種用法:刪除符號連結與重建符號連結。我們另外還可以用

update-rc.d servicename start NN runlevellist .

來調整任意 runlevel 裡的啟動或關閉符號連結,請參考 update-rc.d(8)

5.3   自訂 init.d 的內容

如果我們在系統上自行安裝了服務,希望它能在開機時自動執行,則需要為服務撰寫專用的 init.d script。另一個比較不好的方法是把啟動指令寫到 /etc/init.d/rcS,或是其含入的 /etc/rc.boot 目錄內容 (這個目錄預設不存在) 裡面。第二個方法不好在它太依靠 /etc/init.d/rcS 的內容了,當我們昇級 Debian 的時候,該檔案的內容很可能會改變,我們的設定就付諸流水了。

撰寫專用的 init.d script 不但提供 SysV Init 架構下的統一管理介面,也不會因為系統套件的昇級而洗掉我們辛苦寫下的設定,但缺點就是一開始作的時候比較麻煩一點。不過我們既然有幸讀到這一段,那這個缺點自然也就不算缺點了 。

一般的 init.d script 至少提供 start, stop, restart 等三個參數,分別會啟動、停止與重新啟動服務。我們還是拿 bind9 (很有名的名稱伺服器軟體) 來當例子,假設我們改變了 bind9 的 zone 檔案,要重新啟動 bind9:

$ /etc/init.d/bind9 restart
Stopping domain name service: named.
Starting domain name service: named.

如果我們臨時想停止 bind9 的服務,可以

$ /etc/init.d/bind9 stop
Stopping domain name service: named.

再啟動:

$ /etc/init.d/bind9 start
Starting domain name service: named.

init.d script 的操作幾乎都是這樣。有時我們會多用 reloadforce-reload 參數,不過在絕大多數的情況下,start, stop, restart 就夠了。

當我們要自製 init.d script 的時候,不需要從頭來一遍,/etc/init.d/skeleton 可以用來當作範本。我們來作一個沒用的服務試試看。先從 skeleton 複製一份新檔:

$ cp /etc/init.d/skeleton /etc/init.d/useless ; ls /etc/init.d/useless
/etc/init.d/useless*

這個檔必須要有執行權限,否則不能正常運作;如果它還沒有被設定執行權限 (但這情形一般是不會發生的),請

$ chmod a+x /etc/init.d/useless

準備好了之後,我們就 vi /etc/init.d/useless

首先要修改其中的變數設定:

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="some daemon"
NAME=daemon
DAEMON=/usr/sbin/$NAME
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

其中的 DESC 是一段用來描述服務的字串,我們應該要改成自己的版本:

DESC="useless service"

NAME 用作 Debian 指令 start-stop-daemon 的參數,通常會設成 DAEMON 所指定的路徑的檔名 (basename):

NAME=hostname

DAEMON 是我們要執行的那個程式的完整路徑:

DAEMON=/bin/$NAME

PIDFILESCRIPTNAME 通常使用預設的寫法,它們分别是作為紀錄服務的行程 ID (pid) 和這個 init.d script 本身的路徑之用。因為我們這個 init.d script 的檔名是 useless,與要執行的程式不同,所以 SCRIPTNAME 應該改一下:

SCRIPTNAME=/etc/init.d/useless

這樣填完以後,我們的服務就可以動了:

$ ./useless start
Starting useless service: hostnamethehostname
.
$ ./useless stop
Stopping useless service: hostname$

我們這台機器的名字是 thehostname,而 hostname 這個指令是用來顯示主機名稱的。所以,我們會看到服務在啟動之後 (hostname 的字樣),hostname 的執行結果 thehostname 也被印出來了。不過,結束行程的指令好像怪怪的?

沒錯,這種執行結果有點不正常,因為 hostname 本身是一個前景程式,而不是一個服務。服務程式既然要用來服務其它使用者或程式,就要能一直在系統裡運作,如果這種程式跑在前景的話,終端機行程一結束,程式也會跟著結束,服務就死掉了。如果一個服務程式不會自己跑到背景去,或者我們硬想把一個前景應用程式跑成服務 (像 hostname 這樣),那麼要在 start-stop-daemon 的參數上動一點手腳。

我們剛剛用 hostname 這個程式當作例子,是因為它是最簡單的一個程式,而且會印出訊息。不過 hostname 這個程式太簡單了,它的執行真的是一瞬間的事,實在不太像是要長時間運行的服務,所以我們把 DAEMON 換成另外一種跑得比較久的程式:

NAME=sleep
DAEMON=/bin/$NAME

sleep 這個程式後面還要接一個整數值參數,來指定要睡覺的秒數,所以我們再加寫一個變數:

ARGUMENTS=1000

再來,我們改一下 useless 中的 d_start() 副程式:

d_start() {
  start-stop-daemon --start --quiet --pidfile $PIDFILE \
    --background --make-pidfile \
    --exec $DAEMON -- $ARGUMENTS
}

比原來多了 --background--make-pidfile 參數,用來強制把 sleep 丟到背景,並且建立 pid 檔案。另外還在最後面加了 -- $ARGUMENTS,這會把 $ARGUMENTS 的內容字串指定給 sleep 程式當參數。

讓我們再試一次:

$ sudo /etc/init.d/useless start
Starting useless service: sleep.
$ sudo /etc/init.d/useless restart
Restarting useless service: sleep.
$ sudo /etc/init.d/useless stop
Stopping useless service: sleep.

這樣就都正常了。不過,各種不同的程式常常需要很多怪怪的處理方法,才能把它們丟到背景去當成服務,例如我們在 start-stop-daemon 的參數上所下的手腳,請參考 start-stop-daemon(8)。有時候除了利用 start-stop-daemon 的參數外,還得另外寫一些 shell script 程式來處理額外的問題,這就比較需要靠管理上的經驗了。我建議可以多看看 /etc/init.d 目錄裡其它服務的寫法,抄來用在自己的 script 裡面。

一旦為服務寫好了 init.d script,就可以套用我們之前在「runlevel 管理」裡所討論的 SysV runlevel 管理模式。

6   常用的工具程式

為了方便操作,以及解決一些必要的操作問題,我們手邊應該要有一套工具程式。當然,視日常作業的型態,所需要的工具程式也會有所不同,但還是有一定會用到的基本工具組。對於這些工具組,我們應該要能熟悉到一個相當的程度,才不會妨礙我們的工作能力與效率。

6.1   screen

screen 是一個終端機模擬器,是遠端使用者的福音,是 CLI 使用者的寶貝。

screen 具備包括 VT100 和 ANSI 終端機的模擬能力,但更重要的是它可以簡單地同時管理許多個終端機。screen 在我列的常用工具程式裡排第一個,至少對我來說是非常非常地重要。不過很可惜,我找不出除了文字之外,描述它重要性的其它方法。

想像一下,如果我從我的 Windows 用 PuTTY 連到了 thehost 主機上,開始 vi /etc/inittab,想要改一些系統的啟動設定。改到一半我發現,到底某個我要用的指令碼叫什麼名字,想去參考一下 inittab(5)。這時候我有兩個選擇:

  • 用 shell 的行程控制功能,先 Ctrl-Z 暫停 (suspend) 目前的 vi 行程,回到 shell 以後 man inittab。看完以後,用 fg 叫回原來的 vi 繼續編輯。
  • 再開一個 PuTTY,登入另一個 shell 執行 man inittab,同時在原來的連線裡編輯 /etc/inittab

第一個方法的缺點是我沒辦法同時看著 /etc/inittab 的內容和 manpage。指令那麼多,不小心忘掉是常有的事,到時候又要重來一次。第二個方法好一點,但就是我的 Windows 辛苦點,要多跑一個 PuTTY,而且兩個視窗又沒有編號,不小心可能會弄混掉15

[18]若說要先關掉原來的 vi,看完 manpage 之後再重新 vi /etc/inittab,那實在不夠聰明。

screen 可以完全解決這個問題。進入 shell 之後,在第一時間執行

$ screen

以使用 screen 的終端機模擬功能。在 screen 裡面可以直接開啟新的終端機,每個終端機都有編號 (我們也可以另外為個別的終端機取名字)。

screen 另外也具備行程 (session) 管理的能力,每一個 screen session 都可以被 detach (拆卸) 和 attach (或稱 reattach,接合或接續)。當我們的上班時間結束,但工作還沒有結束,又不想關掉我們的編輯器和正在編譯的工作時 (或是任何程式),可以直接把 screen session detach 掉。這些程式 (包括 shell) 並不會結束,而會一直留在系統裡面,等到明天來上班了,只要再把這個 session 接回來,原來的工作都還會在那裡,等著我們繼續進行 (準備好解決那些 make error 了嗎)。

這種行程管理能力非常好用。如果我本來在控制台前面操作 Debain,突然有需要回到 Windows 工作機上繼續操作,那麼只需要 detach :: 走回 Windows :: attach,原來的作業完全不會被中斷。又如果我在辦公室沒有把工作作完,那麼 detach :: 回家 :: attach,又可以從同一個地方繼續工作。

更方便的是,假設我的 Windows 在我連到 Debian 上進行遠端作業的時候很忠實地出現了藍底白字,我可以完全不用擔心會因為終端機當機而讓我在 Debian 上的工作被不正常結束。因為對 Debian 上的程式來說,我在使用的終端機是 screen,PuTTY 這個終端機當掉沒有關係,screen 不會因為這種小事就出問題。當我重新啟動我的 Windows,或是我移到另一個系統重新連線到 Debian 之後,只要再 attach 這個 session,一切可以照舊進行。就像什麼事都沒有發生過一樣。

工具還是要實際用過,才會知道該怎麼用。我們在表 1裡列出了進入 screen 程式之後,常用來控制 screen 的快速鍵 (快速鍵均以 Ctrl-a 開始,然後接另外一個鍵;例如 Ctrl-a, w 表示先按一次 Ctrl-a``,再按 ``w;如果寫成 Ctrl-a, w/Ctrl-w 的話,表示第二個鍵是按 wCtrl-w 都可以)。

表 1: screen 常用快速鍵

Ctrl-a, w/Ctrl-w 從螢幕的左下方開始,列出目前所有的終端機;目前使用中的終端機會以 * 表示出來。
Ctrl-a, ? 顯示說明畫面,列出所有 screen 的快速鍵;按空白或 Enter 可以離開說明畫面。
Ctrl-a, c/Ctrl-c 建立新的終端機 (視窗)。
Ctrl-a, <number> 切換到 <number> 號終端機。
Ctrl-a, Ctrl-a 回到上一個使用中的終端機。
Ctrl-a, a 送出真正的 Ctrl-a 訊號。
Ctrl-a, k/Ctrl-k 強迫結束目前的終端機。
Ctrl-a, d/Ctrl-d detach 目前的 screen session。
Ctrl-a, s/Ctrl-s 關閉終端機的輸出 (停止把輸入資料回應到螢幕上的動作)。
Ctrl-a, q/Ctrl-q 開啟終端機的輸出 (把輸入的資料回應到螢幕上)。

如果要關閉 screen 所開出來的終端機,在該終端機的 shell 裡輸入 exit,離開這個 shell 就可以了;當 screen 的最後一個終端機被關閉的時候,screen 程式就會結束該行程,同時結束程式本身的執行。

表 2 列出了 screen 常用的參數;若要 attach 先前被 detach 的 screen session,一定要在執行 screen 的時候用參數指定,否則 screen 會建立新的 session。

表 2: screen 常用參數

-ls 列出系統目前存在在的 session。
-r [session name] attach 系統中的 session,並且允許使用 [session name] 來以名稱指定要接續哪一個 session;如果沒有輸入的話,預設會接回唯一的一個 session;如果系統中存在超過一個以上的 session,則一定要指定接續的名稱。session 的名稱可以透過 screen -ls 查得。
-d [session name] 從外部強迫 detach session,可以用 [session name] 指定要 detach 的 session 名稱。以 -d 配合其它參數會有額外的效果,請參考 screen(1)

6.2   tar 與 gzip

Debian 下預設一定會安裝 targzip 程式,因為 deb 套件檔就是用 targzip 進行打包的;這兩個程式分別包裝在同名的套件內。

要解開 tar 的包裝檔 (一般稱之 tarball),我們可以下這樣的指令:

$ tar xvfz /path/to/tarball.tar.gz
.
.
.

.gz 結尾的檔案通常意指 gzip 壓縮檔,所以我們在 tar 後面加上選項 ztar 的選項 x 表示要解開 tarball,v 表示顯示相關資訊,而 f 表示我們要指定一個檔案,不從標準輸入讀資料進來。

在以前沒有 gnu tar 的時候,要解開一個又 targzip 過的檔案得這樣子作:

$ gunzip -c /path/to/tarball.tar.gz | tar xv
.
.
.

gunzip -c 表示解開 gzip 檔之後,用管線 (pipe) 導向到 tar 去,交給它來繼續拆 tarball。

如果我們對某個 gzip 檔直接 gunzip 的話,結果會是

$ ls tarball.tar*
tarball.tar.gz
$ gunzip tarball.tar.gz ; ls tarball.tar*
tarball.tar

讓原本那個 gzip 檔的 .gz 副檔名不見了。反之,如果 gzip

$ ls tarball.tar*
tarball.tar
$ gzip tarball.tar ; ls tarball.tar*
tarball.tar.gz

則會加上 .gz 副檔名。

tar cvf 可以建立新的 tarball,語法是:

tar cvf <tarball> <list of files to be added>

這種未經壓縮的 tarball,副檔名通常會取成 .tar。如果我們想用 gzip 來進行壓縮,選項加個 z,改成 tar cvfz 就可以了。通常經過 gzip 壓縮的 tarball 副檔名是 .tgz.tar.gz

6.3   bzip2, zip 與 rar

Debian 也配有 bzip2, zip 和 rar 等壓縮格式需用的程式。

bzip2 套件裡包含了 bzip2bunzip2 這一對壓縮與解壓縮程式,以及一些相關的工具程式。bzip2 和 gzip 一樣,只能對單一的檔案進行壓縮與解壓縮,所以若要壓縮一組檔案,我們會先包在一個 tarball 裡,再用 bzip2 進行壓縮。bzip2 的操作方法和 gzip 差不多,壓縮用

bzip2 <file to be compressed>

解壓縮用

bunzip2 <file to be decompressed>

bzip2 也會自動附上或摘除識別用的副檔名 bz2。bzip2 的壓縮率比 gzip 更好,Debian 的 Linux 核心原始碼套件就是用 bzip2 來壓的。

要處理 PKZIP 的 .zip 格式壓縮檔,我們需要安裝 zip 與 unzip 套件,分別提供壓縮與解壓縮用的 zipunzip。這兩個套件是由 InfoZIP 所開發的工具程式。zip 可以用來壓縮多個檔案,語法是

zip <zip file name> <list of files to be added>

如果被壓縮的項目有目錄,那麼要加上 -r 選項,指定進行遞迴壓縮作業:

zip -r <zip file name> <list of files to be added>

解壓縮動作的語法則和 gunip, bunzip2 一樣,只要接上被解壓縮的檔名:

unzip <file to be decompressed>

就可以了。

RAR-lab 的 .rar 壓縮檔,也是常見的格式。Debian 中的 rar 和 unrar 兩個套件都提供解壓縮 .rar 檔案的能力,但 rar 是原 RAR-lab 所發行的 shareware,使用的話應該要在 40 天內註冊。unrar 則是可以一直使用的,但它不能執行壓縮作業。

要建立 .rar 壓縮檔,我們用以下的語法呼叫 rar

rar a <rar file name> <list of files to be added>

解壓縮的話,則可以用

rar x <file to be decompressed>

或是

unrar x <file to be decompressed>

關於這些工具程式的詳細使用方法,請參考 bzip2(1), bunzip2(1), zip(1), unzip(1), rar(1)+ unrar(1)

7   結語與展望

本文基於 Debian Bare System 一文cite{bib:debian:bare_sys}所建立的基礎, 說明如何利用 Debian 套件來建構一個方便的 CLI 終端機工作站, 並對相關的使用模式與組態手段進行討論。 主要包含了檔案的內容編輯操作、重要的 system-wide 系統設定與 shell 功能、 使用者的管理及檔案的權限設定,以及操作系統時常會用到的工具程式等等內容。

這樣的一個終端機工作站的建構與使用經驗, 可以作為實作特定服務或進行特定操作時的基礎。 未來,我們可以結合各種不同的操作與組態技巧, 配上相關作業的專門知識,實作出更結構化與自動化的應用系統。

[VIM]http://www.vim.org/