2007年6月24日 星期日

封鎖區網內特定電腦上網的軟體 - NetCut

今天看到一個有趣的軟體,稱為 NetCut,號稱可以封鎖區網內特定電腦的網路流量!

想說怎麼會有這麼強的軟體......查了一下,原來它是利用 fake ARP reply 假造 gateway 的 hardware address 來欺騙特定電腦,讓該電腦的資料沒辦法正確送給 gateway,以達到封鎖網路流量的目的....

以下是在資安論壇的說明:(以下節錄自資安論壇的討論串「請問一下有關於netcut這套軟體的原理」)
如何避免或破解Netcut造成的斷線情形


要破解使用Netcut造成的斷線問題,必須先了解Netcut的運作原理。由於Netcut使用的是假造ARP封包造成目標主機ARP table記錄錯誤來達成斷線目的,因此必須先由ARP協議開始說明。

在乙太網路上僅僅知道某台主機的IP address,並不能立即將封包傳送過去,必須先查明該主機的實體位址(Physical address / MAC address)才能真正發送出去,而ARP協議的功用就是在於將IP address轉換成實體位址。

網路上每一台主機都有一個ARP table,此table中記錄了最近一段時間裡其它IP address及其MAC address的對應關係。如果本機想跟某一台主機通信,則會先在ARP table中查尋對應目的主機IP address的MAC address,如果該對應記錄存在,則直接將目的主機的MAC address填入Data Link層的封包表頭中,然後將封包發送出去;如果該對應記錄不存在,則會向本網段廣播一個ARP請求封包,當目的主機聽見該請求封包後,會將本身的 MAC address填入封包並用廣播方式回送出去,本機收到此回應封包後,就會將相關訊息記錄在ARP table中,然後將目的主機的MAC address填入Data Link層的封包表頭裡。

由於ARP請求封包發送端只管接收回應訊息,卻無法分辨訊息的真偽,因此第三方主機只要建構一個ARP欺騙封包,就可以造成請求端的ARP table資訊錯誤。由於MAC address不正確,所以封包就再也無法傳送到目的主機上,這就是Netcut造成連線中斷的原因。

舉例來說,裝有Netcut的A主機向受害B主機發送假的ARP訊息,使得B主機上ARP table中對應到閘道器IP address的MAC address,更新成錯誤的MAC address。由於B主機上網必須透過閘道器傳送,閘道器的MAC address資訊錯誤,當然會造成B主機的封包再也無法傳送到閘道器上,原本建立好的連線也會因為timeout而導致斷線的情形發生。

知道Netcut的運作原理了,可是要怎樣才能預防或解決被Netcut斷線的問題呢?其實只要下達一個小小的指令就可以對Netcut完全免疫了~~

方法很簡單,由於Netcut的工作原理是透過假造ARP封包,造成你主機上的ARP table記錄到錯誤的閘道器MAC address,藉此讓你的主機跟目地主機間的往來封包發生中斷,所以你只要將正確的對應位址設定成static記錄就可以避免狀況發生。

設定指令如下:
arp -s 閘道器IP address 閘道器MAC address

舉例來說,假設閘道器的IP address是192.168.88.254,打開命令提示字元,執行ping 192.168.88.254,只要ping得通就可以得到正確的閘道器MAC address。這時執行 arp -a 就可以查出192.168.88.254的對應MAC address(就是Physical Address)。例如192.168.88.254的MAC address是00-90-cc-4f-db-18,那麼只要執行 arp -s 192.168.88.254 00-90-cc-4f-db-18 就搞定了。

假如你的主機已經被斷線,這時該怎麼辦呢?很簡單,你只要借用同網段的其他主機查詢閘道器的MAC address,然後用上述方法將正確的對應資訊加入到你的主機上就行了。

注意,如果主機的閘道器IP address改變,MAC address通常也會跟著變動,這時只要下達 arp -d 就可以將原先設定的ARP table對應資訊清除掉。

=== 節錄 - 微風廣場 rien (萊因哈特‧馮‧羅嚴克拉姆) ===

恩....看完覺得,真的有人就是這麼閒要去惡搞別人......是那個人為人太機車還是.....?? 算了,不得而知.......

[TCP/IP Illustrated] ARP & RARP

簡介

若回頭去看看 TCP/IP 四層,會發現 IP 屬於 Network Layer,這表示 IP 僅在 Network Layer 可供辨識,並無法在 Link Layer 中提供辨識的功能;因此在 Link Layer 中,Ethernet、Token Ring.....等提供了一個長度為 48 bit 的硬體位址作為辨識之用。當 frame 在傳送時,所依據的是其硬體位址,而非 IP address,因為 device driver 並不會去檢查 IP datagram 中的 IP address。

ARP(Address Resolution Protocol) 以及 RARP(Reverse Address Resolution Protocol) 就是用來進行 IP address(32 bits) 與硬體位址(48 bits) 的對應之用,以下用一張圖來說明:

從圖中可以看出,兩個 protocol 的目的地都相同(將 32-bit Internet address 與 48-bit Ethernet address 作對應),只是所擁有的資料與要找的資料相反罷了!

就一般的狀況來說,ARP 是用於一般的情形,有 IP address,要取得相對應的 hardware address。

RARP 就比較不一樣了,大部分是用於沒有磁碟(diskless)的主機環境中,由於沒有磁碟,因此就沒有地方可以儲存主機的 IP address 資訊,因此每次主機啟動後,都必須透過 RARP 去詢問自己的 IP address,因此在該網段也必須要有一台 RARP server 存在。


ARP 運作原理

簡單來說,將 IP address 轉換為 Hardware address(以下稱為 MAC address) 的過程,就是 RAP 所負責的工作,運作的方式如下:
  1. 傳送端送出「ARP request
    此為 Ethernet frame,並且是廣播封包,因此該網段的所有主機都會收到,而在此 frame 中包含了目的地主機的 IP address,因此這個 frame 廣播出來的用意在於告訴大家:「如果您是這個 IP address 的擁有者,請告訴我您的 MAC address」
  2. 接收端回應「ARP reply
    當接收端接收到 ARP reqeust 後(不是此 IP address 的主機就會把 frame 丟棄了),就會回應 ARP reply,其中包含了接收端主機的 MAC address
  3. 傳送端取得「ARP reply
    有了原本的 IP address 以及用 ARP 取得的 MAC address 之後,就可以開始傳遞資料了!


ARP Cache

若一次傳送資料,每一次都要使用 ARP 取得接收端的 MAC address,會造成傳輸效率的低落,因此就有 ARP cache 的產生,透過 cache 的機制,將已經知道的 IP address 與 MAC address 暫存起來,就不必一直重複進行用 ARP 取得 MAC address 的工作。

然而,在 Linux 或是 Windows 中,都可以透過以下指令查看 ARP cache 有哪些資料:
shell> arp -a
基本上,cache 也是有其 timeout 的時間,實際時間搞不清楚,不過一段時間沒用後,ARP cache 中的內容的確是會被清除掉,有興趣的人可以自行實驗。

【注意】在 Linux 中有提供處理 ARP cache 的指令,稱為「arp」,有興趣的人可以參考 arp 的 manpage


ARP 封包格式

首先要說明,ARP request 與 ARP reply 的封包格式是相同的,如下圖:

