Shell 文件操作

2018-10-26 16:47 更新

前言

這周來(lái)探討文件操作。

在日常學(xué)習(xí)和工作中,總是在不斷地和各種文件打交道,這些文件包括普通文本文件,可以執(zhí)行的程序,帶有控制字符的文檔、存放各種文件的目錄、網(wǎng)絡(luò)套接字文件、設(shè)備文件等。這些文件又具有諸如屬主、大小、創(chuàng)建和修改日期等各種屬性。文件對(duì)應(yīng)文件系統(tǒng)的一些數(shù)據(jù)塊,對(duì)應(yīng)磁盤(pán)等存儲(chǔ)設(shè)備的一片連續(xù)空間,對(duì)應(yīng)于顯示設(shè)備卻是一些具有不同形狀的字符集。

在這一節(jié),為了把關(guān)注點(diǎn)定位在文件本身,不會(huì)深入探討文件系統(tǒng)以及存儲(chǔ)設(shè)備是如何組織文件的(在后續(xù)章節(jié)再深入探討),而是探討對(duì)它最熟悉的一面,即把文件當(dāng)成是一系列的字符(一個(gè) byte)集合看待。因此之前介紹的《 Shell 編程范例之字符串操作》在這里將會(huì)得到廣泛的應(yīng)用,關(guān)于普通文件的讀寫(xiě)操作已經(jīng)非常熟練,那就是“重定向”,這里會(huì)把這部分獨(dú)立出來(lái)介紹。關(guān)于文件在 Linux 下的“數(shù)字化”(文件描述符)高度抽象,“一切皆為文件”的哲學(xué)在 Shell 編程里也得到了深刻的體現(xiàn)。

下面先來(lái)介紹文件的各種屬性,然后介紹普通文件的一般操作。

文件的各種屬性

首先通過(guò)文件的結(jié)構(gòu)體來(lái)看看文件到底有哪些屬性:

struct stat {
    dev_t st_dev; /* 設(shè)備   */
    ino_t st_ino; /* 節(jié)點(diǎn)   */
    mode_t st_mode; /* 模式   */
    nlink_t st_nlink; /* 硬連接 */
    uid_t st_uid; /* 用戶ID */
    gid_t st_gid; /* 組ID   */
    dev_t st_rdev; /* 設(shè)備類型 */
    off_t st_off; /* 文件字節(jié)數(shù) */
    unsigned long  st_blksize; /* 塊大小 */
    unsigned long st_blocks; /* 塊數(shù)   */
    time_t st_atime; /* 最后一次訪問(wèn)時(shí)間 */
    time_t st_mtime; /* 最后一次修改時(shí)間 */
    time_t st_ctime; /* 最后一次改變時(shí)間(指屬性) */
};

下面逐次來(lái)了解這些屬性,如果需要查看某個(gè)文件屬性,用 stat 命令就可,它會(huì)按照上面的結(jié)構(gòu)體把信息列出來(lái)。另外,ls 命令在跟上一定參數(shù)后也可以顯示文件的相關(guān)屬性,比如 -l 參數(shù)。

文件類型

文件類型對(duì)應(yīng)于上面的 st_mode, 文件類型有很多,比如常規(guī)文件、符號(hào)鏈接(硬鏈接、軟鏈接)、管道文件、設(shè)備文件(符號(hào)設(shè)備、塊設(shè)備)、socket文件等,不同的文件類型對(duì)應(yīng)不同的功能和作用。

范例:在命令行簡(jiǎn)單地區(qū)分各類文件

$ ls -l
total 12
drwxr-xr-x 2 root root 4096 2007-12-07 20:08 directory_file
prw-r--r-- 1 root root    0 2007-12-07 20:18 fifo_pipe
brw-r--r-- 1 root root 3, 1 2007-12-07 21:44 hda1_block_dev_file
crw-r--r-- 1 root root 1, 3 2007-12-07 21:43 null_char_dev_file
-rw-r--r-- 2 root root  506 2007-12-07 21:55 regular_file
-rw-r--r-- 2 root root  506 2007-12-07 21:55 regular_file_hard_link
lrwxrwxrwx 1 root root   12 2007-12-07 20:15 regular_file_soft_link -> regular_file
$ stat directory_file/
  File: `directory_file/'
  Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 301h/769d       Inode: 521521      Links: 2
Access: (0755/drwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2007-12-07 20:08:18.000000000 +0800
Modify: 2007-12-07 20:08:18.000000000 +0800
Change: 2007-12-07 20:08:18.000000000 +0800
$ stat null_char_dev_file
  File: `null_char_dev_file'
  Size: 0               Blocks: 0          IO Block: 4096   character special file
