1. 程式人生 > 實用技巧 >Linux基礎 檔案和目錄

Linux基礎 檔案和目錄

Linux基礎 檔案和目錄

檔案和目錄

前言

本章討論檔案屬性和檔案系統內容。除了上一章討論的普通檔案,Linux的檔案概念還包括:目錄、裝置等。在Linux系統中,檔案的種類包括:普通檔案、目錄、符號連結、塊裝置、字元裝置、管道、套接字。

本章討論的主要內容為普通檔案、目錄和符號連結。它們的公共特點是,真實的儲存在了硬碟中,而其它型別的檔案是核心產生的檔案,在硬碟中並不存在。

檔案屬性

通過stat函式或者stat命令可以獲得檔案屬性。

檔案屬性解釋
dev_t st_dev 裝置號
ino_t st_ino inode編號
mode_t st_mode 訪問許可權相關
nlink_t st_nlink 硬連結數量
uid_t st_uid 擁有該檔案的使用者
gid_t st_gid 擁有該檔案的組
dev_t st_rdev 裝置號
off_t st_size 檔案尺寸
blksize_t st_blksize 檔案系統的IO尺寸
blkcnt_t st_blocks 佔用的block數量,一個block為512位元組
time_t st_atime 最後訪問時間
time_t st_mtime 最後修改時間
time_t st_ctime 最後檔案狀態修改時間
#include <sys/types.h>
#include <sys/stat.h>
#include 
<unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { struct stat buf; int ret = stat(".", &buf); if(ret < 0) { perror("stat"); return 0; }
if(S_ISREG(buf.st_mode)) { printf("this is regular file\n"); } else if(S_ISDIR(buf.st_mode)) { printf("this is dir\n"); } // 測試擁有該檔案的賬戶是否有讀許可權 if(S_IRUSR & buf.st_mode) { printf("user can read\n"); } open("a.txt", O_CREAT|O_RDWR, 0777); // access("./a.out", R_OK|W_OK|X_OK); printf("file size is %d\n", (int)buf.st_size); getchar(); return 0; }

3.3 檔案型別

在前言中,提到檔案型別包括七種,在stat結構題中,儲存了檔案的檔案型別屬性,它的檔案型別屬性儲存在st_mode中。但是七種型別,只需要3位即可,而st_mode是一個整數,因此它還儲存其它內容,如果需要判斷一個檔案屬於何種型別,需要一些巨集的幫助。


intmain() { structstatbuf; stat("a.txt",&stat); if(S_ISREG(buf.st_mode)) { printf("%s\n","這是普通檔案"); } }

檔案型別屬性是隻讀的屬性,無法修改。

3.4 使用者和組

Linux是一個多使用者作業系統,因此每個檔案都有屬性,記錄著這個檔案屬於哪個使用者/組。
使用者/組資訊可以被修改,可以通過chown來修改檔案所屬的使用者和組資訊。
修改檔案所屬使用者和組,需要root許可權。
新檔案所屬使用者和組,是建立該檔案的程序所屬使用者和組。

  • 實際賬戶和有效賬戶

賬戶解釋
實際賬戶 登陸系統時的賬戶
有效賬戶 決定程序的訪問資源的賬戶

3.5 檔案訪問許可權

檔案使用了9個位來表示訪問許可權,和檔案型別一起,儲存在st_mode中。此9位分成3組,每組3個位,分別表示讀/寫/執行許可權,而三個組分別表示擁有該檔案的賬戶,擁有該檔案的組,其它使用者組的許可權。如果對應位是1,表示有許可權,如果是0表示沒有許可權。

檔案訪問許可權經常用8進位制來表示,比如上表的許可權位可以表示為0755,意思是擁有它的賬戶對這個檔案有讀/寫/執行許可權,而擁有它的組有讀/執行許可權,其它賬戶對它有讀/執行許可權。

Linux提供一些巨集,來測試檔案的許可權位

  • 可以通過access函式來測試程式是否有訪問某檔案的許可權。

  • 建立檔案時,可以指定檔案的訪問許可權位,但是會收到umask位影響。

  • 可以通過chmod來修改檔案的許可權位

3.6 其它許可權位

3.6.1 SUID

只能對檔案設定,如果檔案設定了該位,那麼該檔案被執行時,對應的程序的許可權,不是執行該程式賬戶的許可權,而是擁有該使用者的許可權。

在對檔案未設定SUID的情況下:

如果對檔案設定了SUID,那麼:

可以通過chmod u+s或者chmod u-s來設定獲取去除SUID。
設定SUID可以讓一個普通賬戶擁有它不該有的許可權,容易產生安全漏洞。

3.6.2 SGID

可以對檔案和目錄設定,如果對檔案設定,那麼它的作用類似SUID,不過影響的是組。

如果對目錄設定,那麼拷貝到該目錄下的檔案都會被置位,除非拷貝是帶-p引數。 在Ubuntu下測試並不如此。
在Ubuntu下設定了目錄的SGID之後,在那個目錄下建立的檔案,擁有者是有效賬戶,而擁有組是該目錄的擁有組。

命令:
chmod g+s
chmod g-s

3.6.3 StickyBit

可以對檔案或者目錄設定,如果對檔案設定,那麼當這個檔案執行並退出後,系統依舊保留該檔案對應的映象,這樣這個程式再次執行時,就不需要載入再載入了。這個屬性的作用並不大,因為它佔用了記憶體。

如果對目錄設定,那麼表示在該目錄下,賬戶只能刪除和修改它自己建立的檔案,對於其它賬戶建立的檔案,它不能修改和刪除。這個位作用比較大,在一些公共目錄,往往有這個屬性,比如/tmp

命令:
chmod o+t
chmod o-t

總結:

設定物件設定方法檢視效果
SUID 檔案 chmod u+s 如果使用者執行許可權位為s或者S,則表示SUID有設定 當該檔案被執行時,程序所擁有的許可權是擁有該檔案的賬戶許可權
SUID 目錄 不可設定
SGID 檔案 chmod g+s 如果組執行許可權位為s或者S,則表示GUID有設定 當執行該檔案時,程序所屬組是該擁有該檔案的組
SGID 目錄 chmod g+s 同上 在該目錄中建立檔案時,該檔案的所屬組是目錄的所屬組
StickyBit 檔案 chmod o+t 如果其他執行許可權位為t或者T,那麼該檔案有設定StickyBit 執行該檔案並退出後,系統保留該檔案佔用的一些記憶體,以便加快下一次的載入執行
StickyBit 目錄 chmod o+t 同上 賬戶只能修改和刪除該目錄下屬於該賬戶的檔案,不能修改該目錄下其他賬戶建立的檔案

3.7 檔案長度

st_size儲存檔案的長度,write函式會修改該屬性,也可以通過truncate修改檔案大小,truncate可以擴大檔案或者縮小檔案,縮小檔案時,檔案內容會被刪減。

檔案大小可以通過lswc -cstat命令獲取。
也可以通過fseekftell函式配合獲取,或者直接通過stat函式獲取檔案長度。

3.8 檔案系統

3.8.1 檔案管理

檔案系統描述檔案在硬碟中的組織,儲存在硬碟中的檔案只有普通檔案、目錄、軟連結。

為了更加方便的管理永續性檔案儲存,作業系統一般對硬碟進行有規劃的管理,規劃包括:

  • 分割槽

  • 格式化

檔案系統指一個分割槽內,檔案儲存的組織方式。

在Linux下,通過mount命令將分割槽掛載到虛擬檔案系統。

3.8.2 inode

一個硬碟分割槽,被格式化之後,可以認為硬碟被劃分成兩部分:管理資料和資料。管理資料部分儲存著這個分割槽的分割槽資訊,以及inode表。

inode儲存檔案的屬性資訊,stat命令能看到的資訊,大部分都是儲存在inode裡的,一個inode佔用128或者256位元組,這個依賴具體的檔案系統,每當在硬碟上建立一個檔案/目錄時,系統為這個檔案/目錄分配一個inode。值得注意的是,檔名,不存在inode中,而是存在檔案所在目錄的檔案內容部分。

3.8.3 資料塊

資料部分被簡單的、按照等大尺寸的劃分成n塊,一般每塊資料塊的尺寸為1024-4096,由具體檔案系統決定。

3.8.4 檔案

當建立一個檔案時,系統為該檔案分配一個inode。如果往該檔案寫資料,那麼系統為該檔案分配資料塊,inode會記錄這個資料塊位置,當一個數據塊不夠用時,系統會繼續為它分配資料塊。

3.8.5 目錄

當建立一個目錄時,系統為該目錄分配一個inode,同時分配一個數據塊,並且在該資料塊中,記錄檔案...對應的inode。

如果在該目錄下建立檔案newfile,那麼參考上一節內容,會為該檔案建立inode,最後將newfile檔名和它的inode,作為一條記錄,儲存在目錄的資料塊中。

如果一個inode被別人引用,那麼它的引用計數器會加1。

3.8.6 路徑和定址

Linux系統採用以/劃分的路徑字串來定址檔案。

比如命令mkdir testdir,定址和操作過程如下圖:


思考:為什麼mv命令很快,而cp命令很慢,rename如何實現的

補充:分割槽

檢視磁碟資訊

sudofdisk-l

磁碟名字 sda sdb ..
分割槽名字 sda1 sda2 ...
分割槽

sudofdisk/dev/sdb

n 建立新分割槽
p 輸出分割槽資訊
w 儲存分割槽資訊並退出

分割槽和掛載

sudomkfs.ext4/dev/sdb1 sudomount/dev/sdb1xxyy

掛載成功之後,對xxyy目錄的讀寫,其實是在/dev/sdb1檔案系統中。

開機自動掛載
通過mount掛載的目錄是臨時的。如果希望開酒就掛載,那麼可以將掛載命令寫入到/etc/profile。或者修改/etc/fstab檔案,/etc/fstab描述了開機需要掛載的檔案系統資訊。

去除掛載
通過手動umount去除掛載。

3.8.7 硬連結和軟連結

硬連結不佔用inode,只佔用目錄項。
軟連結佔用inode。

建立連結命令ln,硬連結只將對應的inode在目錄總增加一個名字,並且將inode的引用計數器+1。

為了可以跨檔案系統和對目錄進行連結,建立了軟連結這種方式。ln -s

//file-->file2 intmain() { //getfile2 structstatbuf; stat("file",&buf); //get連結檔案file的資訊 structstatbuf2; lstat("file",&buf2); //如果lstat的引數所指檔案不是連結檔案 //那麼它的效果和stat一樣 structstatbuf3; lstat("file2",&buf3) } 讀取symlink內容使用readlink命令。 刪除軟連結不會刪除軟連結指向的檔案。

思考:為什麼硬連結不能跨檔案系統,而且不能對目錄進行硬連結

3.8.8 虛擬檔案系統VFS

記憶體無法載入硬碟所有內容,因為一般記憶體比硬碟小,但是在Linux核心中,維護了一個虛擬檔案系統,將硬碟的目錄結構對映到記憶體中。這個對映一般只包含已經被開啟的檔案。


3.9 檔案刪除

使用unlink命令和函式可以刪除一個檔案。
如果此時檔案已經開啟,那麼該檔案也可以被unlink,但是刪除操作不會立即執行,而會被保留到檔案關閉時執行。

unlink刪除檔案,如果是連結,就刪除連結,如果不是連結就刪除檔案。 rmdir只能刪除空目錄 rm會判斷引數型別,如果是檔案那麼呼叫unlink,如果是目錄呼叫rmdir 如果要刪除非空目錄,要使用rm-r,-r選項先刪除目錄中的檔案,再呼叫rmdir。 intrm(constchar*path); { //怎麼遍歷目錄 }

3.10 檔案時間

對檔案的訪問,會導致檔案時間發生變化。系統會自動記錄使用者對檔案的操作的時間戳,以便將來可以查詢檔案修改時間。

如果需要故意修改,那麼可以通過utime函式,修改檔案的訪問時間和修改時間。

touch命令也可以將檔案的時間修改為當前時間。touch命令的副作用是,如果引數所指檔案不存在,那麼建立一個空檔案。

當用戶進行大規模拷貝時,cp操作會修改檔案的訪問時間,如果想提高效率,可以使用-p選項,避免檔案屬性的修改,同時加快速度。

#include <sys/types.h>
       #include <utime.h>

int main()
{
    struct utimbuf buf;
    buf.actime = 0;
    buf.modtime = 0;
    utime("a.out", &buf);
}

利用utime來修改檔案的訪問時間和修改時間

#include"../h.h" intmain() { structutimbufbuf; buf.actime=0; buf.modtime=0; utime("testfile",&buf); structtimevaltv[2]; tv[0].tv_sec=100000; tv[0].tv_usec=10000; tv[1].tv_sec=100000; tv[1].tv_usec=10000; utimes("testfile",tv); }

3.11 目錄操作

3.11.1 建立和刪除目錄

mkdirrmdir

3.11.2 遍歷目錄

opendirclosedirreaddirrewinddirtelldirseekdir

遍歷目錄

#include"../h.h" intrm(constchar*path) { //判斷path是個檔案還是目錄 //如果是檔案,直接unlink然後返回 structstatstat_buf; intret=stat(path,&stat_buf); if(ret<0) { perror("stat"); return-1; } if(!S_ISDIR(stat_buf.st_mode)) { unlink(path); return0; } //如果path是目錄,遍歷目錄中的所有目錄項 charbuf[1024]; DIR*dir=opendir(path); if(dir==NULL) return-1; structdirent*entry=readdir(dir); while(entry) { //通過entry得到檔案資訊 sprintf(buf,"%s/%s",path,entry->d_name); if(entry->d_type==DT_REG||entry->d_type==DT_LNK) { unlink(buf); } if(entry->d_type==DT_DIR) { //忽略.和..目錄 if(strcmp(entry->d_name,".")==0 ||strcmp(entry->d_name,"..")==0) { entry=readdir(dir); continue; } rm(buf); } entry=readdir(dir); } closedir(dir); rmdir(path); return0; } intmain(intargc,char*argv[]) { if(argc==1) { printf("usage:%s[pathname]\n",argv[0]); return0; } rm(argv[1]); return0; }

seekdir和telldir

#include"../h.h" intmain() { longloc; DIR*dir=opendir("testdir"); //no //seekdir(dir,2); // structdirent*entry; while(1) { loc=telldir(dir); entry=readdir(dir); if(entry==NULL)break; if(strcmp(entry->d_name,"a")==0) { //記錄檔案a的位置 break; } } seekdir(dir,loc); while(1) { entry=readdir(dir); if(entry==NULL) break; printf("locis%d,entry->d_name=%s\n",(int)telldir(dir),entry->d_name); } //將檔案指標回到a的位置 //seekdir(dir,loc); }
#include <dirent.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    DIR* dir = opendir(argv[1]);
    struct dirent* entry;

    while(1)
    {
        entry = readdir(dir);
        if(entry == NULL)
            break;

        // linux下,.開頭是隱藏檔案
        if(entry->d_name[0] == '.')
            continue;

        printf("%s\n", entry->d_name);
    }

    closedir(dir);
}

3.12 練習

3.12.1 實現檔案拷貝,保留檔案屬性

#include"../h.h" intmain(intargc,char*argv[]) { if(argc!=3) { printf("usage:%s[srcfile][dstfile]\n",argv[0]); return-1; } constchar*filesrc=argv[1]; constchar*filedst=argv[2]; FILE*fp_src=fopen(filesrc,"r"); FILE*fp_dst=fopen(filedst,"w"); charbuf[4096]; while(1) { intret=fread(buf,1,sizeof(buf),fp_src); if(ret<=0) break; fwrite(buf,ret,1,fp_dst); } fclose(fp_src); fclose(fp_dst); //獲取原始檔屬性 structstatsrc_stat; stat(filesrc,&src_stat); //修改目標檔案時間 structutimbuftimbuf; timbuf.actime=src_stat.st_atime; timbuf.modtime=src_stat.st_mtime; utime(filedst,&timbuf); //修改許可權 chmod(filedst,src_stat.st_mode); //修改所有者 intret=chown(filedst,src_stat.st_uid,src_stat.st_gid); if(ret<0) { perror("chown"); } return0; }

3.12.2 實現目錄打包到檔案,將檔案解包成目錄

#include"../h.h" #include<map> #include<string> usingnamespacestd; map<ino_t,std::string>savedfiles; voidtarfile(constchar*filename,FILE*fpOut) { structstatstat_buf; stat(filename,&stat_buf); //檢查這個檔案是否已經儲存過,是否是其他檔案的硬連結 std::stringfilesaved=savedfiles[stat_buf.st_ino]; if(filesaved.size()!=0) { //此ino之前已經存過了 fprintf(fpOut,"h\n%s\n%s\n",filename,filesaved.c_str()); return; } fprintf(fpOut,"f\n%s\n%lld\n",filename,(longlongint)stat_buf.st_size); FILE*fpIn=fopen(filename,"r"); charbuf[4096]; while(1) { intret=fread(buf,1,sizeof(buf),fpIn); if(ret<=0) break; fwrite(buf,ret,1,fpOut); } fclose(fpIn); //savedfiles.insert(std::pair<ino_t,std::string>(stat_buf.st_ino,std::string(filename))); //如果該檔案不是別人的硬連結,那麼將檔案拷貝之後,在全域性map中記錄ino savedfiles[stat_buf.st_ino]=std::string(filename); } inttardir(constchar*dirname,FILE*fpOut) { charfilepath[1024]; //寫目錄 fprintf(fpOut,"d\n"); fprintf(fpOut,"%s\n",dirname); DIR*dir=opendir(dirname); structdirent*entry=readdir(dir); while(entry) { sprintf(filepath,"%s/%s",dirname,entry->d_name); //handle if(entry->d_type==DT_REG) { //writefile tarfile(filepath,fpOut); } elseif(entry->d_type==DT_DIR) { if(strcmp(entry->d_name,".")==0|| strcmp(entry->d_name,"..")==0) { entry=readdir(dir); continue; } tardir(filepath,fpOut); } entry=readdir(dir); } closedir(dir); } inttar(constchar*dirname,constchar*outfile) { FILE*fpOut=fopen(outfile,"w"); fprintf(fpOut,"xgltar\n"); fprintf(fpOut,"1.0\n"); intret=tardir(dirname,fpOut); fclose(fpOut); returnret; } #defineline_buf_size1024 charline_buf[line_buf_size]; #defineget_line()fgets(buf,line_buf_size,fin) intuntar1(FILE*fin) { char*buf=line_buf; if(get_line()==NULL) return-1; printf("nowutartype=%s",buf); if(strcmp(buf,"d\n")==0) { get_line(); buf[strlen(buf)-1]=0; mkdir(buf,0777); printf("mkdir%s\n",buf); } elseif(strcmp(buf,"f\n")==0) { get_line(); buf[strlen(buf)-1]=0;//filename FILE*out=fopen(buf,"w"); printf("createfile%s\n",buf); get_line(); longlongintlen=atoll(buf);//1987\n printf("filelen%s\n",buf); while(len>0) { //避免粘包問題 intreadlen=len<line_buf_size?len:line_buf_size; intret=fread(buf,1,readlen,fin); fwrite(buf,1,ret,out); len-=ret; } fclose(out); } elseif(strcmp(buf,"h\n")==0) { get_line(); buf[strlen(buf)-1]=0;//filenamenew std::stringnew_path(buf); get_line(); buf[strlen(buf)-1]=0;//hardlinefilenameold link(buf,new_path.c_str()); } return0; } intuntar(constchar*tarfile) { char*buf=line_buf; FILE*fin=fopen(tarfile,"r"); //fgets(buf,line_buf_size,fin); get_line(); if(strcmp(buf,"xgltar\n")!=0) { printf("unknownfileformat\n"); return-1; } get_line(); if(strcmp(buf,"1.0\n")==0) { while(1) { intret=untar1(fin); if(ret!=0) break; } } else { printf("unknownversion\n"); return-1; } return0; } //./mytar-cdirtarfile.xgl //./mytar-utarfile.xgl intmain(intargc,char*argv[]) { if(argc==1) { printf("usage:\n\t%s-c[dir][tarfile]\n\t%s-u[tarfile]\n",argv[0],argv[0]); return-1; } constchar*option=argv[1]; if(strcmp(option,"-c")==0) { constchar*dirname=argv[2]; constchar*outfile=argv[3]; returntar(dirname,outfile); } elseif(strcmp(option,"-u")==0) { constchar*tarfile=argv[2]; returnuntar(tarfile); } printf("optionerror\n"); return-1; }

3.13 函式和命令

3.13.1 函式

stat/lstat:檢視檔案屬性
chmod:修改檔案許可權
chown:修改檔案的所有者
utime:修改檔案時間
unlink:刪除檔案
link:建立硬連結
symlink:建立軟連結
rmdir:刪除空目錄
mkdir:建立空目錄
opendir:開啟目錄
closedir:關閉目錄
readdir:讀取一個目錄項,並且將目錄項指標移到下一項
seekdir:修改目錄項指標
rewainddir:重置目錄項指標
telldir:獲得當前目錄向指標
判斷許可權位巨集 S_IRUSR(stat.st_mode)
判斷檔案型別巨集S_ISDIR(stat.st_mode)

3.13.2 命令

stat:檢視檔案屬性
chmod:修改檔案許可權
chown
unlink:刪除檔案(不會跟隨)
ln:建立連結
mkdir
rmdir
rm
cp
dd:拷貝資料(可以拷貝檔案,也拷貝塊裝置)
wc:計算檔案內容的行數、單詞數、位元組數
which:查詢非內建的命令位置
fdisk:檢視磁碟資訊、分割槽
mkfs:在分割槽中建立檔案系統(ext2,ext3,ext4, fat32, ntfs, xfs,nfs)
mount:掛載
umount:取消掛載

發表於 2018-07-04 14:30 天碼丶行滿 閱讀(...) 評論(...) 編輯 收藏 重新整理評論重新整理頁面返回頂部