欄位說明如下:
  1. Ethernet destination addr & Ethernet source addr
    當 destination addr 全部為 1 時,表示此為廣播(broadcast)封包
  2. frame type
    描述接在這欄位之後的資料的型態為何,例如:0x0806 表示後續的資料為 ARP request 或是 ARP reply
    【註】RARP 封包為 0x8035
  3. hard type
    描述 hardware address 的型態為何,例如:0x0001 為 Ethernet
  4. prot type
    描述對應到何種型態的 protocol address,例如:0x0800 為 IP address
  5. hard size
    描述 hardware address 的長度(單位:byte),因此一般此欄位的值為 0x06
  6. prot size
    描述 protocol address 的長度(單位:byte),因此一般此欄位的值為 0x04
  7. op
    描述此封包是用於哪種功能上,例如:0x0001(ARP request)、0x0002(ARP reply)、0x0003(RARP request)、0x0004(RARP reply)
  8. sender Ethernet+IP addr & target Ethernet+IP addr
    這些欄位看名稱就很清楚是儲存哪些資料,就不多提了! 只是比較需要注意的是,在 Ethernet header 與 ARP request/reply 中,有重複的部分(Ethernet address)


實際範例

接著用個實際範例說明,ARP 是如何運作的,以下使用 tcpdump 程式觀察封包的進出:
# -e 參數是用在顯示 link layer 的 header inforamtion 之用(斜體字部分根據實際情況必須更改)
shell> tcpdump -i eth0 -e host 140.137.12.10
當另外一台主機 telnet 進來後,就會有封包資料的進出,輸出內容大概如下:

一開始傳送端主機不知道接收端主機的 MAC address,就必須發出 ARP request 去取得,因此就會有上面的封包出現。

有一個地方沒有框起來,但是卻需要注意的地方,是「length 60」的部分;一般來說,ARP request/reply 的長度僅只有 42 bytes(14 bytes Ethernet header + 28 bytes ARP request/reply),但由於 Ethernet frame 最小長度必須為 60 bytes,因此每個 ARP request/reply 會被填滿至 60 bytes 再送出。

此外,之後若是在短時間內再度 telnet 一次遠端主機,就不會有 ARP 的封包出現了,原因是資料已經存在於 ARP cache 中,不需在發送 ARP request 取得 hardware address。

最後,若要觀察在目前的網段中,所有的 ARP request/reply 的內容,可以用以下指令:(ARP 是廣播封包,所以可以全部擷取到)
# 參數 -n 表示要以 IP 的形式呈現
shell> tcpdump -n arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
17:06:09.837584 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:12.874831 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:18.825728 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:30.902268 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:32.711703 arp who-has 140.137.12.43 tell 140.137.12.41
17:06:32.711772 arp reply 140.137.12.43 is-at 00:02:b3:ad:85:43
17:06:33.914824 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:37.658513 arp who-has 140.137.12.10 tell 140.137.12.42
17:06:37.658531 arp reply 140.137.12.10 is-at 00:11:2f:0e:cd:dd
17:06:39.950699 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:42.655529 arp who-has 140.137.12.42 tell 140.137.12.10
17:06:42.655674 arp reply 140.137.12.42 is-at 00:50:ba:1e:d3:af
17:06:43.015543 arp who-has 140.137.12.90 tell 140.137.12.10
17:06:43.015830 arp reply 140.137.12.90 is-at 00:01:80:0a:f2:09
17:06:44.620024 arp who-has 140.137.12.247 tell 140.137.12.229
17:06:44.620078 arp reply 140.137.12.247 is-at 00:40:f4:d8:4e:72
17:06:52.023248 arp who-has 140.137.12.56 tell 140.137.12.254
17:06:55.124773 arp who-has 140.137.12.56 tell 140.137.12.254
17:07:01.205729 arp who-has 140.137.12.56 tell 140.137.12.254
17:07:13.165892 arp who-has 140.137.12.56 tell 140.137.12.254
............
............


Proxy ARP

Proxy ARP 是讓 router 可以針對不同網段間的 ARP request,回傳正確的 ARP reply。基本上,傳送端目的僅是要取得接收端的 hardware address,與其讓 router 將 ARP request 封包 forward 到另外一個網段作廣播,不如就自己回應,網路使用的效率上反而好些(前提是 router 必須要知道接收端的 hardware address 以及 IP address)。


Gratuitous ARP

ARP 還有另外一種功能,就是在主機發送 ARP request 的目的是取得自己的 IP address 所對應的 hardware address 時,稱為 Gratuitous ARP。一般來說,通常都是發生在系統啟動時,或是將網路卡重新啟動時(所以在 Windows 中設定與別台主機相同的 IP 時會有警告訊息,就是 Gratuitous ARP 的功能)。

而 Gratuitous ARP 提供了兩大功能:
  1. 檢查是否有重複 IP address
  2. 當主機更換、或是網路卡更換後,透過 Gratuitous ARP 可以通知網段中的其他主機,IP address 與新的 hardware address 的對應關係

這個功能還曾被提出來可應用於 failover 的情況,可惜似乎不是每個 OS 都有實作出相同功能,所以這個部分稍微看看就好。


RARP 運作原理

其實跟 ARP 也沒有太大差異,只是所發出去跟接收回來的封包,分別是 RARP requestRARP reply

詳細的協定內容可以參考 RFC903

還有一點是需要注意的! 由於 RARP 會發送廣播封包,因此被定位為 Link Layer 的協定,因此封包是沒辦法跨過 router 的喔! 就不會造成網路壅塞的情況發生。


RARP 封包格式

RARP 的封包格式,與 ARP 是相同的,僅是在 frame type 欄位中,儲存的值為 0x8035。

【註】ARP 為 0x0806


2007年6月19日 星期二

[TCP/IP Illustrated] IP(Internet Protocol)

簡介

IP(Internet Protocol) 可說是 TCP/IP 中眾多協定中最重要的一個也不為過,因為有許多協定(TCP、UDP、ICMP、IGMP....等等)都必須要靠 IP 將資料封裝成 IP datagram 來傳送;不過 IP 是屬於不可靠的傳輸協定,因為它僅提供了簡單的錯誤處理功能,作用僅是當發現錯誤後,就回傳 ICMP 訊息給傳送方。因此,若需要更複雜的錯誤處理功能,就必須要在 IP 上層的其他協定去實作。

然而,IP 還被稱為是 connectionless 的協定,原因是因為在傳輸的過程中,IP 並不會為傳輸的資料維護 state 資訊,所以就是....一直送、一直送.....等到發現錯誤再重送....所以也有可能原先傳遞出去的 IP datagram,分別走了不同的路徑,而讓目的地接收資料的順序不一樣。

不過也因為 IP 沒有其他很複雜的功能在其中,因此在使用上效率也是最好的。

在這一篇文章中,會介紹 IP Header 的組成、IP Routing、Subnetting....等等,以下就來慢慢說明。


IP Header

之前一直提的 IP datagram 就是在從上層協定傳給 IP 的資料,再加上 IP header 所形成的,一般來說,IP header 的大小為 20 bytes,以下用一張圖來說明 IP header 中的各欄位:

上圖為一個完整的 IP datagram,其中以紅色框線框起來的部分(也有可能包含到紅框下方的 options),即為 IP header;而比較需要留意的是,資料以 1 bytes(8 bits) 為單位進行傳送,由圖中的左邊編號為 0 所屬的 byte 開始傳送,傳送的順序分別是:
  1. 第 0~7 bits
  2. 第 8~15 bits
  3. 第 16~23 bits
  4. 第 24~31 bits
  5. 以下以此類推..............
這種傳送順序,稱為 Network Byte Order(NBO),TCP/IP 所使用的傳送順序即是如此。

但不巧的,NBO 與某些作業系統或是機器上的 Host Byte Order(HBO) 卻有些是相反的! 因此在撰寫網路程式的時候,程式設計師就必須去判斷 NBO 與 HBO 的順序是否相同,來決定是否進行轉換。
【註】若有興趣深入瞭解可以參考此篇文章(Beej的網路socket編程指南)

