【例4-1】體驗shell對文件名通配符的展開處理。
(1) 設當前目錄下只有try.c, zap.c, arc.c三文件,在shell提示符下,鍵入命令
cat *.c
那麼,shell根據當前目錄下的所有文件的文件名集合,將*.c擴展為arc.c try.c zap.c,擴展後的多個文件名按照字典序排列。這樣,cat *.c被加工成了cat arc.c try.c zap.c,實際執行加工之後的命令。就是說,鍵入命令cat *.c與手工鍵入命令cat arc.c try.c zap.c的效果是完全等價的。從cat命令的角度來說,都是指定了三個文件作為處理對象,cat程序在執行的時候,已經看不到*.c,它看到的是三個文件名。
(2) 設當前目錄下有0131.rpt, 0130.rpt,wang.mail, lee.mail四個文件。在兩個mail文件中查找數字串的命令為:
grep '[0-9][0-9]*' *.mail
這裡,[0-9][0-9]*的兩側,應當用單引號括起來。用單引號括起來的部分,shell就不再進行文件名展開。如果漏掉單引號,錯誤的使用了下面的命令:
grep [0-9][0-9]* *.mail
那麼,經過shell替換之後,命令變成了:
grep 0130.rpt 0131.rpt lee.mail wang.mail
鍵入前邊的命令,與手工鍵入上述命令效果是完全相同的。這樣,grep並不了解發生的這些替換,按照grep命令的語法格式,grep在執行的時候,就會在0131.rpt,lee.mail,wang.mail三個文件中搜尋與正則表達式0130.rpt匹配的字符串。這樣的結果,不符合操作員的初衷,系統的行為顯得有些怪異。因此,輸入命令中必需的單引號是必不可少的。關於單引號等元字符的介紹,在“B-shell及編程”一章6.5.9節中詳細介紹。
(3) 鑒於shell對文件名通配符的處理方式,文件名通配符可以使用在任何命令中,如:vi m*e 替換成 vi makefile,而用戶的輸入cd *work.d 替換成 cd configure_network.d。cd是改變當前工作目錄的命令,要求後面只能有一個參數,如果文件名展開後cd有了兩個參數,cd命令就會失敗。只要所選擇的文件名通配符只能匹配一個名字,cd就可以正確工作。使用文件名通配符,可以簡化一些命令的輸入,尤其是那些較長的名字。
【例4-2】從程序員的角度理解shell對通配符的處理。
編寫一個很簡短的C語言程序,站在與cat命令同等的位置,觀察一下shell對文件名通配符的處理。如果將這個程序進一步擴展,增加相應的處理,就可以實現諸如cat,grep等命令。
$ cat arg.c
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
printf("%d: [%s]\n", i, argv[i]);
}
$ cc arg.c -o arg
$ ./arg abc ABCDEF
0: [./arg]
1: [abc]
2: [ABCDEF]
$ ./arg *.[ch]
0: [./arg]
1: [arg.c]
2: [auth.c]
3: [ccp.c]
4: [chap.c]
……
$ ./arg '*.[ch]'
0: [./arg]
1: [*.[ch]]
將執行結果與同樣的arg.c在DOS下執行結果相比較,在DOS系統中,.\arg *.c的執行時,不會進行文件名展開,執行結果為:
0: [.\arg]
1: [*.c]
【例4-3】cp命令和DOS的COPY命令的區別。
設文件目錄結構圖4-3所示,
圖4-3文件和目錄結構舉例,展示cp命令的處理過程
在DOS中,如果希望將backup.d下的兩個文件p1.c和p2.c復制到當前目錄,使用命令COPY P?.C。但是,在UNIX中執行cp backup.d/p?c命令,經過shell完成文件名生成之後,實際執行:
cp backup.d/pl.c backup.d/p2.c
執行結果是p1.c和p2.c沒有拷貝到當前目錄,而是文件p1.c覆蓋掉文件p2.c。
錯誤的用法cp backup.d/p?.c會帶來不期望的後果。如果運氣好的話,backup.d目錄下還有p3.c,展開後cp有三個參數,既不符合格式1,也不符合格式2,命令無效,拒絕作任何操作。
將上述兩文件拷貝到當前目錄的方法,使用cp命令的格式2,最後一個參數圓點是當前目錄:
cp backup.d/p?.c .
再如: cp /usr/include/*.h .
【例4-4】rm命令選項的功能。
(1) rm -r backap.d
刪除當前目錄下的整個子目錄backup.d。
(2) rm -rf xx*
清除所有以xx打頭的文件。程序員應當為一段時間內臨時使用的一些文件的取名符合某一特定規律,例如名字的前兩個字母為x,以便於清理臨時文件。
(3) rm -i *.test
有選擇地刪除若干文件。
(4) rm * .bak
誤操作, 星號之後多出了一個空格,那麼,經shell文件名展開之後,rm會忠實的刪除所有文件,並且可能會通知你,企圖刪除的文件.bak不存在。
【例4-30】SUID權限的使用。
設用戶liu記錄了在本UNIX系統中的某些用戶的月工資清單,記錄在文件list.txt中,假定文件list.txt的內容如下:
$ cat list.txt
#===========================================================
# 登錄名 工作證號 姓名 月份 工資 獎金 補助 扣除 總額
#-----------------------------------------------------------
tian 2076 田曉星 03 1782 1500 200 175 3307
liang 2074 梁振宇 03 1560 1400 180 90 3050
sun 3087 孫東旭 03 1804 1218 106 213 2915
tian 2076 田曉星 04 1832 1450 230 245 3267
liang 2074 梁振宇 04 1660 1450 230 70 3270
sun 3087 孫東旭 04 1700 1310 283 270 3023
#===========================================================
# 注:煤氣費不再從工資中扣除,由煤氣公司自行收繳。
對於這一文件,希望能夠限制某一用戶,如:用戶liang,只能訪問文件中的部分內容,而不是文件的全部內容。只允許用戶liang讀取行首字符為#的行和與用戶liang有關的行,與其它用戶有關的行對用戶liang保密。這樣,使用前面提到的文件權限設置方式,用戶liang要麼擁有對整個文件list.txt的讀權限,要麼不許他讀取該文件中的任何內容。為了達到前述的保密要求,就不得不將該文件拆成若干個文件,每個用戶擁有自己的文件,並且還要設置權限以防其他用戶讀取。這對於用戶liu來說,很不方便,對這麼多的文件進行修改和維護非常困難。需要有一種機制限制用戶liang只能訪問用戶liu的文件list.txt中的一部分內容,UNIX提供了這樣一種機制。
先看下面用戶liu編寫的C源程序query.c。
$ awk '{printf("%2d %s\n",NR,$0)}' query.c
1 #include <stdio.h>
2 #include <string.h>
3 void main(void)
4 {
5 FILE *f;
6 char line[512], login_name[16];
7 f = fopen("list.txt", "r");
8 if (f == NULL) {
9 perror("*** ERROR: Open file \"list.txt\" ");
10 exit(1);
11 }
12 while (fgets(line, sizeof(line), f)) {
13 if (line[0] == '#') {
14 printf("%s", line);
15 } else {
16 if (sscanf(line, "%s", login_name) > 0) {
17 if (strcmp(login_name, getlogin()) == 0)
18 printf("%s", line);
19 }
20 }
21 }
22 exit(0);
23 }
$ cc query.c -o query
$
下面的內容是用戶liu執行的操作。
$ chmod 600 list.txt
$ chmod 711 query
$ ls -l list.txt query
-rw------- 1 liu leader 722 Dec 10 23:04 list.txt
-rwx--x--x 1 liu leader 56134 Dec 10 23:07 query
$
這樣,用戶liang執行命令query,結果如下。用戶liang也無法用cat列出文件list.txt的內容。下面的內容是用戶liang執行的操作。
$ query
*** ERROR: Open file "list.txt" : Permission denied
$ cat list.txt
cannot open list.txt: Permission denied
$
可見,用戶liang在執行用戶liu的程序query時,由於用戶liang沒有對用戶liu的文件list.txt的讀權限,在源程序的第7行為讀而打開文件時失敗,執行第9-10行,程序退出。
在UNIX中文件query的文件主用戶liu可以給文件query增加SUID權限(設置用戶標識)。用戶liu的操作過程如下:
$ chmod u+s query
$ ls -l query
-rws--x--x 1 liu leader 56134 Dec 10 23:07 query
$
增加了SUID權限以後,ls -l命令顯示的文件主對該文件的操作權限為rws,在執行權限處顯示字母s而不是字母x。
文件query的SUID權限,由它的i節點中的一個比特來記錄。在執行chmod u+s命令給一個文件施加SUID權限時,該文件對文件主必須有可執行權限。
這樣,用戶liang就可以通過query命令查詢只許liu可讀的文件list.txt,盡管用戶liang沒有對文件list.txt的讀權限。用戶liang的操作過程如下:
$ ls -l list.txt query
-rw------- 1 liu leader 722 Dec 10 23:04 list.txt
-rws--x--x 1 liu leader 56134 Dec 10 23:07 query
$ query
#========================================================
# 登錄名 工作證號 姓名 月份 工資 獎金 補助 扣除 總額
#--------------------------------------------------------
liang 2074 梁振宇 03 1560 1400 180 90 3050
liang 2074 梁振宇 04 1660 1450 230 70 3270
#=======================================================
# 注:煤氣費不再從工資中扣除,由煤氣公司自行收繳
$ cat list.txt
cannot open list.txt: Permission denied
$
用戶liang仍無法用cat命令列出文件list.txt的全部內容。文件主liu對可執行文件query授予SUID權限意味著,無論哪個用戶只要擁有對文件query的可執行權,query程序執行時,使用文件主liu的權限來訪問文件。這樣,用戶liang雖然無法通過其它程序如cat命令讀取文件list.txt的內容,但是可以通過query來查詢文件list.txt的內容,他可以看到的內容受限於可執行文件query內部的程序設計。