Device: 301h/769d       Inode: 521240      Links: 1     Device type: 1,3
Access: (0644/crw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2007-12-07 21:43:38.000000000 +0800
Modify: 2007-12-07 21:43:38.000000000 +0800
Change: 2007-12-07 21:43:38.000000000 +0800

說(shuō)明:通過(guò) ls 命令結(jié)果每行的第一個(gè)字符可以看到,它們之間都不相同,這正好反應(yīng)了不同文件的類型。 d 表示目錄,- 表示普通文件(或者硬鏈接),l 表示符號(hào)鏈接,p 表示管道文件,bc 分別表示塊設(shè)備和字符設(shè)備(另外 s 表示 socket 文件)。在 stat 命令的結(jié)果中,可以在第二行的最后找到說(shuō)明,從上面的操作可以看出,directory_file 是目錄,stat 命令的結(jié)果中用 directory 表示,而 null_char_dev_file 它則用 character special file 說(shuō)明。

范例:簡(jiǎn)單比較它們的異同

通常只會(huì)用到目錄、普通文件、以及符號(hào)鏈接,很少碰到其他類型的文件,不過(guò)這些文件還是各有用處的,如果要做嵌入式開(kāi)發(fā)或者進(jìn)程通信等,可能會(huì)涉及到設(shè)備文件、有名管道(FIFO)。下面通過(guò)簡(jiǎn)單的操作來(lái)反應(yīng)它們之間的區(qū)別(具體原理會(huì)在下一節(jié)《Shell 編程范例之文件系統(tǒng)》介紹,如果感興趣,也可以提前到網(wǎng)上找找設(shè)備文件的作用、塊設(shè)備和字符設(shè)備的區(qū)別、以及驅(qū)動(dòng)程序中如何編寫(xiě)相關(guān)設(shè)備驅(qū)動(dòng)等)。

對(duì)于普通文件:就是一系列字符的集合,所以可以讀、寫(xiě)等

$ echo "hello, world" > regular_file
$ cat regular_file
hello, world

在目錄中可以創(chuàng)建新文件,所以目錄還有叫法:文件夾,到后面會(huì)分析目錄文件的結(jié)構(gòu)體,它實(shí)際上存放了它下面的各個(gè)文件的文件名。

$ cd directory_file
$ touch file1 file2 file3

對(duì)于有名管道,操作起來(lái)比較有意思:如果要讀它,除非有內(nèi)容,否則阻塞;如果要寫(xiě)它,除非有人來(lái)讀,否則阻塞。它常用于進(jìn)程通信中??梢源蜷_(kāi)兩個(gè)終端 terminal1terminal2,試試看:

terminal1$ cat fifo_pipe #剛開(kāi)始阻塞在這里,直到下面的寫(xiě)動(dòng)作發(fā)生,才打印test字符串
terminal2$ echo "test" > fifo_pipe

關(guān)于塊設(shè)備,字符設(shè)備,設(shè)備文件對(duì)應(yīng)于 /dev/hda1/dev/null,如果用過(guò) U 盤(pán),或者是寫(xiě)過(guò)簡(jiǎn)單的腳本的話,這樣的用法應(yīng)該用過(guò)::-)

$ mount hda1_block_dev_file /mnt #掛載硬盤(pán)的第一個(gè)分區(qū)到/mnt下(關(guān)于掛載的原理,在下一節(jié)討論)
$ echo "fewfewfef" > /dev/null   #/dev/null像個(gè)黑洞,什么東西丟進(jìn)去都消失殆盡

最后兩個(gè)文件分別是 regular_file 文件的硬鏈接和軟鏈接,去讀寫(xiě)它們,他們的內(nèi)容是相同的,不過(guò)去刪除它們,他們卻互不相干,硬鏈接和軟鏈接又有何不同呢?前者可以說(shuō)就是原文件,后者呢只是有那么一個(gè) inode,但沒(méi)有實(shí)際的存儲(chǔ)空間,建議用 stat 命令查看它們之間的區(qū)別,包括它們的 Blocks,inode 等值,也可以考慮用 diff 比較它們的大小。

$ ls regular_file*
ls regular_file* -l
-rw-r--r-- 2 root root 204800 2007-12-07 22:30 regular_file
-rw-r--r-- 2 root root 204800 2007-12-07 22:30 regular_file_hard_link
lrwxrwxrwx 1 root root     12 2007-12-07 20:15 regular_file_soft_link -> regular_file
$ rm regular_file      # 刪除原文件
$ cat regular_file_hard_link   # 硬鏈接還在,而且里頭的內(nèi)容還有呢
fefe
$ cat regular_file_soft_link
cat: regular_file_soft_link: No such file or directory

雖然軟鏈接文件本身還在,不過(guò)因?yàn)樗旧聿淮鎯?chǔ)內(nèi)容,所以讀不到東西,這就是軟鏈接和硬鏈接的區(qū)別。

需要注意的是,硬鏈接不可以跨文件系統(tǒng),而軟鏈接則可以。另外,也不允許給目錄創(chuàng)建硬鏈接。

范例:普通文件再分類

文件類型從 Linux 文件系統(tǒng)那么一個(gè)級(jí)別分了以上那么多類型,不過(guò)普通文件還是可以再分的(根據(jù)文件內(nèi)容的”數(shù)據(jù)結(jié)構(gòu)“分),比如常見(jiàn)的文本文件,可執(zhí)行的 ELF 文件,odt 文檔,jpg 圖片格式,swap 分區(qū)文件,pdf 文件。除了文本文件外,它們大多是二進(jìn)制文件,有特定的結(jié)構(gòu),因此需要有專門(mén)的工具來(lái)創(chuàng)建和編輯它們。關(guān)于各類文件的格式,可以參考相關(guān)文檔標(biāo)準(zhǔn)。不過(guò)非常值得深入了解 Linux 下可執(zhí)行的 ELF 文件的工作原理,如果有興趣,建議閱讀一下參考資料中和 ELF 文件相關(guān)部分,這一部分對(duì)于嵌入式 Linux 工程師至關(guān)重要。