接著要來介紹各個欄位:
  • version (4 bits)
    表示協定的版本,一般來說是 4,表示 IPv4;但目前 IPv6 已經開始慢慢有在使用了
  • length (4 bits)
    指定 header 長度為 4 bytes(32 bits) * length。架設情況如下:
    length=5:header 長度為 4 bytes * 5 = 20 bytes (此為預設值,沒有 options 的情況下)
    length=6:header 長度為 4 bytes * 6 = 24 bytes (即表示 options 欄位的長度為 4 bytes)
    因此可以瞭解,header length 最多可以指定到 4 bytes * 15(1111) = 60 bytes。
  • type of serviceTOS (8 bits)
    雖然此欄位長度為 8 bits,但前三個與最後一個 bit 都不用理會,因為在目前都沒在使用,有在使用的為第 4~7 的 bit,以粗體紅字表示 => 00000000
    此 4 個 bit 分別要表達的是:
    4th:minimize delay
    5th:maximize throughput
    6th:maximize reliability
    7th:minimize monetary cost
    每個 bit 所訴求的目的並不一樣,因此 TOS 的設定就是根據協定的特性需求來決定 TOS 所應該設定的值,以下有一張簡單的表作為範例:

    從上表各種協定與 TOS 的搭配,應該不難看出每個協定所著重的重點為何吧!
  • total length (16 bits)
    指定整個 IP datagram 的大小,單位為 byte,由於有 16 bits 可以表示,因此每個 IP datagram 最大可以到 65535 bytes。這正好可以驗證上一篇所提到的,MTU 最大不會超過 65535 bytes。但是一般說來,過大的 IP datagram 由於傳送上的困難或是其他協定的限制,會被切割為好幾個小部分。
    【註】透過 total length 與 length 欄位的搭配,就可以計算出在 IP datagram 中資料的實際大小
  • identification (16 bits)
    此欄位是用來識別傳送端所發出的 IP datagram 之用,每個 IP datagram 都會有一個 unique 的 identification 值,一般來講就是以遞增值的方式來作為識別之用
  • flags (3 bits) & fragment offset (13 bits)
    此兩欄位是作為進行 fragmentation 時之用
  • time to liveTTL (8 bits)
    這個欄位中所儲存的值,代表送出的 IP datagram 可以通過 router 數量的上限,此值由傳送端設定,每經過一個 router 會減少 1,當值減為 0 後,則會被丟棄不再繼續傳送,然後傳送端會收到 ICMP 訊息作為通知,避免傳送端一直送出無法達到目的地的封包。
  • protocol (8 bits)
    此欄位在第一篇學習筆記中就提過啦! 目的是在記錄使用 IP 傳送資料的是上層的哪一個協定
  • header checksum (16 bits)
    作為檢查 IP header 之用,跟其他協定(例如:TCP、UDP、ICMP....etc)的 header 無關,因為其他協定的 header 有自己的 checksum 欄位。
    另外,由於 checksum 是依據 IP header 中各欄位所計算出來的,因此在 IP datagram 經過 router 而 TTL 減 1 後,router 會重新計算 checksum 的值並繼續傳遞 IP datagram
  • source IP address (32 bits)
  • destination IP address (32 bits)
  • options (不定長度)
    最後這個欄位就比較少用到了,長度不固定,但以 4 bytes(32 bits) 為單位,而有可能被用在以下幾個地方:
    • 加強安全性
    • 記錄 route 資訊的
    • 記錄時間戳記
    • 指定 routing 的路徑


IP Routing

要知道資料是如何在廣大的網際網路上傳遞? 如何從傳送端,跨過一個個 router 到達目的地? 就是 IP Routing 的觀念了!

當傳送端要傳送 datagram 時,而接收端卻不在同一個網段內,此時 datagram 就會被送到預設的 router 上,router 在根據本身的 routing table 與 datagram 中的目的地比對後,決定要傳送的下一個 router,一直到 datagram 到達目的地為止。

在 IP Layer 中,不僅可以扮演 host 的角色,也可以扮演成 router 的角色,也就是說,不但可以接收資料,也可以傳遞資料(其實 host 與 router 的差別就在於是否有 forward 資料)。而在目前的作業系統(Unix、Linux....etc)都支援這樣子的功能,只要經過正確的設定,以及安裝多張的網路卡,就可以讓電腦不僅僅只能接收資料,還能提供 router 的功能。

而 IP Layer 是怎樣處理從 interface 接收到的 datagram 呢? 以下是其處理規則:
  1. 檢查 datagram 中的 destination IP address 是否屬於自己的 IP address 或是 IP broadcast address
  2. 是(Yes)
    此 datagram 會根據其 IP header 中指定的 protocol,被 IP Layer 往上層送由正確的協定來處理
  3. 否(No)
    • 若此 IP Layer 有被設定為具有 router 的功能,則會將此 datagram 由規則中指定的 interface forward 出去
    • 若 IP Layer 中無提供 route 的功能,則此 datagram 會被丟棄,並且不對傳送方進行任何通知

不論是用一般的主機作為 router,或是直接使用單純的 router,最重要的是「routing table」;routing table 是一條條規則所組成,用來描述到不同目的地的 datagram,應該經由那個 interface 將其 forward 出去,也就是應該要走哪條路徑,一般來說,每條在 routing table 中的規則都會包含以下幾種資訊:
  1. Destination IP address
    這邊指的有兩種可能,分別是:
    • host address
      單一 host 的 IP address,例如:140.137.10.200
    • network address
      某個網段,例如:140.137.12.0/24
  2. Next-Hop router
    這部分的資訊是儲存在名稱為「Next-Hop router」的欄位上,同樣有兩種可能,分別是:
    • Next-Hop router
      要到上述目的地 IP 位址的下一個必須傳送的 router
    • 直接連接到 router 的網段中的 IP address
  3. Flags
    在 Flags 的部分是由兩個 flag 所組成。
    第一個 flag 描述 Destination IP address 為 host address 或是 network address。
    第二個 flag 則描述 next-hop router 欄位中所儲存的,是下一個要將 datagram 繼續 forward 到目的地的 router,或是直接連接到此 router 的網段
  4. 指定從哪些 interface 經過的 datagram 可以將其 forward

IP routing 是以 hop-by-hop 為基礎(這裡指的 hop 即為 router)所進行的,從 routing table 可以看出,並沒有涵蓋相當多的 routing 資訊,因此 IP 是無法知道 datagram 傳送到任何目的地的完整傳送路徑,但是 routing table 中都會有足夠的訊息至少可以讓 datagram 傳送到下一個距離目的地更近的 router,其作法如下:
  1. 根據 datagram 中的 destination IP address 欄位,在 routing table 中搜尋是否有符合的規則;若有發現合適規則,就根據規則將 datagram 傳送到下一個 hop 或是到另一個直接連接的 interface
  2. 若第一個步驟中沒有合適的規則,就會再次尋找,但是僅會尋找是否有符合的 network ID(網段);若有發現合適規則,就根據規則將 datagram 傳送到下一個 hop 或是到另一個直接連接的 interface
  3. 若前兩個步驟都沒發現合適規則,那就會使用 default 的規則將 datagram 傳送到下一個 hop
如果以上三個步驟都無法完成,則表示該 datagram 是無法傳送出去的,而傳送端就會收到 "host unreachable" 的錯誤訊息。

或許有人會問,為什麼不在 routing table 把所有規則都涵蓋起來就好了呢? 恩.....我想應該沒有人希望 routing table 中可能包含了數萬條的規則吧.....


子網路

這個部分包含了 Subnet Addressing 以及 Subnet Mask,慢慢解釋實在太久,剛好在網路上有找到講解得很詳細的文章,以下是連結:

IP 位址

2007年6月17日 星期日

bash 學習筆記 - 流程控制

與其他程式語言相比,bash 當然有提供的流程控制的能力,分別是 if/else、for、case、select、while/until,以下分別介紹這些語法的使用方式。

if/else

if/else 的語法如下:
if condition
then
statements
[elif condition
then statements...]
[else
statements]
fi

Exit Status

與其他程式語言(C、C++、Pascal....等等)不同的地方,在 condition 的部分,其他的程式語言是以 condition 為 True or False 來決定要執行哪一段程式;但在 bash 中卻不是如此!

在 bash 中,就必須按照 Linux 的運作方式來進行處理,因此 condition 是否成立取決於 command 的 exit status

而在 Linux 的所有指令,不論是由何種程式語言開發,執行完後都會回傳一個整數值給呼叫此程式的 process,此整數值即稱為 exit status;一般來說,「0」代表程式執行成功,而「1~255」則代表程式執行失敗。

有了 exit status 的概念後,if/else 的語法就可以修改如下:
if command ran successfully
then
normal processing
else
error processing
fi

Return

這邊要探討的是一般程式語言都有提供的 return 功能,其實說穿了,return N 即是 exit N(Exit Status 為N),不過比較不一樣的是,return 僅能用於 function 中,且要執行含有 function 的 shell script,必須透過 source 的方式來執行才有效。

總而言之,在 function 中使用 return 就跟在其他程式語言的方式差不多。

同時使用多個 Exit Status

或許有人會覺得,如果條件判斷中需要同時判斷兩個條件以上,要如何作呢? 在 bash 中當然也辦的到,就像其他程式語言一樣,bash 也可以針對判斷條件進行 &&(AND) 或是 ||(OR) 運算,來決定是否執行特定程式,以下用個簡單的範例說明:
filename=$1
word1=$2
word2=$3
if grep $word1 $filename || grep $word2 $filename
then
echo "$word1 or $word2 is in $filename."
fi

各種狀況的判斷

當然除了靠 Exit Status 外,還有許多不同的情況會發生,例如:字串比對、檔案屬性的檢查、數字比較....等等,以下逐一來進行介紹各種比對方式。

字串的比對:

Operator
成立條件
str1 = str2
這不多解釋了....
str1 != str2
這不多解釋了....
str1 < str2
這不多解釋了....
str1 > str2
這不多解釋了....
-n str1
當 str1 不為 null 時
-z str1
當 str1 為 null 時(長度為 0)

檔案屬性:
Operator
成立條件
-a file
檔案存在
-d file
檔案存在,並且是個目錄
-e file
檔案存在
-f file
檔案存在,且為一般檔案(非目錄或其他特殊型態)
-r file
目前的使用者有讀取檔案的權限
-s file
檔案存在且內容非空白
-w file
目前的使用者有寫入檔案的權限
-x file
目前的使用者有執行檔案(or 進入目錄)的權限
-N file
檔案自從上一次被讀取後,有被修改過
-O file
檔案的 owner 為目前的使用者
-G file
檔案的 group 為目前使用者所屬之 group
file1 -nt file2
file1 比 file2 的日期新
file1 -ot file2
file1 比 file2 的日期舊

如果使用上面的判斷時,最好加上「中括號」,變成類似以下的格式:
if [ condition ] && [ condition ]; then
當然也可以跟 Exit Status 結合,就會變成類似以下的格式:
if command && [ condition ]; then
以下來段簡單的程式(節錄自書):
# 檢查檔案是否不存在
if [ ! -e "$1" ]; then
echo "file $1 does not exist."
exit 1
fi

# 檢查是否為目錄
if [ -d "$1" ]; then
echo -n "$1 is a directory that you may "
if [ ! -x "$1" ]; then
echo -n "not "
fi
echo "search."
elif [ -f "$1" ]; then # 檢查是否為檔案
echo "$1 is a regular file."
else # 檢查是否為特殊型態的檔案
echo "$1 is a special type of file."
fi

# 檢查目前使用者是否為 owner
if [ -O "$1" ]; then
echo 'you own the file.'
else
echo 'you do not own the file.'
fi

# 檢查是否有讀取檔案的權限
if [ -r "$1" ]; then
echo 'you have read permission on the file.'
fi

# 檢查是否有寫入檔案的權限
if [ -w "$1" ]; then
echo 'you have write permission on the file.'
fi

# 檢查第一個參數是否有執行的權限,且非目錄
if [ -x "$1" -a ! -d "$1" ]; then
echo 'you have execute permission on the file.'
fi
當然,兩兩比較一定會有數字的比對,以下介紹數字比較的方式:
Keyword
效果
-lt
Less than (小於)
-le
Less then or equal (小於或等於)
-eq
Equal (等於)
-ge
Greater than or equal (大於或等於)
-gt
Greater than (大於)
-ne
Not equal (不等於)


for

for loop 在 bash 中當然也是不可欠缺的一員大將啦! 只是跟其他程式語言不同的地方,他並非以「數字」的方式直接指定 for loop 的程式要執行的次數,而是以一個串列的值(例如:1 3 4 6 8)去執行,有幾個值就執行幾次,因此格式大概如下:
for name [in list ]
do
statements that can use $name...
done
其實說穿了,使用方式就跟其他程式語言中的 for each 是差不多相同的,以下結合 if/else 判斷來個範例程式:
#!/bin/bash
IFS=":"
for dir in $PATH
do
if [ -z "$dir" ]; then
dir="."
fi

if ! [ -e "$dir" ]; then
echo "${dir} doesn't exist"
elif ! [ -d "${dir}" ]; then
echo "${dir} diesn't a directory"
else
ls -ld $dir
fi
done

另外再舉個例子,將 function、for、if/else 通通結合進來,下面這個程式的目的是以 recursive 的方式將參數中目錄底下的所有目錄列出,以下是程式內容:
echoDir() {
chrTab="${chrTab}t"

cd $1
for eachFilePath in $(ls)
do
if [ -d $eachFilePath ]; then
echo -e ${chrTab}${eachFilePath}
echoDir $eachFilePath
fi
done
cd ..

chrTab=${chrTab%'t'}
}

filePath=$1
chrTab=""

if [ -d $1 ]; then
cd $1
for eachFilePath in $(ls)
do
if [ -d $eachFilePath ]; then
echo $eachFilePath
echoDir $eachFilePath
fi
done
cd ..
fi
有興趣知道輸出結果的,可以 copy 回去自己試試看。


case

case 在 bash 中的用法其實就是跟 C、Java 等程式語言中的 switch case 差不多,以下是使用語法:
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac
以下用個簡單程式來說明:
for filename in "$@"; do
case $filename in
*.jpg | *.png ) echo "may be jpg or png" ;;
*.tga ) echo "tga" ;;
*.xpm ) echo "xpm" ;;
* ) echo "can not identified file type."
esac
done


select

這個部分就跟一般的程式語言很不一樣了! 而且僅在 bash 1.14 版以後才有支援喔! 透過 select,可以很容易的產生出選單,以下是使用 select 的語法:
select name [in list ]
do
statements that can use $name...
done
透過 select 語法,可以將 list 中的內容變成一個個的數字選項供使用者選擇,並且有兩個變數需要注意到,分別是 $name 以及 $REPLY;其中 $name 變數的值為選項的內容,而 $REPLY 的值則是選項所代表的數字

當使用者輸入某選項的數字時,$name 與 $REPLY 就會存入相對應的值,但若是輸入一個非選項內的數字,則兩個變數的值都會變成 null,因此可以透過檢查 $name 變數值是否為 null 來判斷使用者有無輸入合法的選項。

