coreutil 代码选读:

coreutil 代码选读:
真正的读懂程序,也许你需要跨越读懂它们的宏,及其一些框架(支持多国语言)及getopt函数
那些东西,只是拦路虎,并不是核心东西,这里从略,为什么要分析,因为你懂得,就理直气壮。
所以要懂得多一些。

由易到难。
核心理解: 应用,源码级。C库以下不做分析
----------------------------------------
true  程序: exit(0)
----------------------------------------
false 程序: exit(1)
----------------------------------------
echo 程序:
如果是简单的字符串照印,就是fputs(argv[num], stdout);
如果带转义 -e 选项,支持8进制,16进制字符显示及控制字符,调用putchar(int c) 函数执行
----------------------------------------
tty 程序
它是调用ttyname(STDIN_FILENO) 得到字符串,然后puts 就结束了。
其中STDIN_FILENO 等于0
----------------------------------------
dirname 程序。
argv 传入了字符串,只要知道了dir_len, 调用fwrite(arg,1,len,stdout)就可以了。
那么dir_len 是怎么得到的?
过程稍微复杂点,为了支持不同的系统,首先判断是否有前缀,例如windows下z:, x:等,若有需要减盘符长度。
linux 下没有,最好了,
然后查找最后一个部件(last_component)指针,方法是,
从左到右依次遍历每一个字符,看看是否是slash,把最后一个slash 的地址返回。
最后一个slash 到起始位置之差,就是目录字符个数
----------------------------------------
basename 程序
它与dirname 是个配对程序,它支持多个目录同时打印basename(-a选项),
这很简单,做个循环就可以了,如下:
for (; optind < argc; optind++)
    perform_basename (argv[optind], suffix, use_nuls);
可以,对一个argv的处理是关键。
先查找last_component, 可能为空即不包含/(splash),那基指针也算找到。
再计算base_len, 基本上就是strlen(name)了, 有可能需要去掉盘符前缀之类的。
basename还支持去除后缀选项,(-s 选项)
做法是:用一个指针指向name 的尾部,一个指针指向suffix的尾部,依次比较,
相同则指针--, 不同了,name 就次截断返回。
最后,fputs(name,stdout)
----------------------------------------
env 程序
原来在调用你的程序时,就已经传递了环境变量,叫environ
environ 是一个空终结指针数组,只要用下句就可以遍历出所有环境变量
char *const *e = environ;
while (*e)
    printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
exit (EXIT_SUCCESS);

还有一个设置环境变量的选项,我不太关心,用到时再说。
----------------------------------------
yes 程序
就是不断的向屏幕输出你给的字符串,核心代码如下:
while (true)
{
  int i;
  for (i = optind; i < argc; i++)
    fputs (argv[i], stdout);
}
----------------------------------------
whoami 程序
先调用geteuid()得到uid, 在调用getpsuid(uid)得到passwd 结构的指针pw,
然后就可以输出了。
      puts (pw->pw_name);

----------------------------------------
uname 程序
uname 支持不少的选项,搞得眼花缭乱。
它的实现是调用了glibc提供的接口uname,
 #include <sys/utsname.h>
 int uname(struct utsname *buf);
调用该接口获取信息,根据命令行选项打印不同的成员变量
----------------------------------------
users 程序
1.前言
/var/run/utmp
该日志文件记录有关当前登录的每个用户的信息。
因此这个文件会随着用户登录和注销系 统而不断变化,
它只保留当时联机的用户记录,不会为用户保留永久的记录。
系统中需要查询当前用户状态的程序,如 who、w、users、finger等就需要访问这个文件。
该日志文件的记录不是百分之百值得信赖的 ,因为某些突发错误会终止用户登录会话,而系统没有及时 更新 utmp记录,
该日志文件是一个二进制文件,用cat,head 等程序无法直接观察.

user 程序调用了函数
read_utmp (filename, &n_users, &utmp_buf, options)
获取了n_users 及 utmp_buf 内容,然后根据其数据结构一一列出当前登录的用户信息。

read_utmp 的实现,是通过glic 提供的库函数接口,读取/var/run/utmp 文件来得到
 #include <utmp.h>
1. 调用glibc 函数 utmpxname(/var/run/utmp)
 void setutent(void); // rewind utmp file
 void endutent(void); // close utmp file
 struct utmp *getutent(void); // read a line from utmp file
 把读到的内容可以保存到一个列表里, 供以后使用
