【MyBash项目】一、知识储备

文章目录:

    • 一、Linux相关函数知识储备
      • (一)获取用户信息
      • 1. 获取用户UID、登录名
      • 2. 根据UID或登录名获取用户信息
      • (二)获取主机信息
      • (二)切换目录 && 获取当前工作目录
      • (三)扫描目录获取目录下的文件
      • (四)获取文件属性,类型,权限
      • (五)重新设置有效用户UID
      • (六)获取当前用户密码
      • (七)加密函数
      • (八)添加所有者权限
      • (九)取消/添加系统回显功能
    • 二、处理字符串函数知识储备
      • (一)分割字符函数strtok()
      • (二)初始化数组函数memset()
      • (三)判断字符存在函数strstr()
      • (四)printf()函数的特殊控制
        • 1. 输出带颜色的信息
        • 2. 其余特殊控制

一、Linux相关函数知识储备

(一)获取用户信息

1. 获取用户UID、登录名

在Linux内部机制上用户拥有唯一的用户标识符UID,Linux运行的每个程序实际上都是以某个用户的名义在运行,所以有的程序必须管理员身份才可以运行,我们需要su将UID改为超级用户UID(0)。所以UID时用户身份的关键。UID的分类如下:

UID范围 含义
0 系统管理员,所以当你要让普通用户具有管理员的权限时,将该账号的UID改为0即可
1~999 系统账号,又分为:1~200:由Linux发行版本自行建立的系统账号,一般用来运行网络服务和后台服务;201~999:若用户由系统账号需求时,进行分配
1000~6000 给一般用户使用,目前Linux3.10可以支持到2^32-1

那我们如何获取用户的UID和登录名呢,系统给我们提供了两个方法getuid,getlogin

# include
# include

uid_t getuid(void) //返回程序关联的UID即启动程序的用户UID
char* getlogin(void)//返回当前用户的登录名

uid_t是UID自己的数据类型,定义在头文件sys/types.h中,通常是一个小整数。

2. 根据UID或登录名获取用户信息

我们之前在用户管理命令这一部分说过:系统文件/etc/passwd包含一个用户账号数据库,它由行组成,每行对应一个用户,包括用户名、加密密码占位符,用户标识符UID,组标识符GID,用户全名,家目录和默认shell。如何获取到这些信息呢?系统给我们提供了两个方法getpwuid,getpwnam

# include
# include

struct passwd* getpwuid(uid_t uid);//根据UID获取信息
struct passwd* getpwnam(const char* name);//根据用户名获取信息
                          成功返回一个指向passwd结构的指针,失败返回空指针并设置errno

passwd结构我们称它为密码数据库,存储用户的相信信息,在头文件pwd.h中,主要包含以下信息:

passwd成员 说明
char* pw_name 用户登录名
uid_t pw_uid UID号
gid_t pw_gid GID号
char* pw_dir 用户家目录
char* pw_gecos 用户全名
char* pw_shell 用户默认shell

我们可以根据:

struct passwd *pw;
pw=getpwuid(getuid());
pw=getpwnam("xxx")

获取到指向xxx用户的数据库的pw指针,根据结构体指针的指向我们可以获取passwd中的成员,如指针pw->pw_dir获取家目录信息。

(二)获取主机信息

可以根据函数获取到用户详细信息,那么也可以通过函数uname获取到主机的信息,我们可以根据系统调用函数获取主机的详细信息:

# include
int uname(struct utsname* name);
            成功返回一个非负整数,失败返回-1,并设置errno指出错误

uname函数把主机信息写入name参数指向的结构,这个结构类型是utsname,它定义在头文件sys/utsname.h中,包含以下信息:

utsname成员 说明
char sysyname[] 操作系统名
char nodename[] 主机名
char release[] 系统发行级别
char version[] 系统版本号
char machine[] 硬件类型

我们可以根据:

struct utsname uts;
uname(&uts)

将主机信息保存在uts结构体中,根据结构体的指向可以获取到主机信息,如uts.nodename获取到主机名。

获取用户信息返回的是一个指向passwd结构体的指针,得到其成员变量需要用【->】符号,获取主机信息得到一个被填充的结构体,得到其成员变量需要用【.】符号。因为它们的本质不同,一个是指针结构体,一个是结构体。

(二)切换目录 && 获取当前工作目录

cd命令可以在shell中切换路径目录,在程序中使用系统调用chdir也可以进行目录切换:

# include 
int chdir(const char* path); 
             成功返回0,失败返回-1

程序可以通过调用函数获取自己当前的绝对工作目录,函数getcwd功能和命令pwd一样:

# include 
char* getcwd(char* buf,size_t size);
                成功返回buf指针,失败返回NULL

getcwd函数把当前绝对目录的名字写到给定的缓冲区buf中,如果目录名的长度超过给出的size即缓冲区长度,那么就失败,返回NULL。

(三)扫描目录获取目录下的文件

Linux下确定一个特定目录下存放的文件,在shell终端我们可以ls获取,但是在程序中如何通过函数获取呢,Linux提供了一整套的库函数,可以实现对目录进行扫描,获取等操作。
先了解一下DIR结构,它作为目录操作的基础,被称为目录流的指向,这个结构的指针(DIR*)被用来完成各种目录操作,其使用方法与用来操作普通文件的文件流(FILE*)非常相似。目录数据项本身在dirent结构中返回,该结构也是dirent.h头文件里声明的,这是因为用户不应该直接改动DIR结构中的数据字段。
操作目录流用这三个方法:opendir,readdir,closedir就可以实现,我们逐一来看一下:
1.opendir:作用是打开一个目录并建立一个目录流:

# include
# include
DIR* opendir(const char* name);//传入路径
      成功返回一个指向DIR结构的指针,该指针用于读取目录数据项,失败返回一个空指针

当目录流使用一个底层文件描述符来访问文件本身,那么如果打开文件过多,opendir可能会失败。

2.readdir:用于读取已打开目录中的目录项相关信息 :

# include 
# include
struct dirent* readdir(DIR *dirp);
   成功返回一个指向存储目录项结构体dirent的指针,有错误发生或读取到目录文件尾则返回NULL

结构体dirent中包含的目录项内容包含以下部分:

成员 含义
ino_t d_ino 文件的inode节点号
char d_name[] 文件的名字

我们可以通过得到的指针,进行结构体成员的访问,如dir->d_name可以获取目录下的文件名。
注意,如果在readdir函数扫描目录的同时还有其他进程在该目录里创建或删除文件,readdir将不保证能够列出该目录里的所有文件和子目录。
3.closedir:用于关闭一个目录流并释放与之关联的资源:

# include
# include
int closedir(DIR* dirp);
        执行成功时返回0,发生错误时返回-1

根据这几个函数我们就可以实现一个类似ls的功能,即打开一个路径下的目录流,读取目录项,输出文件名,最后关闭目录流:下面是该目录下只有一个文件的情况,如果有多个文件,那么需要循环readdir读取目录流,获取信息

DIR* dp;//先定义DIR结构指针,指向打开的目录流
struct dirent *entry;//定义保存目录项的结构体指针,指向读取到的目录项结构体
dp=opendir(path);//path为需要打开目录的路径,结果是dp指针指向一个目录流
entry=readdir(dp);//打开dp指针指向的目录流,entry指针指向存储目录项的结构体
printf("%d",entry->d_node);//输出文件节点
printf("%s",entry->d_name);//输出文件名

(四)获取文件属性,类型,权限

  1. 文件属性的获取可以利用函数stat,lstat,fstat获取;
  2. 文件类型可以用头文件提供的函数,根据文件类型宏判断:
文件类型宏(st_mode) //判断是否为这个类型的文件,是返回1,否则返回0
  1. 文件权限可以利用:
if(st.st_mode & 文件权限宏) //判断该文件用户是否对应宏描述的权限

具体讲解见文件属性获取这篇博客,就不在此重复讲述了。

(五)重新设置有效用户UID

在Linux中每个进程都有三个用户标识符,分别为:

UID类型 用处
real uid 真实用户ID 是登录时的用户ID
saved uid 已保存的用户ID 由exec函数保存
effective uid 有效用户ID 在进程运行时的用户ID,可用于文件访问权限检查

一般来说,三者相等,但是在某些情况下,我们需要让程序运行时需要特殊的权限,如访问Linux系统的/etc/passwd文件,或让A用户可以以B用户的权限去做某些事,这就需要改变有效用户ID,使用setuid可以进行改变,让它们暂时获得相关操作权限。

# include
int setuid(uid_t uid);
     执行成功返回0,失败返回-1

setuid函数的执行分为以下几个方面:

  • 如果由普通用户调用,将当前进程的有效ID设置为uid。
  • 如果是由管理员(uid为0)调用,将真实,有效,保存的uid都设置新的uid。那么root用户就完全替换为该用户,风险很大。
  • 如果进程没有超级用户权限,那么只将有效用户id设置为uid,即以uid的权限运行该进程。
  • 如果进程有超级用户权限,那么将真实,有效,保存用户id都设置为uid,那么运行该进程的用户完全改变。

(六)获取当前用户密码

用户密码为了安全起见,都在一个称为阴影口令的文件/etcshadow中存放着,里面的密码都是加密过的密码,那么我们如何获取到shadow文件中加密后的密码呢?需要用到getspnam函数:

# include
struct spwd* getspnam(const char* name);//参数传入当前用户名
    成功返回指向spwd结构体的指针,失败返回NULL

spwd结构包含以下信息:

struct spwd {   
 char *sp_namp; //用户登录名   
 char *sp_pwdp; //加密密码
 long int sp_lstchg; //上次更改密码以来的时间   
 long int sp_min; // 进过多少天后可以更改 
 long int sp_max; //要求更改的剩余天数 
 long int sp_warn; // 到期警告的天数  
 long int sp_inact; // 账户不活动之前尚余天数    
 long int sp_expire; //账户到期天数 
 unsigned long int sp_flag; //保留 

那么我们根据:

struct spwd* sp=getspnam("root");//得到spwd结构体
char* p=sp->sp_pwdp;//获取加密密码

加密密码,由3部分组成:

$加密算法ID$加密密钥$密文

获取到它可以为我们密码验证做铺垫,如果我们将我们输入的密码,按照相同的加密算法,密钥进行加密,如果和系统的一样,就证明我们密码输入正确。

(七)加密函数

当我们输入密码后,系统会将我们的密码进行加密存储,我们学习一种常用的加密函数,即crypt函数,是一种单向加密技术,不可还原,同时不在C默认库里面,在crypt库里面,运行时需要手动连接。函数原型为:

char* crypt(const char* key,const char* salt);//key是我们需要传入的明文,即自己输入的密码,salt是我们指定的加密算法和密钥。
       成功返回加密后的密文,失败返回NULL

salt一般为:

$id$密钥;//id为加密算法类型,密钥不超过8字符

常用的加密算法,即id的取值为:

id 算法类型
1 MD5
2a Blowfish
5 SHA-256
6 SHA-512(当前Linux采用的加密算法)

最后形成的加密信息为:

$id$密钥$明文

(八)添加所有者权限

Linux系统中我们经常使用的权限有r,w,x,其实除了这3个权限外还支持s,t权限,我们今天主要了解s权限。
s权限:设置使文件在执行阶段具有文件所有者二等权限,相当于临时拥有文件所有者的身份。如访问shadow中的信息,需要管理员权限,那么我们就可以给它添加s权限。我们采用字符模式设置s权限:

chmod a+s filename;//给这个文件的所有者都添加s权限

在设置s权限前,必须要先设置x权限,否则chmod虽然不报错但无法s权限无法生效

(九)取消/添加系统回显功能

回显功能是显示屏幕上输入的信息,系统默认是存在回显功能的,但是当我们输入密码时,我们不希望别人看到我们的密码,那么就需要取消系统的回显功能。
回显功能的设置需要设置终端的本地模式,那么就需要了解一个结构体,termios结构体中保存了终端不同模式的成员变量,通过改变成员变量的值就可以改变终端的相关模式。termios结构体包含:

# include
struct termios{
      tcflag_t c_iflag;//输入模式
      tcflag_t c_oflag;//输出模式
      tcflag_t c_cflag;//控制模式
      tcflag_t c_lflag;//本地模式
      cc_t     c_cc[NCCS];//特殊控制字符
      }  

我们可以调用函数tcgetattr来初始化和当前终端对应的termios结构:

# include
int tcgetattr(int fd,struct termios*termios_p);//参数fd为终端的文件描述符,再传入temios结构体

这样会把当前终端接口变量的值写入termios_p参数指向的结构。
我们可以对termios结构体中的变量进行修改,修改过后需要再调用tcsetattr重新配置终端接口:

# include
int tcsetattr(int fd,int actions,const struct termios* termios_p);//文件描述符,修改方式,需要修改的结构体

常见的修改方式有三种:

变量 含义
TCSANOW 立刻对值进行修改
TCSADRAIN 等当前的输出完成后再对值进行修改
TCSAFLUSH 等当前输出完成后再对值进行修改,但丢弃还未从read调用返回的当前可用的任何输入

一定要注意,程序有责任将终端设置恢复到程序开始运行之前的状态,这一点是非常重要的,所以我们需要先保存termios结构体的值,最后再程序结束时恢复它们。

我们现在对本地模式进行不回显修改:不回显宏为