以下用一個簡單的小程式來作範例:
#!/bin/bash

# 指定分隔 $PATH 的字元
IFS=":"
# 變數 PS3 儲存使用 select 時提示使用者的字串
PS3="which path you want to use? "
select mySelect in $PATH;
do
if [ $mySelect ]; then
echo "mySelect = ${mySelect}"
echo "REPLY = ${REPLY}"
else
echo "no such option!"
# 透過關鍵字「break」就可離開了!
break;
fi
done


# 以下是執行結果:
1) /usr/kerberos/sbin 4) /usr/local/bin 7) /usr/sbin
2) /usr/kerberos/bin 5) /sbin 8) /usr/bin
3) /usr/local/sbin 6) /bin 9) /root/bin
which path you want to use? 1
mySelect = /usr/kerberos/sbin
REPLY = 1
which path you want to use? 5
mySelect = /sbin
REPLY = 5
which path you want to use? 10
no such option!
至於 select 要如何應用在許許多多的管理工作上,就需要管理者發揮創意了!


while & until

在 while 與 until 的部分,也是跟一般程式語言中的用法差不多,以下分別介紹 while 與 until 的語法:

while
while condition do
statements... done
until
until command ; do statements... done
其實說穿了,while 與 until 不過就是一體兩面的用法而已,差別僅在於 while 判斷條件是否為 true,until 判斷條件是否為 false

以下用兩段程式來說明,以下兩段程式的作用是相同的,只是分別以 while 與 until 來撰寫:
until [ cp $1 $2 ];
do
echo 'Attempt to cpoy failed. waiting...'
sleep 5
done
while [ ! cp $1 $2 ];
do
echo 'Attempt to cpoy failed. waiting...'
sleep 5
done
這裡僅是簡單用法的介紹,其他就靠大家發揮了!

2007年6月16日 星期六

[NFS] 解決「mount request from unknown host」的問題

剛剛在掛載 NFS partition 的時候遇到的問題....

其中有三台主機:
  1. NFS Server:192.168.0.1/24
  2. NFS Client1:192.168.0.10/24
  3. NFS Client2:192.168.1.10/24
請注意喔! Client 2 與 Server 是在不同網段上喔........


先說明一下原本在 NFS server 上的設定內容,如下:
/var/share/nfs/glite 192.168.*(rw,async,all_squash,anonuid=1000)

在此設定中,Client 1 是可以正確 mount NFS partition,但是 Client 2 卻失敗了! 在 Client 2 上面顯示了類似以下的訊息:
mount: 192.168.0.1:/var/share/nfs/glite failed, reason given by server: Permission denied
說也奇怪,只有在網段外的機器會有這種情形喔......

不過沒關係,此時我連到 Server 翻 log.....翻到類似以下訊息:
mount request from unknown host 192.168.1.10 for /var/share/nfs/glite (/var/share/nfs/glite)
啊......明明我在 server 就有設定進去啊! 怎麼會變成 unknown host ?

這跟本也不會是 firewall 的問題....(因為 showmount -e 是可以看到資料的!)

試了半天,終於發現問題所在,原來在 server 上不能這樣設定,應該要把設定改為如下:
/var/share/nfs/glite 192.168.0.0/16(rw,async,all_squash,anonuid=1000)
就是上面藍字的部分,改成那樣後再重新啟動 NFS server,問題就解決了.....

2007年6月12日 星期二

[BrazilFW] 解決使用 ARGENTO BRIDGE 後,防火牆規則消失的問題!

由於特殊需求,需要將 BrazilFW 設定為 Bridge Mode,因此網路上找了資料,發現可以使用「ARGENTO BRIDGE」擴充模組,因此就下載來用!

安裝方式在之前的文章就有提到了.....

只是裝了之後,出現奇怪的現象......原先設定好的防火牆規則通通消失了,內建 L7 Filter 的功能設定後也無效!

不過進入 Firewall 設定,選擇 Reload Firewall,所有的防火牆規則就通通回來了! 只是....為什麼呢?
查了一下,原來是在 Initial Script 中的「/argentobr/bridge」這一支 script 所造成的,他在後面把防火牆規則用 -F -X 參數把流量歸零並刪除規則了....... Orz

因此把這些部分通通註解掉就行了!

可是發現一個奇怪的狀況....全部註解掉之後,Web Administrator 卻不能連了.....@_@

後來發現解決方式,只要註解掉 mangle 的部分,就可以正常進入 Web Adinistrator 了!

至於會發生此種情形的原因,目前還不知道.....有查到再補好了....

2007年6月11日 星期一

[TCP/IP Illustrated] Link Layer

簡介

在之前提到,Link Layer的用途在於接收與傳送以下內容:
  1. 與 IP 之間的 IP datagrams
  2. 與 ARP 之間的 ARP request & reply
  3. 與 RARP 之間的 RARP request & reply
此外,TCP/IP 之所以會幾乎成為 Internet 的標準,一個極大的原因是 Link Layer 可以支援許多不同種類的網路硬體,例如:Ethernet、Token Ring、FDDI、RS-232....等等。

在此部分的重點內容大致分為 SLIP、PPP、MTU,以下就一個一個來仔細介紹。


Ethernet 與 IEEE 802 標準

Ethernet 原本是在 1982 年被提出,用於 10MByte/sec 的 LAN 上,所使用的存取方式稱為 CSMA/CD(Carrier Sense, Multiple Access with Collision Detection);幾年後,IEEE 提出的 802.3 協定,取代了原本的 Ethernet,另外更提出了許多其他一系列的 802.x 協定,定義了其他網路型態的標準,常見的如下:
因此 802.2 + 802.3 便完整的取代了原本的 Ethernet。

從上述的說明可以瞭解,在 IEEE802 標準裡面﹐將 Link Layer 劃分為兩層:Media Access ControlLogical Link Control,以下用張圖(資料來源:Study Area)來說明這些標準之間的關係:


由於 Ethernet 被後來 IEEE 所制訂出來的標準(802.2 + 802.3)所取代,其中兩者在資料的封裝(encapsulation)上有所差異,不過也是有其相容之處,因此以下用一張圖來說明:

  1. 在前兩個 6 bytes 長度的部分(destination addr 與 source addr),兩種封裝方式都是相同的,儲存的皆是 NIC 的 MAC address。
  2. 在第 7~8 個 byte 則有不同(長度為 2 bytes),說明如下:
    • IEEE 802
      欄位名稱為「length」,用來標示後續的資料內容到欄位 CRC 之前的資料長度。
    • Ethernet
      欄位名稱為「type」,標示後續的資料的種類為何。
  3. 第 15 個 byte 開始到 CRC 欄位前的資料,也是分別進行說明:
    • IEEE 802
      在 length 欄位之後,緊接著欄位分別如下:
      • DSAP(Destination Service Access Point):長度 1 byte,設定為 0xAA
      • SSAP(Source Service Access Point:長度 1 byte,設定為 0xAA
      • ctrl:長度 1 byte,設定為 0x03
      • org code:長度為 3 btyes,設定為 0x000000
      • type:與 Ethernet 中的 byte 欄位是相同作用
      • data:長度為 38~1492
    • Ethernet
      在 type 欄位之後,接著即是長度為 46~1500 bytes 的資料
  4. 最後長度 4 bytes 的 CRC 欄位
    作為對後續傳送過來的 frame 進行錯誤偵測之用

最後,還需要注意的是,兩種不同封裝方式的 frame,都要其最小長度的限制,其中 Ethernet 為 46 bytes,IEEE 802 為 38 bytes;若資料不足到這些 bytes 長度,則會自動補空白資料進來。

【備註】Ethernet 封裝的方式定義在 RFC 894;而 IEEE 802 封裝的方式則定義在 RFC 1042


SLIP (Serial Line IP)

此協定是用於使用 Serial Line(RS-232)作為網路傳輸媒介時使用,常見於一般的撥接 modem。

SLIP 的作用在於用較為簡單的方式將 IP datagram 封裝,並透過 Serial Line 傳輸,封裝的方式定義於 RFC 1055,以下用一張圖介紹封裝 SLIP 的過程:

  1. 每個 IP datagram 的最後加上一個 END 字元為 0xc0
  2. 若在 IP datagram 中有 END 字元(0xc0)存在,則會被替換為 SLIP ESC 字元(0xdb)加上 0xdc
  3. 若在 IP datagram 中有 SLIP ESC 字元(0xdb)存在,則會被替換為 SLIP ESC 字元(0xdb)加上 0xdd

當然,SLIP 也是有其缺點的,例如:
  1. 傳送資料的兩端必須知道對方的 IP address,因為在封裝中並不會加入這些資訊
  2. 沒有 type 欄位說明 frame 的型態為何,因此若透過 Serial Line 傳輸的資料,都會被認為使用 SLIP 協定,因此無法同時使用其他協定
  3. 沒有 checksum 的機制,因此必須仰賴上層進行錯誤檢查


一般來說,Serial Line 常被用來傳送小型的 TCP 封包,但假設資料很小,長度為 1 byte,為了成功傳送,卻必須多加入 20 bytes 的 IP header 以及 20 bytes 的 TCP header,如此一來就會產生 40 bytes 的 overhead,對網路傳輸來說的確是不怎麼有效率。

因此新的 SLIP 協定被提出,稱為 CSLIP(Compressed SLIP),可將長達 40 bytes 的 header 資訊縮短為 3 或 5 btyes,所使用的方式是同時維護多個 TCP 連線的狀態,並讓資料傳送兩端記住 header 不會變動的部分,僅針對會變動的部分進行傳輸,透過此方式,便大大降低了 overhead 的情形。

而 CSLIP 詳細的運作方式,都記錄在 RFC 1144 中。


PPP (Point-to-Point Protocol)

PPP,是一個點對點(Point-to-Point)的協定,通常用於兩端點間直接建立連結的情況(注意! 此協定還是用在 serial link 上),此協定改善了 SLIP 所有的缺點。而 PPP 包含了三個部分:
  1. 在 serial link 上封裝 IP datagram 的方式,並支援同步與非同步的連結
  2. LCP(Link Control Protocol),用來建立、設定、測試 Data-Link connection,允許兩端點間以不同的方式進行交談
  3. 多個 NCP(Network Control Protocol),用來處理來自上層(Network Layer)的不同協定,例如:IP、DECnet、AppleTalk....等等
其中 PPP 封裝的方式以及 LCP 的相關資料,可參考 RFC 1548;而定義處理 IP 的 NCP 可以參考 RFC 1332

接著要介紹 PPP frame 的格式,以下用一張圖來說明:

一開始為固定的 3 個 byte,分別是 flag(0x7E)、addr(0xFF)、control(0x03)。

接著是 protocol 欄位,類似 Ethernet 中的 type 欄位,用來表示下一個 information 欄位中的內容為何,有可能是 IP datagram(0x0021)、Link Control Data(0xC021)、Network Control Data(0x8021)。

再來是長度為 2 bytes 的 CRC 欄位,用來檢查錯誤之用。

而由於 PPP 使用 0x7e 作為 flag 欄位,因此在 information 欄位中若是有出現 0x7e,就必須進行額外的處理:
  1. 同步連結
    在同步連結中,所此用的方式稱為 bit stuffing
  2. 非同步連結
    • 在非同步連結中,使用 0x7d 作為跳脫字元(escape character),並將 0x7E 的第 6 個 bit 的 1 換為 0,因此 0x7E 會被轉換為 0x7d 0x5e。
    • 但若是資料是 0x7d 呢? 也是同樣方式,因此會變成 0x7d 0x5d。
    • 而若是 byte 資料小於 0x20(ASCII 控制字元),也會將其以跳脫的方式來處理,因此 0x01 會轉換為 0x7d 0x21。
而進行如此麻煩轉換的目的,則是為了怕兩端的任何裝置將傳輸過來的資料誤認為 ASCII 控制字元。

最後,要說明運作在 SLIP 之上的 PPP 協定,擁有以下幾個優點:
  1. 在單一 serial line 中支援多種協定,並非只有 IP datagram
  2. 多個 CRC 錯誤檢查
  3. 可使用 IP network control protocol 在兩端點間進行動態的交談
  4. 可對 TCP 與 IP header 進行壓縮
  5. Link Control Protocol 可允許兩端點間以不同的方式進行 Data-Link 層級的交談


MTU (Maximun Transmission Unit)

從上面封裝的圖可以看出,每一個封裝後的 frame,不論是在 Ethernet 或是在 IEEE 802 的環境中,都會有其大小的限制,在 data 的部分最大值分別是 1500 btyes(Ethernet) 以及 1492 bytes(IEEE 802),此即稱為 MTU(Maximun Transmission Unit),基本上,不同類型的網路有其不同的 MTU 值。

若 IP 有個大於 Link Layer MTU 值的 datagram 要傳送,就會進行 IP fragmentation,將 datagram 進行切割,讓資料不會超過 MTU 的大小;而各類型網路的 MTU 值可參考 RFC 1191,以下列一張簡圖:


另外還有一個必須提到的重點,稱為「Path MTU」。

假設在同一個網路下的兩端點要互傳資料,必須注意到 MTU 大小;但一般的情況下,兩端點往往不在同一個網路中,而必須經過許多不同的網路才能互傳資料,而這些路徑上所使用的網路又不太可能是完全與自己是相同的型態,因此 MTU 的設定必須要符合所有網路型態的要求才可正確傳送! 因此....MTU 必須修改成傳送路徑上所有不同網路中最小的 MTU 值,此即稱為 Path MTU。

一般來說,兩端點間的 MTU 並非完全不變的,因為一來一回的 routing 不見得是相同,因此可以適時的進行調整以取得最佳的傳送效果,而調整 Path MTU 的方式,稱為「Path MTU Discovery」,可以參考 RFC 1191 得到更多詳細資料。

2007年6月10日 星期日

基本 bash 程式設計 (4) - Command Substitution

到此為止,目前看到了兩種指定變數值的方式:
  1. script 中指定
  2. 以 position parameter 的方式指定

在這個部分中,要介紹更強大好用的變數指定方式,稱為 Command Substitution !

Command Substitution 的神奇之處在於「可以將各種命令的 standard output 作為變數的值」! 使用語法如下:
$(指令內容)
以下用一些範例來說明:
# 列出目前所在的目錄
echo $(pwd)

# 列出使用者家目錄的檔案
echo $(ls $HOME)

# 列出目前所在目錄下的檔案
echo $(ls $(pwd))

# 檔案中的斷行字元會被移除掉後印出其內容
echo $(< sort_data.txt)

接著再來個稍微複雜一些的,假設從主機中發信給目前登入主機的所有使用者,可以這樣做:
# 顯示目前所有登入 server 的使用者
who

# 取得所有使用者的清單
who | cut -d' ' -f1

# 寄信給他們囉!
mail $(who | cut -d' ' -f1)
最後,其他更加靈活的使用,就是在於管理者的創意與巧思了!

2007年6月6日 星期三

[BrazilFW] Bridge Mode 設定

找個 ARGENTO BRIDGE Add-On 真是找到快讓我吐血了....

原來他不是正式的 Add-On,雖然有看到文章說要去 Spanish Phorum 去找下載的位置,但......我看不懂西班牙文啊~~Orz

好不容易找到載點了,如下:

http://www.ladelbarrio.com.ar/nachazo/argentobr/

目前出到 beta8,搞不好以後會有更新的版本,所以就直接給 index 的網頁.....


此網頁的上一層還有 Qos 的 Add-On,下次有時間來試試看。

以下進入正題....... BrazilFW 設定為 Bridge Mode

步驟說明如下:

1、下載 ARGENTO BRIDGE Add-On


就用剛剛上面的網址進入下載即可,目前這個 Add-On 已經出到 beta8 了! 以下是安裝指令:(安裝後必須重新啟動)
shell> mount -t vfat /dev/hda1 /mnt
shell> cd /mnt
shell> wget http://www.ladelbarrio.com.ar/nachazo/argentobr/beta8/argenbr.tgz
shell> reboot


2、修改主設定檔

進入「Configuration Files -> 1. BrazilFW Main Configuration File」,進行以下修改:
  1. 將 Internet 的 IP(IPADDR) 與 Netmask(NETMASK) 設定修改成與 LAN(LOCAL_IPADDR、LOCAL_NETMASK) 相同
  2. 設定可以正常連外的 GATEWAY
  3. 設定可以正確提供名稱解析服務的 DNS Server(DNS1、DNS2)
  4. 將 Internet 與 LAN 的介面(IF_LOCAL、IF_INET)皆修改為 br0
  5. 在設定檔最後加入「DISABLE_NAT='YES'
最後別忘了儲存設定(Backup Now)囉!


3、修改啟動設定檔

進入「Configuration Files -> 2. Local Commands Init Script」,加入以下這行:
/argentobr/bridge
最後別忘了儲存設定(Backup Now)囉!


4、修改 Bridge 相關變數設定

這個部分在 Web Administrator 中並沒有地方可以修改,必須透過 SSH client 連到 server 上,修改檔案「/argentobr/variables.conf」,將「LOCAL_IPADDR」以及「gateway」兩個變數修改為第二個步驟所設定的內容。

最後,務必記得要儲存設定「w) Write configuration to disk囉!