雖然各類普通文件都有專屬的操作工具,但是還是可以直接讀、寫(xiě)它們,這里先提到這么幾個(gè)工具,回頭討論細(xì)節(jié)。

  • od :以八進(jìn)制或者其他格式“導(dǎo)出”文件內(nèi)容。
  • strings :讀出文件中的字符(可打印的字符)
  • gcc,gdb,readelf,objdump等:ELF文件分析、處理工具(gcc編譯器、gdb調(diào)試器、readelf分析 ELF 文件,objdump` 反編譯工具)

再補(bǔ)充一個(gè)非常重要的命令,file,這個(gè)命令用來(lái)查看各類文件的屬性。和 stat 命令相比,它可以進(jìn)一步識(shí)別普通文件,即 stat 命令顯示的 regular file 。因?yàn)?regular file 可以有各種不同的結(jié)構(gòu),因此在操作系統(tǒng)的支持下得到不同的解釋,執(zhí)行不同的動(dòng)作。雖然,Linux 下,文件也會(huì)加上特定的后綴以便用戶能夠方便地識(shí)別文件的類型,但是 Linux 操作系統(tǒng)根據(jù)文件頭識(shí)別各類文件,而不是文件后綴,這樣在解釋相應(yīng)的文件時(shí)就更不容易出錯(cuò)。下面簡(jiǎn)單介紹 file 命令的用法。

$ file ./
./: directory
$ file /etc/profile
/etc/profile: ASCII English text
$ file /lib/libc-2.5.so
/lib/libc-2.5.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
$ file /bin/test
/bin/test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
$ file /dev/hda
/dev/hda: block special (3/0)
$ file /dev/console
/dev/console: character special (5/1)
$ cp /etc/profile .
$ tar zcf profile.tar.gz profile
$ file profile.tar.gz
profile.tar.gz: gzip compressed data, from Unix, last modified: Tue Jan  4 18:53:53 2000
$ mkfifo fifo_test
$ file fifo_test
fifo_test: fifo (named pipe)

更多用法見(jiàn) file 命令的手冊(cè),關(guān)于 file 命令的實(shí)現(xiàn)原理,請(qǐng)參考 magic 的手冊(cè)(看看 /etc/file/magic 文件,了解什么是文件的 magic number 等)。

文件屬主

Linux 作為一個(gè)多用戶系統(tǒng),為多用戶使用同一個(gè)系統(tǒng)提供了極大的方便,比如對(duì)于系統(tǒng)上的文件,它通過(guò)屬主來(lái)區(qū)分不同的用戶,以便分配它們對(duì)不同文件的操作權(quán)限。為了更方便地管理,文件屬主包括該文件所屬用戶,以及該文件所屬的用戶組,因?yàn)橛脩艨梢詫儆诙鄠€(gè)組。先來(lái)簡(jiǎn)單介紹 Linux 下用戶和組的管理。

Linux 下提供了一組命令用于管理用戶和組,比如用于創(chuàng)建用戶的 useraddgroupadd,用于刪除用戶的 userdelgroupdel,另外,passwd 命令用于修改用戶密碼。當(dāng)然,Linux 還提供了兩個(gè)相應(yīng)的配置,即 /etc/passwd/etc/group,另外,有些系統(tǒng)還把密碼單獨(dú)放到了配置文件 /etc/shadow 中。關(guān)于它們的詳細(xì)用法請(qǐng)參考后面的資料,這里不再介紹,僅介紹文件和用戶之間的一些關(guān)系。

范例:修改文件的屬主

$ chown 用戶名:組名 文件名

如果要遞歸地修改某個(gè)目錄下所有文件的屬主,可以添加 -R 選項(xiàng)。

從本節(jié)開(kāi)頭列出的文件結(jié)構(gòu)體中,可以看到僅僅有用戶 ID 和組 ID 的信息,但 ls -l 的結(jié)果卻顯示了用戶名和組名信息,這個(gè)是怎么實(shí)現(xiàn)的呢?下面先看看 -n 的結(jié)果:

范例:查看文件的屬主

$ ls -n regular_file
-rw-r--r-- 1 0 0 115 2007-12-07 23:45 regular_file
$ ls -l regular_file
-rw-r--r-- 1 root root 115 2007-12-07 23:45 regular_file

范例:分析文件屬主實(shí)現(xiàn)的背后原理

可以看到,ls -n 顯示了用戶 ID 和組 ID,而 ls -l 顯示了它們的名字。還記得上面提到的兩個(gè)配置文件 /etc/passwd/etc/group 文件么?它們分別存放了用戶 ID 和用戶名,組 ID 和組名的對(duì)應(yīng)關(guān)系,因此很容易想到 ls -l 命令在實(shí)現(xiàn)時(shí)是如何通過(guò)文件結(jié)構(gòu)體的 ID 信息找到它們對(duì)應(yīng)的名字信息的。如果想對(duì) ls -l 命令的實(shí)現(xiàn)有更進(jìn)一步的了解,可以用 strace 跟蹤看看它是否讀取了這兩個(gè)配置文件。

$ strace -f -o strace.log ls -l regular_file
$ cat strace.log | egrep "passwd|group|shadow"
2989  open("/etc/passwd", O_RDONLY)     = 3
2989  open("/etc/group", O_RDONLY)      = 3

說(shuō)明: strace 可以用來(lái)跟蹤系統(tǒng)調(diào)用和信號(hào)。如同 gdb 等其他強(qiáng)大的工具一樣,它基于系統(tǒng)的 ptrace 系統(tǒng)調(diào)用實(shí)現(xiàn)。

實(shí)際上,把屬主和權(quán)限分開(kāi)介紹不太好,因?yàn)橹挥兴鼈儍烧呓Y(jié)合才使得多用戶系統(tǒng)成為可能,否則無(wú)法隔離不同用戶對(duì)某個(gè)文件的操作,所以下面來(lái)介紹文件操作權(quán)限。

文件權(quán)限

ls -l 命令的結(jié)果的第一列的后 9 個(gè)字符中,可以看到類似這樣的信息 rwxr-xr-x,它們對(duì)應(yīng)于文件結(jié)構(gòu)體的 st_mode 部分(st_mode 包含文件類型信息和文件權(quán)限信息兩部分)。這類信息可以分成三部分,即 rwxr-xr-x,分別對(duì)應(yīng)該文件所屬用戶、所屬組、其他組對(duì)該文件的操作權(quán)限,如果有 rwx 中任何一個(gè)表示可讀、可寫(xiě)、可執(zhí)行,如果為 - 表示沒(méi)有這個(gè)權(quán)限。對(duì)應(yīng)地,可以用八進(jìn)制來(lái)表示它,比如 rwxr-xr-x 就可表示成二進(jìn)制 111101101,對(duì)應(yīng)的八進(jìn)制則為 755 。正因?yàn)槿绱?,要修改文件的操作?quán)限,也可以有多種方式來(lái)實(shí)現(xiàn),它們都可通過(guò) chmod 命令來(lái)修改。

范例:給文件添加讀、寫(xiě)、可執(zhí)行權(quán)限

比如,把 regular_file 的文件權(quán)限修改為所有用戶都可讀、可寫(xiě)、可執(zhí)行,即 rwxrwxrwx,也可表示為 111111111,翻譯成八進(jìn)制,則為 777 。這樣就可以通過(guò)兩種方式修改這個(gè)權(quán)限。

$ chmod a+rwx regular_file

$ chmod 777 regular_file

說(shuō)明: a 指所用用戶,如果只想給用戶本身可讀可寫(xiě)可執(zhí)行權(quán)限,那么可以把 a 換成 u ;而 + 就是添加權(quán)限,相反的,如果想去掉某個(gè)權(quán)限,用 -,而 rwx 則對(duì)應(yīng)可讀、可寫(xiě)、可執(zhí)行。更多用法見(jiàn) chmod 命令的幫助。

實(shí)際上除了這些權(quán)限外,還有兩個(gè)涉及到安全方面的權(quán)限,即 setuid/setgid 和只讀控制等。

如果設(shè)置了文件(程序或者命令)的 setuid/setgid 權(quán)限,那么用戶將可用 root 身份去執(zhí)行該文件,因此,這將可能帶來(lái)安全隱患;如果設(shè)置了文件的只讀權(quán)限,那么用戶將僅僅對(duì)該文件將有可讀權(quán)限,這為避免諸如 rm -rf 的“可惡”操作帶來(lái)一定的庇佑。

范例:授權(quán)普通用戶執(zhí)行root所屬命令

默認(rèn)情況下,系統(tǒng)是不允許普通用戶執(zhí)行 passwd 命令的,通過(guò) setuid/setgid,可以授權(quán)普通用戶執(zhí)行它。

$ ls -l /usr/bin/passwd
-rwx--x--x 1 root root 36092 2007-06-19 14:59 /usr/bin/passwd
$ su      #切換到root用戶,給程序或者命令添加“粘著位”
$ chmod +s /usr/bin/passwd
$ ls -l /usr/bin/passwd
-rws--s--x 1 root root 36092 2007-06-19 14:59 /usr/bin/passwd
$ exit
$ passwd #普通用戶通過(guò)執(zhí)行該命令,修改自己的密碼

說(shuō)明:

setuidsetgid 位是讓普通用戶可以以 root 用戶的角色運(yùn)行只有 root 帳號(hào)才能運(yùn)行的程序或命令。

雖然這在一定程度上為管理提供了方便,比如上面的操作讓普通用戶可以修改自己的帳號(hào),而不是要 root 帳號(hào)去為每個(gè)用戶做這些工作。關(guān)于 setuid/setgid 的更多詳細(xì)解釋,請(qǐng)參考最后推薦的資料。

范例:給重要文件加鎖

只讀權(quán)限示例:給重要文件加鎖(添加不可修改位 [immutable])),以避免各種誤操作帶來(lái)的災(zāi)難性后果(例如 :``rm -rf

$ chattr +i regular_file
$ lsattr regular_file
----i-------- regular_file
$ rm regular_file    #加immutable位后就無(wú)法對(duì)文件進(jìn)行任何“破壞性”的活動(dòng)啦
rm: remove write-protected regular file `regular_file'? y
rm: cannot remove `regular_file': Operation not permitted
$ chattr -i regular_file #如果想對(duì)它進(jìn)行常規(guī)操作,那么可以把這個(gè)位去掉
$ rm regular_file

說(shuō)明: chattr 可以用于設(shè)置文件的特殊權(quán)限,更多用法請(qǐng)參考 chattr 的幫助。

文件大小

文件大小對(duì)于普通文件而言就是文件內(nèi)容的大小,而目錄作為一個(gè)特殊的文件,它存放的內(nèi)容是以目錄結(jié)構(gòu)體組織的各類文件信息,所以目錄的大小一般都是固定的,它存放的文件個(gè)數(shù)自然也就有上限,即它的大小除以文件名的長(zhǎng)度。設(shè)備文件的“文件大小”則對(duì)應(yīng)設(shè)備的主、次設(shè)備號(hào),而有名管道文件因?yàn)樘厥獾淖x寫(xiě)性質(zhì),所以大小常是 0 。硬鏈接(目錄文件不能創(chuàng)建硬鏈接)實(shí)質(zhì)上是原文件的一個(gè)完整的拷貝,因此,它的大小就是原文件的大小。而軟鏈接只是一個(gè) inode,存放了一個(gè)指向原文件的指針,因此它的大小僅僅是原文件名的字節(jié)數(shù)。下面我們通過(guò)演示增加記憶。

范例:查看普通文件和鏈接文件

原文件,鏈接文件文件大小的示例:

$ echo -n "abcde" > regular_file   #往regular_file寫(xiě)入5字節(jié)
$ ls -l regular_file*
-rw-r--r-- 2 root root  5 2007-12-08 15:28 regular_file
-rw-r--r-- 2 root root  5 2007-12-08 15:28 regular_file_hard_file
lrwxrwxrwx 1 root root 12 2007-12-07 20:15 regular_file_soft_link -> regular_file
lrwxrwxrwx 1 root root 22 2007-12-08 15:21 regular_file_soft_link_link -> regular_file_soft_link
$ i="regular_file"
$ j="regular_file_soft_link"
$ echo ${#i} ${#j}   #軟鏈接存放的剛好是它們指向的原文件的文件名的字節(jié)數(shù)
12 22

范例:查看設(shè)備文件

設(shè)備號(hào)對(duì)應(yīng)的文件大?。褐鳌⒋卧O(shè)備號(hào)

$ ls -l hda1_block_dev_file
brw-r--r-- 1 root root 3, 1 2007-12-07 21:44 hda1_block_dev_file
$ ls -l null_char_dev_file
crw-r--r-- 1 root root 1, 3 2007-12-07 21:43 null_char_dev_file

補(bǔ)充:主 (major)、次(minor)設(shè)備號(hào)的作用有不同。當(dāng)一個(gè)設(shè)備文件被打開(kāi)時(shí),內(nèi)核會(huì)根據(jù)主設(shè)備號(hào)(major number)去查找在內(nèi)核中已經(jīng)以主設(shè)備號(hào)注冊(cè)的驅(qū)動(dòng)(可以 cat /proc/devices 查看已經(jīng)注冊(cè)的驅(qū)動(dòng)號(hào)和主設(shè)備號(hào)的對(duì)應(yīng)情況),而次設(shè)備號(hào)(minor number)則是通過(guò)內(nèi)核傳遞給了驅(qū)動(dòng)本身(參考《The Linux Primer》第十章)。因此,對(duì)于內(nèi)核而言,通過(guò)主設(shè)備號(hào)就可以找到對(duì)應(yīng)的驅(qū)動(dòng)去識(shí)別某個(gè)設(shè)備,而對(duì)于驅(qū)動(dòng)而言,為了能夠更復(fù)雜地訪問(wèn)設(shè)備,比如訪問(wèn)設(shè)備的不同部分(如硬件通過(guò)分區(qū)分成不同部分,而出現(xiàn) hda1,hda2,hda3 等),比如產(chǎn)生不同要求的隨機(jī)數(shù)(如 /dev/random/dev/urandom 等)。

范例:查看目錄

目錄文件的大小,為什么是這樣呢?看看下面的目錄結(jié)構(gòu)體的大小,目錄文件的 Block 中存放了該目錄下所有文件名的入口。

$ ls -ld directory_file/
drwxr-xr-x 2 root root 4096 2007-12-07 23:14 directory_file/

目錄的結(jié)構(gòu)體如下:

struct dirent {
    long d_ino;
    off_t d_off;
    unsigned short d_reclen;
    char d_name[NAME_MAX+1]; /* 文件名稱 */
}

文件訪問(wèn)、更新、修改時(shí)間

文件的時(shí)間屬性可以記錄用戶對(duì)文件的操作信息,在系統(tǒng)管理、判斷文件版本信息等情況下將為管理員提供參考。因此,在閱讀文件時(shí),建議用 cat 等閱讀工具,不要用編輯工具 vim 去閱讀,因?yàn)榧词箾](méi)有做任何修改操作,一旦執(zhí)行了保存命令,將修改文件的時(shí)間戳信息。

文件名

文件名并沒(méi)有存放在文件結(jié)構(gòu)體內(nèi),而是存放在它所在的目錄結(jié)構(gòu)體中。所以,在目錄的同一級(jí)別中,文件名必須是唯一的。

文件的基本操作

對(duì)于文件,常見(jiàn)的操作包括創(chuàng)建、刪除、修改、讀、寫(xiě)等。關(guān)于各種操作對(duì)應(yīng)的“背后動(dòng)作”將在下一章《Shell編程范例之文件系統(tǒng)操作》詳細(xì)分析。

范例:創(chuàng)建文件

socket 文件是一類特殊的文件,可以通過(guò) C 語(yǔ)言創(chuàng)建,這里不做介紹(暫時(shí)不知道是否可以用命令直接創(chuàng)建),其他文件將通過(guò)命令創(chuàng)建。

$ touch regular_file      #創(chuàng)建普通文件
$ mkdir directory_file     #創(chuàng)建目錄文件,目錄文件里頭可以包含更多文件
$ ln regular_file regular_file_hard_link  #硬鏈接,是原文件的一個(gè)完整拷比
$ ln -s regular_file regular_file_soft_link  #類似一個(gè)文件指針,指向原文件
$ mkfifo fifo_pipe   #或者通過(guò) "mknod fifo_pipe p" 來(lái)創(chuàng)建,F(xiàn)IFO滿足先進(jìn)先出的特點(diǎn)
$ mknod hda1_block_dev_file b 3 1  #塊設(shè)備
$ mknod null_char_dev_file c 1 3   #字符設(shè)備

創(chuàng)建一個(gè)文件實(shí)際上是在文件系統(tǒng)中添加了一個(gè)節(jié)點(diǎn)(inode),該節(jié)點(diǎn)信息將保存到文件系統(tǒng)的節(jié)點(diǎn)表中。更形象地說(shuō),就是在一顆樹(shù)上長(zhǎng)了一顆新的葉子(文件)或者枝條(目錄文件,上面還可以長(zhǎng)葉子的那種),這些可以通過(guò)tree命令或者ls` 命令形象地呈現(xiàn)出來(lái)。文件系統(tǒng)從日常使用的角度,完全可以當(dāng)成一顆倒立的樹(shù)來(lái)看,因?yàn)樗鼈兲窳?,太容易記憶啦?/p>