 ECHO:启动输入字符的本地回显功能
~ECHO:取消输入字符的本地回显功能

把termios结构体中的本地模式设置为不回显,然后进行重新配置,获得密码后,再将终端配置恢复,这个很重要,否则以后的输入都是不回显的了。
所以我们可以:

struct termios oldter,newter;//定义两个保存终端信息的两个结构体,oldter用来恢复,newter用来设置
tcgetatrr(0,&oldter);//oldter结构体获取到当前终端的模式信息
newter=oldter;//把信息给newter结构体
newter.c_lflag&=~ECHO;//进行本地模式的修改,修改为不回显
tcsetattr(0,TCSANON,&newter);//进行修改后的配置,立即修改
fgets(password,127,stdin);//获取不回显的信息
tcsetattr(0,TCSANON,&oldter);//恢复系统原来的回显功能

二、处理字符串函数知识储备

(一)分割字符函数strtok()

分割函数可以按照一定的标识将字符串切割,函数原型为:

# include
char* strtok(char s[],const char* delim);//s为要分解的字符串,delim为分割字符

返回值:从s开头开始一个个分割,当s中的字符查找到末尾是,返回NULL;如果找不到分割字符,则返回当前strtok的字符串指针。
首次调用时,s指向要分割的字符串,之后再次调用把s设为NULL,表示接着上次分割的位置继续分割。
如,将字符串“hello$world!"进行分割,分割字符保存在buff数组中:

char* s="hello$world!"
char* p=strtok(s,"$");
cout<

(二)初始化数组函数memset()

初始化数组,我们可以用memset,函数原型:

# include
void* memset(void*s,int ch,size_t n);//s为指定数组,ch为初始化数组的数值,n个字节
            返回替换好的指向数组的指针

如将buff数组初始化为0:

char buff[4];
memset(buff,0,sizeof(char*)4);

(三)判断字符存在函数strstr()

判断一个字符串是否存在于另一个字符串,用strstr,函数原型为:

string  strstr(string s1,string s2);

如果存在返回一个指针,指向s2在s1首次出现的位置。
如:s1=”hello",s2=“el”,判断s2是否是s1的子串:

char *s1=”hello”;
char *s2=”el”;
printf(“%sn\n.”strstr(s1, s2);

输出ello

(四)printf()函数的特殊控制

1. 输出带颜色的信息

要想输出带颜色的信息,printf函数需要遵循一定的格式:

printf("\033[字背景颜色;字体颜色m字符串\033[0m");
函数说明:
\033   表示后面的字符是转义字符,通知终端切换到转义模式,进行特殊处理;
[      标识命令序列的开始
m      制定将要执行的动作
33[0m  ANSI控制码,对屏幕进行控制,此处表示为关闭所有属性,只显示颜色

常见的字体背景颜色和字体颜色如下:

颜色 字背景颜色数值(40~49) 字颜色数值(30~39)
黑色 40 30
红色 41 31
绿色 42 32
黄色 43 33
蓝色 44 34
紫色 45 35
深绿 46 36
白色 47 37

输出红色的字体,白色背景的字符hello:

printf("\033[47;31mhello\033[0m");

2. 其余特殊控制

可以对ANSI控制码进行改变来达到不同的效果 ,常见的ANSI控制码如下:

控制码 含义
33[0m 关闭所有属性
33[1m 设置高亮度
33[4m 下划线
33[5m 闪烁
33[7m 反显
33[8m 消隐
33[30m – 33[37m 设置前景色
33[40m – 33[47m 设置背景色
33[nA 光标上移n行
33[nB 光标下移n行
33[nC 光标右移n行
33[nD 光标左移n行
33[y;xH 设置光标位置
33[2J 清屏
33[K 清除从光标到行尾的内容
33[s 保存光标位置
33[u 恢复光标位置
33[?251 隐藏光标
33[?25h 显示光标

加油哦!。

你可能感兴趣的:(项目介绍)