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是一個整數,因此它還儲存其它內容,如果需要判斷一個檔案屬於何種型別,需要一些巨集的幫助。
檔案型別屬性是隻讀的屬性,無法修改。
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可以擴大檔案或者縮小檔案,縮小檔案時,檔案內容會被刪減。
檔案大小可以通過ls
,wc -c
,stat
命令獲取。
也可以通過fseek
和ftell
函式配合獲取,或者直接通過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 ...
分割槽
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,但是刪除操作不會立即執行,而會被保留到檔案關閉時執行。
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 建立和刪除目錄
mkdir
和rmdir
3.11.2 遍歷目錄
opendir
,closedir
,readdir
,rewinddir
,telldir
,seekdir
遍歷目錄
#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:取消掛載