$ tree 當(dāng)前目錄

或者

$ ls 當(dāng)前目錄

范例:刪除文件

刪除文件最直接的印象是這個(gè)文件再也不存在了,這同樣可以通過(guò) ls 或者 tree 命令呈現(xiàn)出來(lái),就像樹(shù)木被砍掉一個(gè)分支或者摘掉一片葉子一樣。實(shí)際上,這些文件刪除之后,并不是立即消失了,而是僅僅做了刪除標(biāo)記,因此,如果刪除之后,沒(méi)有相關(guān)的磁盤(pán)寫(xiě)操作把相應(yīng)的磁盤(pán)空間“覆蓋”,那么原理上是可以恢復(fù)的(雖然如此,但是這樣的工作往往很麻煩,所以在刪除一些重要數(shù)據(jù)時(shí),請(qǐng)務(wù)必三思而后行,比如做好備份工作),相應(yīng)的做法可以參考后續(xù)資料。

具體刪除文件的命令有 rm,如果要?jiǎng)h除空目錄,可以用 rmdir 命令。例如:

$ rm regular_file
$ rmdir directory_file
$ rm -r directory_file_not_empty

rm 有兩個(gè)非常重要的參數(shù),一個(gè)是 -f,這個(gè)命令是非?!耙靶U的”,它估計(jì)給很多 Linux user 帶來(lái)了痛苦,另外一個(gè)是 -i,這個(gè)命令是非?!皽厝岬摹保烙?jì)讓很多用戶感覺(jué)煩躁不已。用哪個(gè)還是根據(jù)您的“心情”吧,如果做好了充分的備份工作,或者采取了一些有效避免災(zāi)難性后果的動(dòng)作的話,您在做這些工作的時(shí)候就可以放心一些啦。

