可重用函數
不使用全局部變量,可以重復使用的函數.
stat 命令
作用:顯示一個文件或文件夾的“元信息”。
文件基本信息
文件(File):顯示所查詢對象的名稱。
大小(Size):文件的大小,以字節為單位。
塊(Blocks):文件在磁盤上所占用的物理塊數量(通常以 512字節 或 1K 的塊為單位,取決于系統和 stat 的實現)。
IO 塊(IO Block):文件系統每次讀寫操作的數據塊大小。
文件類型和索引節點(Inode)
- 設備(Device):文件所在的設備標識符,通常以 主設備號,次設備號 的形式表示,指明了文件存儲在哪個硬件設備上。
- Inode:文件的索引節點號。Inode 是文件系統中存儲文件元數據(除文件名外的一切信息)的數據結構。每個 Inode 號在同一個文件系統內是唯一的。
文件類型(Filetype),例如:
- 常規文件(regular file)
- 目錄(directory)
- 符號鏈接(symbolic link)
- 塊設備(block device)
- 字符設備(character device)
- 套接字(socket)
- 命名管道(FIFO)等。
權限和鏈接
- 權限(Access):文件的訪問權限,以數字(八進制,如 0644) 和類似 ls -l 的符號(如 -rw-r--r--) 兩種形式顯示。
- 硬鏈接數(Links):指向該 Inode 的硬鏈接數量。對于目錄,這個數字通常至少為 2(本身和其內部的 . 目錄)。
所有權信息
UID / GID:文件所有者的用戶ID(User ID)和組ID(Group ID)。
時間戳(Timestamps) - 這是 stat 命令最核心和詳細的信息之一 它提供了三個精確到納秒的時間戳,遠比其他命令詳細:
- 訪問時間(Access):文件內容最后一次被讀取的時間(例如,
- 修改時間(Modify):文件內容最后一次被修改的時間。
- 變更時間(Change):文件的元數據(如權限、所有權、硬鏈接數等屬性)最后一次被改變的時間。
stat 命令格式
stat [文件或文件夾]
stat 函數
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<unistd.h>// 使用文件路徑獲件的信息
int?stat(const?char?*pathname,?struct?stat *statbuf);// 使用文件描述符獲件的信息
int?fstat(int?fd,?struct?stat *statbuf);// lstat獲取軟鏈接文件本身的信息, stat獲取文件的信息
int?lstat(const?char?*pathname,?struct?stat *statbuf);
struct stat 結構體
struct?stat?{dev_t?????st_dev;?????????/* ID of device containing file 設備文件ID */ino_t?????st_ino;?????????/* Inode number 索引節點數*/mode_t????st_mode;????????/* File type and mode 文件類型和模式 , st_mode. 的文件類型等信息,詳見 inode(7):man 7 inode*/nlink_t???st_nlink;???????/* Number of hard links 硬連接數*/uid_t?????st_uid;?????????/* User ID of owner 擁有者ID*/gid_t?????st_gid;?????????/* Group ID of owner 擁有者組ID*/dev_t?????st_rdev;????????/* Device ID (if special file) 設備ID */off_t?????st_size;????????/* Total size, in bytes 文件大小*/blksize_t?st_blksize;?????/* Block size for filesystem I/O 每次IO 的塊大小 */blkcnt_t??st_blocks;??????/* Number of 512B blocks allocated 以 512 字節計算的塊數。*//* Since Linux 2.6, the kernel supports nanosecondprecision for the following timestamp fields.For the details before Linux 2.6, see NOTES. */struct?timespec?st_atim;??/* Time of last access 最后訪問時間*/struct?timespec?st_mtim;??/* Time of last modification 最后修改時間 */struct?timespec?st_ctim;??/* Time of last status change 最后元數據塊修改時間*/#define?st_atime st_atim.tv_sec??????/* Backward compatibility */
#define?st_mtime st_mtim.tv_sec
#define?st_ctime st_ctim.tv_sec
};
st_mode 的類型掩碼和值
// 類型掩碼(4bit)
S_IFMT 0170000 bit mask for the file type b
it field// 掩碼后的值:
S_IFSOCK 0140000 socket 套接字
S_IFLNK 0120000 symbolic link 符號鏈接
S_IFREG 0100000 regular file 普通文件
S_IFBLK 0060000 block device 塊文件
S_IFDIR 0040000 directory 文件夾
S_IFCHR 0020000 character device 字符設備文件
S_IFIFO 0010000 FIFO 管道文件
// 用法
strcut stat sb;
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {/* Handle regular file */
}
判斷 st_mode 是哪一類型文件的宏定義
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not inPOSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
// 用法
struct stat sb;
stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {/* Handle regular file */
}
示例代碼:
#include?<time.h>int?main(int?argc,?char?* argv[])?{int?ret;struct?stat?st;ret = stat("homework01.c", &st);// ret = stat("./test", &st);printf("文件大小:%ld\n", st.st_size);printf("文件塊個數:%ld\n", st.st_blocks);printf("修改時間:%s\n", ctime(&st.st_mtim.tv_sec));// 判斷stat 的第一個參數是什么類型的文件。switch?(st.st_mode & S_IFMT) {?// man 7 inode 查找宏定義case?S_IFREG:printf("普通文件\n");break;case?S_IFDIR:printf("文件夾\n");break;default:printf("其他類型的文件\n");}printf("程序結束\n");return?0;
}
時間函數
#include?<time.h>// 將 struct tm 轉為時間字符串。
char?*asctime(const?struct?tm *tm);
char?*asctime_r(const?struct?tm *tm,?char?*buf);// 將 time_t 轉為時間字符串,格式: Fri Aug 22 09:40:48 2025。
char?*ctime(const?time_t?*timep);
char?*ctime_r(const?time_t?*timep,?char?*buf);
文件夾遍歷
// 打開文件夾
#include?<sys/types.h>
#include?<dirent.h>
DIR *opendir(const?char?*name);// 遍歷文件夾
#include?<dirent.h>
struct?dirent *readdir(DIR *dirp);// 關閉文件夾
#include?<sys/types.h>
#include?<dirent.h>
int?closedir(DIR *dirp);
示例
#include?<stdio.h>
#include?<sys/types.h>
#include?<dirent.h>int?main(int?argc,?char?* argv[])?{DIR *dir;struct?dirent?*curd;dir = opendir(".");while(curd = readdir(dir)) {// #define NULL ((void*)0)// printf("%p\n", curd);printf("curd->d_type:%#x, curd->d_name:%s\n",?curd->d_type, curd->d_name);}closedir(dir);printf("程序結束\n");return?0;
}
glob 函數查找文件
作用:使用模式匹配解析文件夾內容。
匹配樣式特殊字符
* 匹配0個一個或多個字符
? 匹配任意單個字符。
[a-z] 匹配一個某個范圍內的字符。
[xyz] 匹配一個字符,這個字符必須是 x 或 y 或 z
[^xyz] 匹配一個字符,這個字符不能是 x 或 y 或 z
相關函數
#include?<glob.h>// 獲取模式匹配的路徑信息,存入pglob。
int?glob(const?char?*pattern,?int?flags,int?(*errfunc) (const?char?*epath,?int?eerrno),glob_t?*pglob);
// 參數:
// pattern 路徑的樣式
// flags 搜索的標志位(位或可以加入多個選項):GLOB_NOSORT 表示不排序,
// errfunc 錯誤回調函數,不用時置空
// pglob 解析到的目錄個數以及各個文件名的數組。//釋放glob函數動態開辟的空間
void?globfree(glob_t?*pglob);// 類型:
glob_t?//存儲解析到的目錄個數以及各個文件名
glob_t 類型
?typedef?struct?{size_t???gl_pathc;????/* Count of paths matched so far */char???**gl_pathv;????/* List of matched pathnames. */size_t???gl_offs;?????/* Slots to reserve in gl_pathv. */
}?glob_t;
示例:
#include?<stdio.h>
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<unistd.h>
#include?<time.h>
#include?<sys/types.h>
#include?<dirent.h>
#include?<glob.h>// 搜索 /etc/pass* 或 當前的"*.c" 文件
int?main(int?argc,?char?* argv[])?{glob_t?gs;?// 用于存儲解析到的文件個數和文件名int?i;?// 循環變量int?ret;ret = glob("/etc/pass*", GLOB_NOSORT,?NULL, &gs);if?(ret) {printf("glob error:%d\n", ret);}ret = glob("./0[0-9]*.c", GLOB_APPEND,?NULL, &gs);// 循環打印遍歷的結果。for?(i =?0; i < gs.gl_pathc; i++) {printf("path: %s\n", gs.gl_pathv[i]);}// 釋放glob 開辟的內存空間。globfree(&gs);printf("程序結束\n");return?0;
}
getopt 函數
作用:解析命令行選項【option】和參數【argument】
函數格式
#include?<unistd.h>
int?getopt(int?argc,?char?*?const?argv[],?const?char?*optstring);
示例代碼
#include?<stdio.h>
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<unistd.h>// 命令選項:?
// -f <文件路徑> 文件路徑可以通過 optarg 全局變量獲取。
// -c 創建?
// -x 解壓縮
int?main(int?argc,?char?* argv[])?{const?char?* optstring =?"cxf:";int?ret;??//用于接收返回的命令選項while?(1) {ret = getopt(argc, argv, optstring);if?(ret ==?-1) {??// 解析完畢break;?// 結束循環}switch(ret) {case?'x':?printf("-x 選項被輸入\n");break;case?'c':?printf("-c 選項被輸入\n");break;case?'f':?printf("-f 選項: %s\n", optarg);break;default:break;}}printf("程序結束\n");return?0;
}
密碼加密
Linux 下保存密碼的文件是:/etc/shadow
easthome 的密碼信息:
easthome:$6$ISBgRfwrvZNBigDh$ynkvsffzGH2ovijtzar9CUiu37cTfx0SJcSs.TYWsXyVoJb1xFfmQX.PDyx5B0eU7mNZf0umcPI0ZvmpDf/dR/:20290:0:99999:7:::$6$ISBgRfwrvZNBigDh$ynkvsffzGH2ovijtzar9CUiu37cTfx0SJcSs.TYWsXyVoJb1xFfmQX.PDyx5B0eU7mNZf0umcPI0ZvmpDf/dR/
實現用戶登陸之前的密碼校驗:
相關函數
// 獲取密碼函數
#include?<unistd.h>
char?*getpass(const?char?*prompt);// 根據加密算法和鹽信息加密字符串?
// 使用 crypt 函數在編譯時需要加載 libcrypt 庫。 gcc -lcrypt
#include?<crypt.h>
char?*?crypt(const?char?*phrase,?const?char?*setting);
// 參數:?
// phrase: 要加密的信息。
// setting 加密算法和鹽信息,格式為 "$加密算法$鹽$"
// 返回值:
// 加密后的哈希字符串。// 讀取/etc/shadow 密碼信息
#include?<shadow.h>
struct?spwd *getspnam(const?char?*name);??// name 是用戶名。
// 參數:
// name 用戶名
// 返回值:
// shadow 中的加密算法、鹽、和哈希字符串。
示例代碼
#include?<stdio.h>
#include?<sys/types.h>
#include?<sys/stat.h>
#include?<unistd.h>
#include?<shadow.h>
#include?<crypt.h>
#include?<string.h>#define?MAX_NAME_SIZE (20)int?main(int?argc,?char?* argv[])?{char?username[MAX_NAME_SIZE] = {};?// 用于存儲用戶名.char?* pwd =?NULL;?// 用于指向用戶輸入的密碼。struct?spwd?*?tp?=?NULL;?// 指向shadow文件中讀出的信息。char?* serial =?NULL;?// 事項加密后的哈希值(密碼)。printf("請輸入用戶名:");fflush(stdout);int?ret = read(0, username, MAX_NAME_SIZE);//int ret = fgets(username, MAX_NAME_SIZE, stdin); // 此函數會讀取回車符號username[ret-1] =?'\0';?// 把 '\n' 替換成 '\0';// 讀取密碼pwd = getpass("請輸入密碼:");if?(NULL?== pwd) {perror("getpass");return?-1;}?// printf("username:%s, password:%s\n", username, pwd);// 讀取系統內/etc/shadow真實的密碼信息tp = getspnam(username);if?(NULL?== tp) {printf("獲取shadow 密碼失敗\n");return?-2;}//將輸入的密碼進行加密serial = crypt(pwd, tp->sp_pwdp);?// 使用加密算法和鹽進行加密。if?(NULL?== serial) {perror("crypt");return?-3;}?//對比生成的 serial 和 tp->sp_pwdp 是否相同。如果相同說明驗證成功。if?(0?==?strcmp(tp->sp_pwdp, serial)) {printf("登陸成功\n");}?else?{printf("登陸失敗\n");}printf("程序結束\n");return?0;
}
編譯(加?-lcrypt
?選項):
gcc?05_check_password.c -lcrypt
字符串分割
strtok 函數
作用:分割字符串
調用格式
#include?<string.h>
char?*strtok(char?*str,?const?char?*delim);
// 參數:
// str 需要分割的字符串,第一次需要傳入可變字符串,之后需要傳入NULL
// delim 分割的字符串
// 返回值: 返回分割的子字符串的起始地址,到達末尾時返回NULL。
字符串的內容:
"zhangsan,18,100"
示例代碼
#include?<stdio.h>
#include?<string.h>// 分割字符串示例;
int?main(int?argc,?char?* argv[])?{char?buffer[] =?"zhangsan,18,100,1.73";char?* str_head = buffer;?//將str_head指向字符串的首地址char?* str_ret;while(1) {str_ret = strtok(str_head,?",");?if?(NULL?== str_ret)break;printf("str_ret:%s\n", str_ret);str_head =?NULL;}// str_ret = strtok(str_head, ",");?// printf("str_ret:%s\n", str_ret);// str_ret = strtok(NULL, ",");?// printf("str_ret:%s\n", str_ret);// str_ret = strtok(NULL, ",");?// printf("str_ret:%s\n", str_ret);printf("程序結束\n");return?0;
}
strsep 函數
調用格式
#include?<string.h>
char?*strsep(char?**stringp,?const?char?*delim);
// 參數
// stringp 是指向字符串指針的地址。
// delim 分割的字符串
// 返回值: 返回子字符串的起始地址,即*stringp。到達末尾時返回NULL。
示例代碼
#include?<stdio.h>
#include?<string.h>// 分割字符串示例;
int?main(int?argc,?char?* argv[])?{char?buffer[] =?"zhangsan,18,100,1.73";char?* str_head = buffer;?//將str_head指向字符串的首地址char?* str_ret;while(1) {str_ret = strsep(&str_head,?",");?if?(NULL?== str_ret)break;printf("str_ret:%s\n", str_ret);}// str_ret = strsep(&str_head, ",");// printf("str_ret:%s\n", str_ret);// str_ret = strsep(&str_head, ",");// printf("str_ret:%s\n", str_ret);// str_ret = strsep(&str_head, ",");// printf("str_ret:%s\n", str_ret);printf("程序結束\n");return?0;
}
常量字符串不能用于上述兩個函數。