if (n_read == n_alloc)
  utmp = x2nrealloc (utmp, &n_alloc, sizeof *utmp);

utmp[n_read++] = *u;

----------------------------------------
who 程序:
查询活动用户在线状态。
也是通过read_utmp 函数,然后就可以直接列出用户状态了。

w 命令比who 列的消息还多, 代码还没看,估计也是访问这个文件
----------------------------------------
cksum 程序:
计算文件或一块数据的CRC, 下句即是核心算法。
把取来的每一个byte, 与crc表数据异或运算,这种数据移位累积结果为crc
while (bytes_read--)
    crc = (crc << 8) ^ crctab[((crc >> 24) ^ *cp++) & 0xFF];
还有一些细节,例如长度也计算在内,具体还是看代码。
----------------------------------------
tee 程序:
核心代码, 从标准输入读取数据,向标准输出和文件中写数据。
其中descriptors[0] 是stdout, 其它是命令行传递的文件名称对应的表述符。
每个文件描述符都是不带缓冲的,立即显示。
    bytes_read = read (0, buffer, sizeof buffer);
    for (i = 0; i <= nfiles; i++)
        if (descriptors[i]
            && fwrite (buffer, bytes_read, 1, descriptors[i]) != 1)
    {
        ...
    }
----------------------------------------
sleep 程序:
把输入的参数化成秒值, 然后调用nanosleep 去等。所以精度可以达到纳秒级。
----------------------------------------
kill 程序:
向一个进程发送信号,最常用的是kill -9 了
有一个宏,我特别的关照了一下:
#define NUMNAME(name) { SIG##name, #name }

static struct numname { int num; char const name[8]; } numname_table[] =
  {
#ifdef SIGHUP
    NUMNAME (HUP),
#endif
#ifdef SIGINT
    NUMNAME (INT),
#endif
    ....
  }

NUMNAME (HUP), 展开后为 { SIGHUM, "HUP" }
因为 ## 为连接符号, #为字符串化的意思,
进一步展开宏, 为 {1, "HUP"}
因为在/usr/include/bits/signum.h 定义了一批宏

#define    SIGHUP        1    /* Hangup (POSIX).  */
#define    SIGINT        2    /* Interrupt (ANSI).  */
...

所以
NUMNAME(INT) 展开为 { 2, "INT"}
余类推。正好构成一个number, 一个名称

kill 程序有两个功能,
一个列出信号,一个发送信号。
这个人的写法, case 中不带break, 所以一大堆case 连在一起执行,
好像习惯不太好!

列出信号,就是把numname_table 打印出来了。
发送信号, 调用的就是glibc 接口
 int kill(pid_t pid, int sig);
----------------------------------------
pwd 程序:
显示当前工作路径
pwd 支持-L(logical) -P(physical) 选项,
physical 的意思是如果所处的位置是在连接里,要打印它原始的位置。
例如: 在 /etc/init.d 目录下
[hjj@hjj /etc/init.d]$ pwd
/etc/init.d
[hjj@hjj /etc/init.d]$ pwd -L
/etc/init.d
[hjj@hjj /etc/init.d]$ pwd -P
/etc/rc.d/init.d
由此也可知/etc/init.d 只是/etc/rc.d/init.d 的一个连接,当然用ls 更明确
下面看看实现:
1. 逻辑工作路径
  char *wd = getenv ("PWD");
这个大体上就是逻辑工作路径了。

2. 物理工作路径
   char *cwd = getcwd (NULL, 0);
大体上就如此了。
若以上两种方法失效,强制获取当前工作路径,我们就先不关心了。


----------------------------------------
tr 程序: 用得较少,先不管
----------------------------------------
dircolors 程序:
不看代码还真是容易理解偏啊。
-p 参数,原来程序已经定义好一个大的0终结字符串组,依次输出字符串就可以了。
   以前我还以为是打印系统目前使用的color 设置呢。这个要用-b 参数
-b 参数. 输出shell code to set LS_COLORS
实现:
先判断一下shell 类型, 用
  shell = getenv ("SHELL");


----------------------------------------
head
tail
nl
cat
----------------------------------------
uniq
wc
seq
date
touch
du
df
mkdir
chmod
chgrp
rm
cp
ls
ln
mv
----------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值