范例:復(fù)制文件

文件的復(fù)制通常是指文件內(nèi)容的“臨時(shí)”復(fù)制。通過(guò)這一節(jié)開(kāi)頭的介紹,我們應(yīng)該了解到,文件的硬鏈接和軟鏈接在某種意義上說(shuō)也是“文件的復(fù)制”,前者同步復(fù)制文件內(nèi)容,后者在讀寫(xiě)的情況下同步“復(fù)制”文件內(nèi)容。例如:

cp 命令常規(guī)地復(fù)制文件(復(fù)制目錄需要 -r 選項(xiàng))

$ cp regular_file regular_file_copy
$ cp -r diretory_file directory_file_copy

創(chuàng)建硬鏈接(linkcopy 不同之處是后者是同步更新,前者則不然,復(fù)制之后兩者不再相關(guān))

$ ln regular_file regular_file_hard_link

創(chuàng)建軟鏈接

$ ln -s regular_file regluar_file_soft_link

范例:修改文件名

修改文件名實(shí)際上僅僅修改了文件名標(biāo)識(shí)符。可以通過(guò) mv 命令來(lái)實(shí)現(xiàn)修改文件名操作(即重命名)。

$ mv regular_file regular_file_new_name

范例:編輯文件

編輯文件實(shí)際上是操作文件的內(nèi)容,對(duì)應(yīng)普通文本文件的編輯,這里主要涉及到文件內(nèi)容的讀、寫(xiě)、追加、刪除等。這些工作通常會(huì)通過(guò)專門(mén)的編輯器來(lái)做,這類編輯器有命令行下的 vim 、 emacs 和圖形界面下的 gedit,kedit 等。如果是一些特定的文件,會(huì)有專門(mén)的編輯和處理工具,比如圖像處理軟件 gimp,文檔編輯軟件 OpenOffice 等。這些工具一般都會(huì)有專門(mén)的教程。

下面主要簡(jiǎn)單介紹 Linux 下通過(guò)重定向來(lái)實(shí)現(xiàn)文件的這些常規(guī)的編輯操作。

創(chuàng)建一個(gè)文件并寫(xiě)入 abcde

$ echo "abcde" > new_regular_file

再往上面的文件中追加一行 abcde

$ echo "abcde" >> new_regular_file

按行讀一個(gè)文件

$ while read LINE; do echo $LINE; done < test.sh

提示:如果要把包含重定向的字符串變量當(dāng)作命令來(lái)執(zhí)行,請(qǐng)使用 eval 命令,否則無(wú)法解釋重定向。例如,

$ redirect="echo \"abcde\" >test_redirect_file"
$ $redirect   #這里會(huì)把>當(dāng)作字符 > 打印出來(lái),而不會(huì)當(dāng)作 重定向 解釋
"abcde" >test_redirect_file
$ eval $redirect    #這樣才會(huì)把 > 解釋成 重定向
$ cat test_redirect_file
abcde

范例:壓縮/解壓縮文件

壓縮和解壓縮文件在一定意義上來(lái)說(shuō)是為了方便文件內(nèi)容的傳輸,不過(guò)也可能有一些特定的用途,比如內(nèi)核和文件系統(tǒng)的映像文件等(更多相關(guān)的知識(shí)請(qǐng)參考后續(xù)資料)。

這里僅介紹幾種常見(jiàn)的壓縮和解壓縮方法:

tar

$ tar -cf file.tar file   #壓縮
$ tar -xf file.tar    #解壓

gz

$ gzip  -9 file
$ gunzip file

tar.gz

$ tar -zcf file.tar.gz file
$ tar -zxf file.tar.gz

bz2

$ bzip2 file
$ bunzip2 file

tar.bz2

$ tar -jcf file.tar.bz2 file
$ tar -jxf file.tar.bz2

通過(guò)上面的演示,應(yīng)該已經(jīng)非常清楚 tarbzip2,bunzip2,gzip,gunzip命令的角色了吧?如果還不清楚,多操作和比較一些上面的命令,并查看它們的手冊(cè):man tar`...

范例:文件搜索(文件定位)

文件搜索是指在某個(gè)目錄層次中找出具有某些屬性的文件在文件系統(tǒng)中的位置,這個(gè)位置如果擴(kuò)展到整個(gè)網(wǎng)絡(luò),那么可以表示為一個(gè) URL 地址,對(duì)于本地的地址,可以表示為 file://+ 本地路徑。本地路徑在 Linux 系統(tǒng)下是以 / 開(kāi)頭,例如,每個(gè)用戶的家目錄可以表示為: file:///home/ 。下面僅僅介紹本地文件搜索的一些辦法。

find 命令提供了一種“及時(shí)的”搜索辦法,它根據(jù)用戶的請(qǐng)求,在指定的目錄層次中遍歷所有文件直到找到需要的文件為止。而 updatedb+locate 提供了一種“快速的”的搜索策略,updatedb 更新并產(chǎn)生一個(gè)本地文件數(shù)據(jù)庫(kù),而 locate 通過(guò)文件名檢索這個(gè)數(shù)據(jù)庫(kù)以便快速找到相應(yīng)的文件。前者支持通過(guò)各種文件屬性進(jìn)行搜索,并且提供了一個(gè)接口(-exec 選項(xiàng))用于處理搜索后的文件。因此為“單條命令”腳本的愛(ài)好者提供了極大的方便,不過(guò)對(duì)于根據(jù)文件名的搜索而言,updatedb+locate 的方式在搜索效率上會(huì)有明顯提高。下面簡(jiǎn)單介紹這兩種方法:

find 命令基本使用演示

$ find ./ -name "*.c" -o -name "*.h"  #找出所有的C語(yǔ)言文件,-o是或者
$ find ./ \( -name "*.c" -o -name "*.h" \) -exec mv '{}' ./c_files/ \;
# 把找到的文件移到c_files下,這種用法非常有趣

上面的用法可以用 xargs 命令替代

$ find ./ -name "*.c" -o -name "*.h" | xargs -i mv '{}' ./c_files/
# 如果要對(duì)文件做更復(fù)雜的操作,可以考慮把mv改寫(xiě)為你自己的處理命令,例如,我需要修

改所有的文件名后綴為大寫(xiě)。

$ find ./ -name "*.c" -o -name "*.h" | xargs -i ./toupper.sh '{}' ./c_files/

toupper.sh 就是我們需要實(shí)現(xiàn)的轉(zhuǎn)換小寫(xiě)為大寫(xiě)的一個(gè)處理文件,具體實(shí)現(xiàn)如下:

$ cat toupper.sh
#!/bin/bash

# the {} will be expended to the current line and becomen the first argument of this script
FROM=$1
BASENAME=${FROM##*/}

BASE=${BASENAME%.*}
SUFFIX=${BASENAME##*.}

TOSUFFIX="$(echo $SUFFIX | tr '[a-z]' '[A-Z]')"
TO=$2/$BASE.$TOSUFFIX
COM="mv $FROM $TO"
echo $COM
eval $COM

updatedb+locate 基本使用演示

$ updatedb #更新庫(kù)
$ locate find*.gz #查找包含find字符串的所有g(shù)z壓縮包

實(shí)際上,除了上面兩種命令外,Linux 下還有命令查找工具:whichwhereis,前者用于返回某個(gè)命令的全路徑,而后者用于返回某個(gè)命令、源文件、man 文件的路徑。例如,查找find` 命令的絕對(duì)路徑:

