可重用函数
不使用全局部变量,可以重复使用的函数.
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 nanosecond
precision 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 in
POSIX.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;
}
常量字符串不能用于上述两个函数。