通通設定完成後,重開機就可以了!

【注意】在實驗的時候失敗很多次,才發現都忘了儲存設定... Orz

2007年6月3日 星期日

基本 bash 程式設計 (3) - String Operators

由於 shell script 重點相當著重在字串的處理,當然在 Linux 中有提供許多指令可以來進行字串的處理;然後,透過一些小技巧,就可以免去使用 pipe 搭配其他指令的麻煩,以下就慢慢來介紹...

String Substitution

在宣告變數時,有可能會因為疏忽而打錯;或是在寫給使用者使用的 script,每次都要詳細指定參數的麻煩....

其實這些錯誤或麻煩都可以避免,只要稍微修改一下變數的使用方式(搭配符號「{ }」)即可,以下用一張圖表來說明:

Operator
作用
目的
範例
${varname:-word}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則回傳 word
用於設定變數預設值
${count:-0}
若變數 count 未定義,則回傳 0
${varname:=word}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則設定此變數的值為 word,再回傳變數的值 用於將未定義的變數設定預設值
${count:=0}
若變數 count 未定義,則設定 count 變數的值為 0 並回傳
${varname:?message}
假如 varname 變數存在,並且不是 null,則回傳變數內容;反之則顯示「varname: message」,並中斷目前的命令或 script。 用來針對未定義的變數進行 debug
${count:?"undefined!"}
若變數 count 未定義,則會顯示「count: undefined!」,並中斷程式執行
${varname:+word}
假如 varname 變數存在,並且不是 null,則回傳 word 用來測試變數是否存在
${count:+1}
若變數 count 存在,則回傳 1
${varname:offset:length}
回傳 varname 變數的值中,從第 offset 的字元開始(變數值的第一個位置為 0),長度為 length 的字串。
有以下幾種用法:
  1. 若 length 省略,則從 offset 位置取到最後
  2. 若 offset 小於 0,表示從後面的位置往回取
  3. 若 varname 為 @,則回傳第 offset 個參數開始算的第 length 個參數

取得變數所儲存字串的部分字串
count="frogfootman"
${count:4} => footman
${count:4:4} => foot

像是表格中的第一種用法,就相當適用於必須為變數指定預設值的情形下。

以下直接用個例子來說明使用方式,假設我們檔案「sort-data.txt」,內容如下:
8 eight
9 nine
1 one
2 two
3 three
7 seven
5 five
6 six
4 four
0 zero
接著要寫一支 script 來根據最前面的數字作排序,並顯示指定的行數:
#!/bin/bash
# 檔案名稱:highest.sh

# 檢查 file 名稱是否有輸入
fileName=${1:?"filename missing, please check your file."}

# 設定預設顯示的行數
# 若使用者未輸入第二個參數就預設為 5
displayLine=${2:-5}

# sort 是用來排序的程式
# 參數「n」指定以數字為依據作排序
# 參數「r」則是 reverse 的意思,由大排到小
# head 是用來取「前幾行」
sort -nr $fileName | head -$displayLine
執行結果如下:
# 沒指定檔名時
shell> sh highest.sh
highest.sh: line 5: 1: filename missing, please check your file.

# 未指定所要顯示的行數
shell> sh highest.sh sort-data.txt
9 nine
8 eight
7 seven
6 six
5 five

# 指定顯示行數
shell> sh highest.sh sort-data.txt 3
9 nine
8 eight
7 seven


Patterns & Pattern Matching

說到字串處理,怎麼可以缺乏正規表示式呢?

當然 bash 中提供的不是正規表示式,不過提供的也是類似的功能,以下用一個表格來介紹:

