?? shell編程案例詳解.txt
字號:
第2章 帶著一個Sha-Bang出發(Sha-Bang指的是#!)
==============================================
在一個最簡單的例子中,一個shell腳本其實就是將一堆系統命令列在一個文件中.它的最基本的
用處就是,在你每次輸入這些特定順序的命令時可以少敲一些字.
Example 2-1 清除:清除/var/log下的log文件
################################Start Script#######################################
1 # Cleanup
2 # 當然要使用root身份來運行這個腳本
3
4 cd /var/log
5 cat /dev/null > messages
6 cat /dev/null > wtmp
7 echo "Logs cleaned up."
################################End Script#########################################
這根本就沒什么稀奇的, 只不過是命令的堆積, 來讓從console或者xterm中一個一個的輸入命
令更方便一些.好處就是把所有命令都放在一個腳本中,不用每次都敲它們.這樣的話,對于特定
的應用來說,這個腳本就很容易被修改或定制.
Example 2-2 清除:一個改良的清除腳本
################################Start Script#######################################
1 #!/bin/bash
2 # 一個Bash腳本的正確的開頭部分.
3
4 # Cleanup, 版本 2
5
6 # 當然要使用root身份來運行.
7 # 在此處插入代碼,來打印錯誤消息,并且在不是root身份的時候退出.
8
9 LOG_DIR=/var/log
10 # 如果使用變量,當然比把代碼寫死的好.
11 cd $LOG_DIR
12
13 cat /dev/null > messages
14 cat /dev/null > wtmp
15
16
17 echo "Logs cleaned up."
18
19 exit # 這個命令是一種正確并且合適的退出腳本的方法.
################################End Script#########################################
現在,讓我們看一下一個真正意義的腳本.而且我們可以走得更遠...
Example 2-3. cleanup:一個增強的和廣義的刪除logfile的腳本
################################Start Script#######################################
1 #!/bin/bash
2 # 清除, 版本 3
3
4 # Warning:
5 # -------
6 # 這個腳本有好多特征,這些特征是在后邊章節進行解釋的,大概是進行到本書的一半的
7 # 時候,
8 # 你就會覺得它沒有什么神秘的了.
9 #
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0 # $UID為0的時候,用戶才具有根用戶的權限
15 LINES=50 # 默認的保存行數
16 E_XCD=66 # 不能修改目錄?
17 E_NOTROOT=67 # 非根用戶將以error退出
18
19
20 # 當然要使用根用戶來運行
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23 echo "Must be root to run this script."
24 exit $E_NOTROOT
25 fi
26
27 if [ -n "$1" ]
28 # 測試是否有命令行參數(非空).
29 then
30 lines=$1
31 else
32 lines=$LINES # 默認,如果不在命令行中指定
33 fi
34
35
36 # Stephane Chazelas 建議使用下邊
37 #+ 的更好方法來檢測命令行參數.
38 #+ 但對于這章來說還是有點超前.
39 #
40 # E_WRONGARGS=65 # 非數值參數(錯誤的參數格式)
41 #
42 # case "$1" in
43 # "" ) lines=50;;
44 # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 # * ) lines=$1;;
46 # esac
47 #
48 #* 直到"Loops"的章節才會對上邊的內容進行詳細的描述.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ] # 或者 if[ "$PWD" != "$LOG_DIR" ]
54 # 不在 /var/log中?
55 then
56 echo "Can't change to $LOG_DIR."
57 exit $E_XCD
58 fi # 在處理log file之前,再確認一遍當前目錄是否正確.
59
60 # 更有效率的做法是
61 #
62 # cd /var/log || {
63 # echo "Cannot change to necessary directory." >&2
64 # exit $E_XCD;
65 # }
66
67
68
69
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages # 變為新的log目錄.
72
73
74 # cat /dev/null > messages
75 #* 不再需要了,使用上邊的方法更安全.
76
77 cat /dev/null > wtmp # ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79
80 exit 0
81 # 退出之前返回0,返回0表示成功.
82 #
################################End Script#########################################
因為你可能希望將系統log全部消滅,這個版本留下了log消息最后的部分.你將不斷地找到新
的方法來完善這個腳本,并提高效率.
要注意,在每個腳本的開頭都使用"#!",這意味著告訴你的系統這個文件的執行需要指定一個解
釋器.#!實際上是一個2字節[1]的魔法數字,這是指定一個文件類型的特殊標記, 換句話說, 在
這種情況下,指的就是一個可執行的腳本(鍵入man magic來獲得關于這個迷人話題的更多詳細
信息).在#!之后接著是一個路徑名.這個路徑名指定了一個解釋腳本中命令的程序,這個程序可
以是shell,程序語言或者是任意一個通用程序.這個指定的程序從頭開始解釋并且執行腳本中
的命令(從#!行下邊的一行開始),忽略注釋.[2]
如:
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/perl
4 #!/usr/bin/tcl
5 #!/bin/sed -f
6 #!/usr/awk -f
上邊每一個腳本頭的行都指定了一個不同的命令解釋器,如果是/bin/sh,那么就是默認shell
(在Linux系統中默認是Bash).[3]使用#!/bin/sh,在大多數商業發行的UNIX上,默認是Bourne
shell,這將讓你的腳本可以正常的運行在非Linux機器上,雖然這將會犧牲Bash一些獨特的特征.
腳本將與POSIX[4] 的sh標準相一致.
注意: #! 后邊給出的路徑名必須是正確的,否則將會出現一個錯誤消息,通常是
"Command not found",這將是你運行這個腳本時所得到的唯一結果.
當然"#!"也可以被忽略,不過這樣你的腳本文件就只能是一些命令的集合,不能夠使用shell內建
的指令了,如果不能使用變量的話,當然這也就失去了腳本編程的意義了.
注意:這個例子鼓勵你使用模塊化的方式來編寫腳本,平時也要注意收集一些零碎的代碼,
這些零碎的代碼可能用在你將來編寫的腳本中.這樣你就可以通過這些代碼片段來構
造一個較大的工程用例. 以下邊腳本作為序,來測試腳本被調用的參數是否正確.
################################Start Script#######################################
1 E_WRONG_ARGS=65
2 script_parameters="-a -h -m -z"
3 # -a = all, -h = help, 等等.
4
5 if [ $# -ne $Number_of_expected_args ]
6 then
7 echo "Usage: `basename $0` $script_parameters"
8 # `basename $0`是這個腳本的文件名
9 exit $E_WRONG_ARGS
10 fi
################################End Script#########################################
大多數情況下,你需要編寫一個腳本來執行一個特定的任務,在本章中第一個腳本就是一個這樣
的例子, 然后你會修改它來完成一個不同的,但比較相似的任務.用變量來代替寫死的常量,就是
一個好方法,將重復的代碼放到一個函數中,也是一種好習慣.
2.1 調用一個腳本
----------------
編寫完腳本之后,你可以使用sh scriptname,[5]或者bash scriptname來調用它.
(不推薦使用sh <scriptname,因為這禁用了腳本從stdin中讀數據的功能.)
更方便的方法是讓腳本本身就具有可執行權限,通過chmod命令可以修改.
比如:
chmod 555 scriptname (允許任何人都具有 可讀和執行權限) [6]
或:
chmod +rx scriptname (允許任何人都具有 可讀和執行權限)
chmod u+rx scriptname (只給腳本的所有者 可讀和執行權限)
既然腳本已經具有了可執行權限,現在你可以使用./scriptname.[7]來測試它了.如果這個腳本
以一個"#!"行開頭,那么腳本將會調用合適的命令解釋器來運行.
最后一步,在腳本被測試和debug之后,你可能想把它移動到/usr/local/bin(當然是以root身份)
,來讓你的腳本對所有用戶都有用.這樣用戶就可以直接敲腳本名字來運行了.
注意事項:
[1] 那些具有UNIX味道的腳本(基于4.2BSD)需要一個4字節的魔法數字,在#!后邊需要一個
空格#! /bin/sh.
[2] 腳本中的#!行的最重要的任務就是命令解釋器(sh或者bash).因為這行是以#開始的,
當命令解釋器執行這個腳本的時候,會把它作為一個注釋行.當然,在這之前,這行語句
已經完成了它的任務,就是調用命令解釋器.
如果在腳本的里邊還有一個#!行,那么bash將把它認為是一個一般的注釋行.
1 #!/bin/bash
2
3 echo "Part 1 of script."
4 a=1
5
6 #!/bin/bash
7 # 這將不會開始一個新腳本.
8
9 echo "Part 2 of script."
10 echo $a # Value of $a stays at 1.
[3] 這里可以玩一些小技巧.
1 #!/bin/rm
2 # 自刪除腳本.
3
4 # 當你運行這個腳本時,基本上什么都不會發生...除非這個文件消失不見.
5
6 WHATEVER=65
7
8 echo "This line will never print (betcha!)."
9
10 exit $WHATEVER # 沒關系,腳本是不會在這退出的.
當然,你還可以試試在一個README文件的開頭加上#!/bin/more,并讓它具有執行權限.
結果將是文檔自動列出自己的內容.(一個使用cat命令的here document可能是一個
更好的選則,--見Example 17-3).
[4] 可移植的操作系統接口,標準化類UNIX操作系統的一種嘗試.POSIX規范可以在
http://www.opengroup.org/onlinepubs/007904975/toc.htm中查閱.
[5] 小心:使用sh scriptname來調用腳本的時候將會關閉一些Bash特定的擴展,腳本可能
因此而調用失敗.
[6] 腳本需要讀和執行權限,因為shell需要讀這個腳本.
[7] 為什么不直接使用scriptname來調用腳本?如果你當前的目錄下($PWD)正好有你想要
執行的腳本,為什么它運行不了呢?失敗的原因是,出于安全考慮,當前目錄并沒有被
加在用戶的$PATH變量中.因此,在當前目錄下調用腳本必須使用./scriptname這種
形式.
2.2 初步的練習
--------------
1. 系統管理員經常會為了自動化一些常用的任務而編寫腳本.舉出幾個這種有用的腳本的實例.
2. 編寫一個腳本,顯示時間和日期,列出所有的登錄用戶,顯示系統的更新時間.然后這個腳本
將會把這些內容保存到一個log file中.
第二部分 基本
++++++++++++++++
第3章 特殊字符
================
# 注釋,行首以#開頭為注釋(#!是個例外).
1 # This line is a comment.
注釋也可以存在于本行命令的后邊.
1 echo "A comment will follow." # 注釋在這里
2 # ^ 注意#前邊的空白
注釋也可以在本行空白的后邊.
1 # A tab precedes this comment.
注意:命令是不能跟在同一行上注釋的后邊的,沒有辦法,在同一行上,注釋的后邊想
要再使用命令,只能另起一行.
當然,在echo命令中被轉義的#是不能作為注釋的.
同樣的,#也可以出現在特定的參數替換結構中或者是數字常量表達式中.
1 echo "The # here does not begin a comment."
2 echo 'The # here does not begin a comment.'
3 echo The \# here does not begin a comment.
4 echo The # 這里開始一個注釋
5
6 echo ${PATH#*:} # 參數替換,不是一個注釋
7 echo $(( 2#101011 )) # 數制轉換,不是一個注釋
8
9 # Thanks, S.C.
標準的引用和轉義字符("'\)可以用來轉義#
; 命令分隔符,可以用來在一行中來寫多個命令.
1 echo hello; echo there
2
3
4 if [ -x "$filename" ]; then # 注意:"if"和"then"需要分隔
5 # 為啥?
6 echo "File $filename exists."; cp $filename $filename.bak
7 else
8 echo "File $filename not found."; touch $filename
9 fi; echo "File test complete."
有時候需要轉義
;; 終止"case"選項.
1 case "$variable" in
2 abc) echo "\$variable = abc" ;;
3 xyz) echo "\$variable = xyz" ;;
4 esac
. .命令等價于source命令(見Example 11-20).這是一個bash的內建命令.
. .作為文件名的一部分.如果作為文件名的前綴的話,那么這個文件將成為隱藏文件.
將不被ls命令列出.
bash$ touch .hidden-file
bash$ ls -l
total 10
-rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook
bash$ ls -al
total 14
drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook
-rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file
.命令如果作為目錄名的一部分的話,那么.表達的是當前目錄.".."表示上一級目錄.
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
.命令經常作為一個文件移動命令的目的地.
bash$ cp /home/bozo/current_work/junk/* .
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -