文章目錄:
一、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<sys/types.h>
# include<unistd.h>
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<sys/types.h>
# include<pwd.h>
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<sys/utsname.h>
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 <unistd.h>
int chdir(const char* path);
成功返回0,失敗返回-1
程序可以通過調用函數獲取自己當前的絕對工作目錄,函數getcwd
功能和命令pwd一樣:
# include <unistd.h>
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<sys/types.h>
# include<dirent.h>
DIR* opendir(const char* name);//傳入路徑
成功返回一個指向DIR結構的指針,該指針用於讀取目錄數據項,失敗返回一個空指針
當目錄流使用一個底層文件描述符來訪問文件本身,那麼如果打開文件過多,opendir可能會失敗。
2.readdir
:用於讀取已打開目錄中的目錄項相關信息 :
# include <sys/types.h>
# include<dirent.h>
struct dirent* readdir(DIR *dirp);
成功返回一個指向存儲目錄項結構體dirent的指針,有錯誤發生或讀取到目錄文件尾則返回NULL
結構體dirent
中包含的目錄項內容包含以下部分:
成員 | 含義 |
---|---|
ino_t d_ino | 文件的inode節點號 |
char d_name[] | 文件的名字 |
我們可以通過得到的指針,進行結構體成員的訪問,如dir->d_name可以獲取目錄下的文件名。
注意,如果在readdir函數掃描目錄的同時還有其他進程在該目錄裏創建或刪除文件,readdir將不保證能夠列出該目錄裏的所有文件和子目錄。
3.closedir
:用於關閉一個目錄流並釋放與之關聯的資源:
# include<sys/types.h>
# include<dirent.h>
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);//輸出文件名
(四)獲取文件屬性,類型,權限
- 文件屬性的獲取可以利用函數
stat,lstat,fstat
獲取; - 文件類型可以用<sts/stat.h>頭文件提供的函數,根據文件類型宏判斷:
文件類型宏(st_mode) //判斷是否爲這個類型的文件,是返回1,否則返回0
- 文件權限可以利用:
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<unistd.h>
int setuid(uid_t uid);
執行成功返回0,失敗返回-1
setuid函數的執行分爲以下幾個方面:
- 如果由普通用戶調用,將當前進程的有效ID設置爲uid。
- 如果是由管理員(uid爲0)調用,將真實,有效,保存的uid都設置新的uid。那麼root用戶就完全替換爲該用戶,風險很大。
- 如果進程沒有超級用戶權限,那麼只將有效用戶id設置爲uid,即以uid的權限運行該進程。
- 如果進程有超級用戶權限,那麼將真實,有效,保存用戶id都設置爲uid,那麼運行該進程的用戶完全改變。
(六)獲取當前用戶密碼
用戶密碼爲了安全起見,都在一個稱爲陰影口令的文件/etcshadow中存放着,裏面的密碼都是加密過的密碼,那麼我們如何獲取到shadow文件中加密後的密碼呢?需要用到getspnam
函數:
# include<shadow.h>
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<termios.h>
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<termios.h>
int tcgetattr(int fd,struct termios*termios_p);//參數fd爲終端的文件描述符,再傳入temios結構體
這樣會把當前終端接口變量的值寫入termios_p參數指向的結構。
我們可以對termios結構體中的變量進行修改,修改過後需要再調用tcsetattr
重新配置終端接口:
# include<termios.h>
int tcsetattr(int fd,int actions,const struct termios* termios_p);//文件描述符,修改方式,需要修改的結構體
常見的修改方式有三種:
變量 | 含義 |
---|---|
TCSANON | 立刻對值進行修改 |
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<string.h>
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<<p;
p=strtok(NULL,"$");
cout<<p:
(二)初始化數組函數memset()
初始化數組,我們可以用memset
,函數原型:
# include<memory.h>
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 | 顯示光標 |
加油哦!💪。