這篇文章是《動手寫 Shell》系列文章的第 <1> 篇,在這篇文章中,我們先完成一個 Shell 中最基本的功能 - Shell 提示符的實現。在這篇文章中,我會介紹一下實現的思路,以及介紹下用到的系統 API 和一些 C 語言中的庫函數。
Shell 提示符
用過 Linux 的人都知道當我們打開終端時,在命令行中會出現一行字,後邊會有光標在一直刪,那一行字就是 Shell 的提示符。
提示符格式
我們通常看到的 Shell 提示符的格式如下所示:
username@hostname:~/path$
我們要寫的 Shell 的第一步就是來實現這個東西。
實現思路
從提示符的格式中我們就知道我們首先需要得到:
- 用戶名
- 主機名
- 當前路徑
我們主要通過調用 Linux 系統 API 的方式來完成上述功能。
所需要的功能
0x00 得到當前用戶名
passwd 結構體
在 Linux 中定義了一個 passwd
結構體,該結構體定義了與用戶有關的信息,在 /usr/include/pwd.h
中
該結構體定義如下:
/* The passwd structure. */
struct passwd
{
char *pw_name; /* Username. */
char *pw_passwd; /* Password. */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
getpwuid() 與 getuid() 函數
- getuid(): 用來獲取當前用戶的 ID
- getpwuid(uid_t uid): 根據用戶 ID 來獲取
passwd
結構體
用法:
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
uid_t my_uid;
structpasswd *my_info;
my_info =getpwuid(getuid());
printf( "my name = [%s]\n", my_info->pw_name );
printf( "my passwd = [%s]\n", my_info->pw_passwd );
printf( "my uid = [%d]\n", my_info->pw_uid );
printf( "my gid = [%d]\n", my_info->pw_gid );
printf( "my gecos = [%s]\n", my_info->pw_gecos );
printf( "my dir = [%s]\n", my_info->pw_dir );
printf( "my shell = [%s]\n", my_info->pw_shell );
return0;
}
0x02 得到當前主機名
通過 gethostname()
函數我們可以獲得當前主機名
int max_name_len = 256;
char hostname[max_name_len];
gethostname(hostname, max_path_len);
0x03 獲取當前路徑
通過 getced()
函數我們可以獲得當前路徑
int max_path_len = 1024;
char pathname[max_path_len];
getcwd(pathname, max_path_len);
0x04 需要處理的其它問題
當前用戶目錄下的顯示
對於在當前目錄下的提示符我們採用將用戶主目錄用 ~
來代替,對於不在當前目錄下的提示符我們再使用完整的目錄來進行顯示,這也是目前 Ubuntu 中的默認終端所採取的提示符顯示格式。
實現策略:
- 如果當前目錄前面一部分同用戶主目錄路徑不相符,則顯示完整目錄
- 如果當前目錄的長度小於用戶主目錄路徑,則顯示完整目錄
- 其他情況,將當前目錄與用戶主目錄相同部分用
~
代替
實現方法:
- 獲取用戶主目錄:
我們通過訪問 passwd
結構體的方式來獲取用戶主目錄路徑:
char home_dir[1024];
pwd = getpwuid(getuid());
home_dir = pwd->pw_dir;
是否是 root 用戶
對於是 root 用戶的提示符我們將使用 #
來進行表示,對於其他用戶使用 $
來表示。
實現方法:
我們使用 getuid()
函數來判斷當前用戶是否是 root 用戶, 如果返回值爲 0,則是 root 用戶。
用到的C庫函數
sprintf()
功能
它的功能是把格式化的數據寫入某個字符串緩衝區。
頭文件
stdio.h
原型
int sprintf( char *buffer, const char *format, [ argument] … );
參數列表
- buffer: char型指針,指向將要寫入的字符串的緩衝區。
- format: 格式化字符串。
- [argument]…: 可選參數,可以是任何類型的數據。
strncmp()
功能
這個函數用來比較 s1 和 s2 字符串的前 num 個字符。如果兩個字符串相等的話,strncmp 將返回0。
頭文件
string.h
原型
int strncmp ( const char * str1, const char * str2, size_t num );
參數列表
- str1: 待比較字符串 1
- str2: 待比較字符串 2
- num: 比較的位數
參考代碼
下面貼上實現 Shell 提示符的代碼:
詳見:https://github.com/luoyhang003/linux_kernel_expriment/tree/master/exp2
/*
* prompt.c ---- Description
*------------------------------------------------------------
* Date: April 8th, 2016
* Copyright: Written by Jason Luo - [email protected]
* Function: Promption of the Shell
*------------------------------------------------------------
*/
#include"lshell.h"
const int max_name_len = 256;
const int max_path_len = 1024;
void get_prompt(char *prompt)
{
extern struct passwd *pwd;
char hostname[max_name_len];
char pathname[max_path_len];
int prompt_length;
pwd = getpwuid(getuid());
getcwd(pathname, max_path_len);
if(gethostname(hostname, max_path_len) == 0)
{
sprintf(prompt, "lshell>%s@%s:", pwd->pw_name, hostname);
}
else
{
sprintf(prompt, "lshell>%s@unknown:", pwd->pw_name);
}
prompt_length = strlen(prompt);
if(strlen(pathname) < strlen(pwd->pw_dir) || (strncmp(pathname, pwd->pw_dir, strlen(pwd->pw_dir))) != 0)
{
sprintf(prompt + prompt_length, "%s", pathname);
}
else
{
sprintf(prompt + prompt_length, "~%s", pathname + strlen(pwd->pw_dir));
}
prompt_length = strlen(prompt);
if(geteuid() != 0)
{
sprintf(prompt + prompt_length, "$");
}
else
{
sprintf(prompt + prompt_length, "#");
}
return;
}
本文的版權歸作者 羅遠航 所有,採用 Attribution-NonCommercial 3.0 License。任何人可以進行轉載、分享,但不可在未經允許的情況下用於商業用途;轉載請註明出處。感謝配合!