$ which find
/usr/bin/find
$ whereis find
find: /usr/bin/find /usr/X11R6/bin/find /usr/bin/X11/find /usr/X11/bin/find /usr/man/man1/find.1.gz /usr/share/man/man1/find.1.gz /usr/X11/man/man1/find.1.gz

需要提到的是,如果想根據(jù)文件的內(nèi)容搜索文件,那么 findupdatedb+locate 以及 whichwhereis 都無(wú)能為力啦,可選的方法是 grep,sed 等命令,前者在加上 -r 參數(shù)以后可以在指定目錄下文件中搜索指定的文件內(nèi)容,后者再使用 -i 參數(shù)后,可以對(duì)文件內(nèi)容進(jìn)行替換。它們的基本用法在前面的章節(jié)中已經(jīng)詳細(xì)介紹了,這里就不再贅述。

值得強(qiáng)調(diào)的是,這些命令對(duì)文件的操作非常有意義。它們?cè)谀硞€(gè)程度上把文件系統(tǒng)結(jié)構(gòu)給抽象了,使得對(duì)整個(gè)文件系統(tǒng)的操作簡(jiǎn)化為對(duì)單個(gè)文件的操作,而單個(gè)文件如果僅僅考慮文本部分,那么最終卻轉(zhuǎn)化成了之前的字符串操作,即上一節(jié)討論過(guò)的內(nèi)容。為了更清楚地了解文件的組織結(jié)構(gòu),文件之間的關(guān)系,在下一節(jié)將深入探討文件系統(tǒng)。

參考資料

后記

  • 考慮到文件和文件系統(tǒng)的重要性,將把它分成三個(gè)小節(jié)來(lái)介紹:文件、文件系統(tǒng)、程序與進(jìn)程。在“文件”這一部分,主要介紹文件的基本屬性和常規(guī)操作,在“文件系統(tǒng)”那部分,將深入探討 Linux 文件系統(tǒng)的各個(gè)部分(包括 Linux 文件系統(tǒng)的結(jié)構(gòu)、具體某個(gè)文件系統(tǒng)的大體結(jié)構(gòu)分析、底層驅(qū)動(dòng)的工作原理),在“程序與進(jìn)程”一節(jié)將專門(mén)討論可執(zhí)行文件的相關(guān)內(nèi)容(包括不同的程序類型、加載執(zhí)行過(guò)程、不同進(jìn)程之間的交互[命令管道和無(wú)名管道、信號(hào)通信]、對(duì)進(jìn)程的控制等)
  • 有必要討論清楚 目錄大小 的含義,另外,最好把一些常規(guī)的文件操作全部考慮到,包括文件的讀、寫(xiě)、執(zhí)行、刪除、修改、復(fù)制、壓縮/解壓縮等
  • 下午剛從上?;貋?lái),比賽結(jié)果很“糟糕”,不過(guò)到現(xiàn)在已經(jīng)不重要了,關(guān)鍵是通過(guò)決賽發(fā)現(xiàn)了很多不足,發(fā)現(xiàn)了設(shè)計(jì)在系統(tǒng)開(kāi)發(fā)中的關(guān)鍵角色,并且發(fā)現(xiàn)了上海是個(gè)美麗的城市,上交也是個(gè)美麗的大學(xué)?;貋?lái)就開(kāi)始整理這個(gè)因?yàn)楸荣惵湎铝藘芍艿?Blog
  • 12月15日,添加文件搜索部分內(nèi)容
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)