Operator
作用
${variable#pattern}
從字串開頭開始比對,當 pattern 符合變數中的值,即不需繼續比對,僅比對至最短的符合字串,刪除比對到的部分並回傳剩下的變數值
${variable##pattern} 同上,會回傳刪除比對後的變數值,但所採取的是貪婪比對,會一直比對到最長的部分為止
${variable%pattern} 從字串尾端開始比對,當 pattern 符合變數中的值,即不需繼續比對,僅比對至最短的符合字串,刪除比對到的部分並回傳剩下的變數值
${variable%%pattern}
同上,會回傳刪除比對後的變數值,但所採取的是貪婪比對,會一直比對到最長的部分為止
${variable/pattern/string}
差異處:
在 variable 與 pattern 比對後,在上面的 operator 中,只有第一個比對到的字串會被更換為 string;在下面的 operator 中則會取代所有比對到的字串。
相同處:
  1. 若 pattern 開始加上「#」,就會比對開頭
  2. 若 pattern 開始加上「%」,則會比對結尾
  3. 若 string 沒有指定,則比對符合的部分會刪除(其實就是取代為空字串啦!)
  4. 若 variable 為「@」或「*」,則會逐一比對所有 position parameter
${variable//pattern/string}

以下直接用範例來說明:
myPath="/home/cam/book/long.file.name"
# 將兩個「/」之間的內容都刪除(貪婪比對)
echo ${myPath##/*/} # long.file.name
# 效果同上(因為 * 也可以代表 /)
echo ${myPath##*/} # long.file.name
# 將兩個「/」之間的內容刪除
echo ${myPath#/*/} # cam/book/long.file.name
# 從尾端開始比對,將比對符合結果刪除
echo ${myPath%.*} # /home/cam/book/long.file
# 從尾端開始比對,將比對符合結果刪除(貪婪比對)
echo ${myPath%%.*} # /home/cam/book/long

myPhotoName="myphoto.png"
# 將副檔名 png 改為 jpg
echo ${myPhotoName%.png}.jpg # myphoto.jpg

myEnvPath="/home/user/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin"
# 將「:」全部換成斷行字元,結果如下:
# /home/user/bin
# /usr/local/bin
# /bin
# /usr/bin
# /usr/X11R6/bin
echo -e ${myEnvPath//:/'\n'}
繼續提到上面說的 pattern,有更進階的用法,可以進行多重比對,只要把每個 pattern 以符號「|」相隔即可,以下用表格來說明:
Operator
說明
範例
效果
*(PatternList)
符合 0 個到多個指定的 pattern 清單
*(alice|hatter|hare)
字串中出現 alice 或是 hatter 或是 hare,甚至完全沒有,都符合
+(PatternList) 符合 1 個到多個指定的 pattern 清單 +(alice|hatter|hare) 字串中出現 alice 或是 hatter 或是 hare 都符合
+[0-9]
僅數字符合
?(PatternList) 符合 0 個到 1 個指定的 pattern 清單 ?(alice|hatter|hare) 字串中僅出現一次 alice 或是 hatter 或是 hare,或是完全沒有,就符合
@(PatternList) 剛好完全符合指定 pattern 清單的其中 1 個
@(alice|hatter|hare) 僅有 alice、hatter、hare 這三個字串符合(精確比對)
!(PatternList) 完全不符合指定的 pattern 清單
!(alice|hatter|hare) 只要字串中沒有 alice、hatter、hare 即符合
!(vt+([0-9]))
只要字串中沒有「vt+數字」的部分即符合

至於如何使用,就要靠使用者的創意與巧思了!


Length Operator

最後,要來介紹如何取得變數值的長度,這很簡單,語法如下:
${#varname}
以下來個簡單的範例:
myFile="myfile.txt"
echo ${#myFile} # 輸出 10

2007年6月2日 星期六

基本 bash 程式設計 (2) - Variable

前言

Variable(變數),是每個程式語言中都不可或缺的重要角色之一,當然 shell script 也不例外!

不像其他程式語言,variable 擁有許多不同的型態,甚至可以是 object;在 shell script 中,variable 中儲存的內容幾乎是 character(字元)、string(字串)、number(數字)。

而關於 variable 的使用方式之前就有提過,因此這邊不再多提。


Positional Parameters

不知道是否有人曾經想過,如果執行 shell script 時想要加上多個參數,應該要怎麼下指令呢? 其實很簡單,跟執行一般程式沒兩樣,大致就是以下格式:
shell> ./script_name para1 para2 para3 ....... paraN
那在 script 中要如何使用這些參數呢? 答案就是「Positional Parameter」!

而 Positional Parameter 是以 0 1 2 3 4 .... 等數字來命名的,其中 0 是 script 名稱,而之後就是所接的參數,因此就上面的例子來說:
  • $0=script_name
  • $1=para1
  • $2=para2
  • $3=para3
  • .......(以此類推)
  • $N=paraN
由於這些參數的使用是類似其他程式語言中 index 的使用方式,才會被稱作是 Positional Parameter。

此外,當然還有一些額外且常用的 Positional Parameter,以下直接列出他們的效果:
  • $@="$1 $2 $3 ...... $N"
    為所有參數組合而成的字串,以 space 隔開,若 script 沒有參數就是空字串囉!
  • $#=N
    參數數量,若 script 沒有參數就是 0 了
  • $*="$0$IFS$1$IFS$2$IFS$3$IFS ....... $N"
    包含所有 Positional Parameter(包括 script name) 內容,且用 $IFS 所隔開的字串;一般來說 $IFS 有可能是 space、TAB、NEWLINE 三種其中一個!
    簡單來說,「$*」與「$@」的差別就是是否有包含 $0 的內容而已。
    【註】書上是這樣說,但在 Ubuntu 中實測並非如此,而是跟 $@ 是相同的

以下用一個簡單的例子來說明:

alice.sh 的內容:
#!/bin/bash
echo "alice: $@"
echo "$0 $1 $2 $3 $4"
echo "$# arguments"
echo "$*"
執行 alice.sh
shell> sh alice.sh in wonderland

# 以下為輸出結果:
alice: in wonderland
alice.sh in wonderland
2 arguments
in wonderland

另外,除了在執行程式時使用 Position Parameter 的功能外,也可以在 function 中使用喔! 以下舉個簡單例子:
#!/bin/bash

alice() {
echo "alice: $@"
echo "$0 $1 $2 $3 $4"
echo "$# arguments"
echo "$*"
}

alice in wonderland
此時直接執行程是不需帶參數:(結果跟上面是相同的喔!)
shell> sh alice.sh


Variable 有效範圍

在 variable 的有效範圍這個部分,shell script 與一般的程式語言不太一樣,在 function 中所定義的 variable 是 global 的! 而非 local 的喔!

這邊直接以範例來解釋! 請注意 $var1 囉!
#!/bin/bash

myFunc() {
echo "in function: $0 $1 $2"
var1="in function"
echo "var1: $var1"
}

var1="outside function"
echo "var1: $var1"
echo "$0: $1 $2"

myFunc funcArg1 funcArg2

echo "var1: $var1"
echo "$0: $1 $2"
接著執行程式囉!
shell> sh var_scope.sh arg1 arg2

# 以下為執行結果:
var1: outside function
var_scope.sh: arg1 arg2
in function: var_scope.sh funcArg1 funcArg2
var1: in function
var1: in function # 注意此行! $var1 的值被 function 改變囉!
var_scope.sh: arg1 arg2 # position parameter 不會被 function 影響到!


可能此時有人會問,script 要如何撰寫,才能讓 function 中的變數不會影響到 function 外面的變數呢? 其實很簡單,只要透過關鍵字「local」即可,如果將上面程式中的 function 改為以下內容:
myFunc() {
local var1 # 將變數加上了 local 關鍵字
echo "in function: $0 $1 $2"
var1="in function"
echo "var1: $var1"
}
出來的結果就會跟一般的程式語言相同的,結果如下:
shell> sh var_scope.sh arg1 arg2

# 以下為執行結果:
var1: outside function
var_scope.sh: arg1 arg2
in function: var_scope.sh funcArg1 funcArg2
var1: in function
var1: outside function # 果然 script 中的變數沒被 function 所影響到
var_scope.sh: arg1 arg2
以下用兩張圖來說明使用「local」關鍵字後,變數範圍的差異:
未用 local 變數
使用 local 變數後


最後,大家都知道要使用變數,必須在變數名稱前加上符號「$」,但假設有一個變數的名稱為 TEST,然後要在螢幕上輸出變數內容,在加一條底線,但如果使用以下方式:
echo $TEST_
這時系統會去判斷是否有名稱為「TEST_」的變數,結果當然是沒有,所以會輸出空白! 而為了得到正確的結果,可以在變數名稱旁邊加上左右大括號「{}」,所以只要改成以下語法就正常了:
echo ${TEST}_

Network Simulator

要架設網路環境來練習,一般人並沒有什麼機會可以接觸到真正不同型態的實體網路,頂多家裡接一接 Hub,了不起架台 Linux server 作 router.....

於是就有 Network Simulator(網路模擬器) 的產生,可以用來建置一個模擬的環境供練習,今天上 google 查了一下,似乎還蠻多的......以下有一則評比:
CISCO認證 --- 思科模擬器完全介紹

好像市面上的模擬器都是以 CISCO 的網路設備與環境為主......上面推薦 Boson 出的模擬器 NetSim

另外我還有在 freshmeat 發現這個稱為「Network Simulator」的模擬器....

等我玩過了,如果覺得就來繼續介紹。

這邊先作個筆記留下來.....