如何编写一个完整的Linux命令

来源:互联网 发布:大数据综合服务平台 编辑:程序博客网 时间:2024/05/21 09:19

一个完整的Linux命令需要有以下几个重要的部分组成:
1.使用方法
2.命令行参数
3.移植性

1.使用方法

在每个命令当中,都需要提供一个usage函数,当然名称不一定要用这个。看了很多开源软件,几乎都是使用usage命名。usage一般是在用户输入不规则的命令行参数才调用的,也就是打印出详细的使用方法。比如我以下随便给一个Linux命令传入一个没有被提供的参数,执行结果是这样:

[plain] view plaincopy
  1. gzshun@ubuntu:~/c/getopt$ e2fsck -x  
  2. e2fsck: invalid option -- 'x'  
  3. Usage: e2fsck [-panyrcdfvtDFV] [-b superblock] [-B blocksize]  
  4.                 [-I inode_buffer_blocks] [-P process_inode_size]  
  5.                 [-l|-L bad_blocks_file] [-C fd] [-j external_journal]  
  6.                 [-E extended-options] device  
  7.   
  8. Emergency help:  
  9.  -p                   Automatic repair (no questions)  
  10.  -n                   Make no changes to the filesystem  
  11.  -y                   Assume "yes" to all questions  
  12.  -c                   Check for bad blocks and add them to the badblock list  
  13.  -f                   Force checking even if filesystem is marked clean  
  14.  -v                   Be verbose  
  15.  -b superblock        Use alternative superblock  
  16.  -B blocksize         Force blocksize when looking for superblock  
  17.  -j external_journal  Set location of the external journal  
  18.  -l bad_blocks_file   Add to badblocks list  
  19.  -L bad_blocks_file   Set badblocks list  

这里每一个命令行选项都代表一个功能,有些选项后面可以跟着参数,比如e2fsck中[-b superblock] [-B blocksize]等等。

2.命令行参数
在第1点,已经贴出usage的打印信息,上面那些-p,-n等等的就是命令行参数,本文也是重点说明这个命令行参数的使用。在执行一个Linux命令的时候,可能只需要增加一个选项(比如: cp -a ...),也可能需要在一个选项后面跟一个参数(比如: e2fsck -C0 或者 e2fsck -C 0),其实"e2fsck -C0"与"e2fsck -C 0"是等价的。这里分析命令行参数的功臣归功于"getopt"函数,getopt()用来分析参数选项,具体可以参考:(UNIX环境高级编程 第21章 与网络打印机通信 p619),当然在UNIX环境高级编程这本书里面,这个函数只是稍微带过,没有详细讲解。

3.移植性
通常一个开源软件的移植性都是考虑得相当周全,在Linux平台下的开源软件,经常被移植到不同的平台,这就必须考虑到不同平台的可移植性。当然我对软件的可移植性这方面的知识欠缺,有待改进,本文不对移植性解释。

一、为什么要写一个完整的Linux命令?
在Linux的平台下,很多重要的功能都是在命令行实现的,也许我们平时也需要自己动手实现一个自己的程序,当然要一个相对比较有质量的。这里就会涉及到一个Linux命令的设计,第一步肯定是要分析命令行参数选项。
自己动手写一个完整的Linux命令,这里的标题写得有点吹,本来我打算写一个相对比较完整的cp命令,但考虑下流程,处理的东西很多,包括文件,目录的属性,还有递归,去掉上班时间,我晚上时间有限,就不想写,那些也都是比较容易的问题。cp这一个命令对于Linux的用户再熟悉不过了,网上也提供很多程序员自己写的cp命令,但我觉得都不完整,因为网上有些cp程序都是直接读取源文件写到目标文件,仅仅写了一个while循环,这样根本就谈不上一个完整的Linux命令。我们平时使用的cp命令,源码有1063行,包括注释,一个简单的功能,其实涉及到很多Linux平台下的特性,需要考虑到文件属性,目录属性,权限,用户和组ID,修改时间,状态修改时间等等。

二、Linux命令的一些例子?
举个例子:
复制一个src目录到dst目录的命令,这里可能会有3种写法:
1.cp -a src dst
2.cp src -a dst
3.cp src dst -a
这3条命令都可以达到将src目录拷贝到dst目录的效果,这里就是命令行参数的功劳了,这也就是getopt函数的好处。如果写一个命令,没有使用getopt函数来处理命令行参数,那将处理不了这三种命令行参数的写法。

例子:
检测磁盘的一个工具e2fsck,该函数有一个选项是[-C fd],后面跟一个参数,这个选项是选择一个不同的进度打印消息。这里同样有几种写法:
1.e2fsck /dev/sda1 -C0
2.e2fsck /dev/sda1 -C 0
在命令行参数,也支持这样的形式,选项可以跟选项后面跟的参数写在一起,也可以分离,但必须紧跟在选项后面。

当然本文也写不出完整的Linux命令,水平有限,只是写出命令行参数的解析部分。

例子:这里是一个简单的测试程序,将程序的参数打印出来;
./cp aa bb cc dd ee -l -d

//这里直接将main函数的argv二位数组打印出来
argv[0]=./cp
argv[1]=aa
argv[2]=bb
argv[3]=cc
argv[4]=dd
argv[5]=ee
argv[6]=-l
argv[7]=-d
//这里是调用完getopt后argv的变化结果
argv[0]=./cp
argv[1]=-l
argv[2]=-d
argv[3]=aa
argv[4]=bb
argv[5]=cc
argv[6]=dd
argv[7]=ee

从这个例子,可以得到以下结果:
1.getopt函数具有排序功能;
2.argv是一个可以修改的二维数组;

三、getopt的使用?
1.排序功能
getopt会先将命令行参数进行排序,选项在前,剩下的参数在后面,第0个参数始终是程序自己;
排序规则:
只将有"-"选项与该选项的参数提到程序的后面,剩下的一些都放在最后面,但原有的参数顺序没有更改,这里有点拗口,直接看例子:
cp -a -r src1/ src2/ src3/ dst/
这条命令的任务是将src1,src2,src3目录拷贝到dst目录下,src1,src2,src3参数必须在dst参数的前面,所以getopt不会破坏原来的参数顺序,例子:
cp src1/ src2/ -a src3/ -r dst/
经过getopt的处理,argv数组将会变成cp -a -r src1/ src2/ src3/ dst/,-a和-r原有的顺序也保持不变,-a在前,-r在后。

2.参数解析
有些命令的选项后面会跟着一个参数,可以是数字,也可以是字母,或字符串,均可。例子:
e2fsck /dev/sda1 -C0,这里-C选项的参数是0。也可以写成e2fsck /dev/sda1 -C 0,跟前面那句命令等价。
  
3.getopt的具体用法
讲这么废话,终于到了getopt了。
getopt函数声明在unistd.h头文件中,有以下一些相关函数和全局变量:

[cpp] view plaincopy
  1. #include <unistd.h>  
  2. extern char *optarg;  
  3. extern int optind;  
  4. extern int opterr;  
  5. extern int optopt;  
  6. extern int getopt(int argc, char * const argv[], const char *optstring);  

这里写个例子来说明(虚拟的): mycp -a -b hello -c123 src dst
char *optarg: -b的optarg是hello,-c的optarg是123,optarg就是选项后面跟着的参数;
int optind  : 下一个要处理的参数的下标(argv);
int opterr  : 如果将opterr设为0,则getopt不输出错误信息,否则报错,形如(e2fsck: invalid option -- 'x')
int optopt  : 当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,该选项存储在optopt中
int getopt(int argc, char * const argv[], const char *optstring) : optstring看起来比较不清楚,待会儿看代码。该函数执行完或者失败返回-1.

源程序:

[cpp] view plaincopy
  1. /***************************************************** 
  2. ** Name         : getopt.c  
  3. ** Author       : gzshun 
  4. ** Version      : 1.0 
  5. ** Date         : 2012-01 
  6. ** Description  : getopt test program 
  7. ** 
  8. ** This file may be redistributed under the terms 
  9. ** of the GNU Public License. 
  10. ******************************************************/  
  11. #include <stdio.h>  
  12. #include <stdlib.h>  
  13. #include <unistd.h>  
  14.   
  15. extern char *optarg;  
  16. extern int optind;  
  17. extern int opterr;  
  18. extern int optopt;  
  19. extern int getopt(int argc, char * const argv[], const char *optstring);  
  20.   
  21. #define OPTION_A        (1<<0)  
  22. #define OPTION_B        (1<<1)  
  23. #define OPTION_C        (1<<2)  
  24. #define OPTION_D        (1<<3)  
  25. #define OPTION_E        (1<<4)  
  26. #define OPTION_F        (1<<5)  
  27. #define OPTION_G        (1<<6)  
  28. #define OPTION_H        (1<<7)  
  29.   
  30. const char *programe_name = "getopt";  
  31.   
  32. static void usage(void);  
  33. static int PRS(int argc, char **argv, int *opts);  
  34.   
  35. static void usage(void)  
  36. {  
  37.     fprintf(stderr,   
  38.         "Usage:  %s [OPTION]\n"  
  39.         "\t[-abcdeh]\n"  
  40.         "\t[-f digit] [-g string]\n",  
  41.         programe_name);  
  42.     exit(1);  
  43. }  
  44.   
  45. static int PRS(int argc, char **argv, int *opts)  
  46. {  
  47.     int retval, prog_num;  
  48.     char ch;  
  49.   
  50.     *opts &= 0x00000000;  
  51.   
  52.     /******************************************************* 
  53.     optstring: "abcdef:g:h" 
  54.     这个字符串是给getopt指定需要的选项. 
  55.     abcdeh : 这几个选项后面没有跟选项参数 
  56.     f:g:   : 在一个选项的后面多一个:号,说明这个选项后面是有参数的. 
  57.             这个选项参数可以通过optarg全局变量获取到. 
  58.     *******************************************************/  
  59.     while ((ch = getopt(argc, argv, "abcdef:g:h")) != -1) {  
  60.         switch (ch) {  
  61.             case 'a':  
  62.                 *opts |= OPTION_A;  
  63.                 break;  
  64.             case 'b':  
  65.                 *opts |= OPTION_B;  
  66.                 break;  
  67.             case 'c':  
  68.                 *opts |= OPTION_C;  
  69.                 break;  
  70.             case 'd':  
  71.                 *opts |= OPTION_D;  
  72.                 break;  
  73.             case 'e':  
  74.                 *opts |= OPTION_E;  
  75.                 break;  
  76.             case 'f':  
  77.                 *opts |= OPTION_F;  
  78.                 printf("-f optarg=%s\n", optarg);  
  79.                 break;  
  80.             case 'g':  
  81.                 *opts |= OPTION_G;  
  82.                 printf("-g optarg=%s\n", optarg);  
  83.                 break;  
  84.             case 'h':  
  85.                 *opts |= OPTION_H;  
  86.                 usage();  
  87.                 break;  
  88.             default:  
  89.                 fprintf(stderr,   
  90.                         "our: %s: invalid option -- '%c'\n",  
  91.                         programe_name, optopt);  
  92.                 exit(1);  
  93.                 break;  
  94.         }  
  95.     }  
  96.   
  97.     return 0;  
  98. }  
  99.   
  100. int main(int argc ,char **argv)  
  101. {  
  102.     int i, retval;  
  103.     int opts;  
  104.   
  105.     /*调用getopt之前打印argv数组*/  
  106.     printf("Before calling getopt\n");  
  107.     for (i = 0; i < argc; i++) {  
  108.         printf("%s ", argv[i]);  
  109.     }  
  110.     printf("\n\n");  
  111.   
  112.     /********************************************************** 
  113.     该函数是编写一个Linux命令最基本也最重要的一个操作,函数里面 
  114.     调用getopt()函数,函数作用是用来解析命令的操作选项: 
  115.     比如:cp -a -r src dst 
  116.     getopt()用来分析参数选项,具体可以参考: 
  117.     (UNIX环境高级编程 第21章 与网络打印机通信 p619) 
  118.     ***********************************************************/  
  119.     retval = PRS(argc, argv, &opts);  
  120.     if (retval < 0) {  
  121.         exit(1);  
  122.     }  
  123.     printf("\n");  
  124.   
  125.     /*调用getopt之后打印argv数组*/  
  126.     printf("After calling getopt\n");  
  127.     for (i = 0; i < argc; i++) {  
  128.         printf("%s ", argv[i]);  
  129.     }  
  130.     printf("\n\n");  
  131.   
  132.     /*这里来打印一下选项的设置情况*/  
  133.     printf("-a \t set %s\n", opts&OPTION_A ? "yes" : "no");  
  134.     printf("-b \t set %s\n", opts&OPTION_B ? "yes" : "no");  
  135.     printf("-c \t set %s\n", opts&OPTION_C ? "yes" : "no");  
  136.     printf("-d \t set %s\n", opts&OPTION_D ? "yes" : "no");  
  137.     printf("-e \t set %s\n", opts&OPTION_E ? "yes" : "no");  
  138.     printf("-f \t set %s\n", opts&OPTION_F ? "yes" : "no");  
  139.     printf("-g \t set %s\n", opts&OPTION_G ? "yes" : "no");  
  140.     printf("-h \t set %s\n", opts&OPTION_H ? "yes" : "no");  
  141.     printf("\n");  
  142.       
  143.     /********************************************************** 
  144.     当执行完getopt后,argv二位数组里面的字符串已经被排序,选项部分 
  145.     会被排在前面,非选项部分的会被排在最后面,但原来的顺序还是没有 
  146.     发生变化; 
  147.     为什么要加这个循环,因为在很多情况都要另外加一些参数,比如: 
  148.     cp -a -r src1 src2 src3 src4 dst 
  149.     这条复制命令, -a -r是选项, src[1-4] dst这几个就是其余的参数, 
  150.     若需要这些,使用以下循环即可获得. 
  151.     ***********************************************************/  
  152.     for (i = optind; i < argc; i++) {  
  153.         printf("optind=%d\t: argv[%d]=%s\n", i, i, argv[i]);  
  154.     }  
  155.   
  156.     return 0;  
  157. }  

程序的执行结果1: 
这里故意打乱顺序,为了更清楚的查看getopt的使用方法

[plain] view plaincopy
  1. gzshun@ubuntu:~/c/getopt$ ./getopt china -a beijing -b shanghai -c shenzhen -d -f 1234 -g hello  
  2. Before calling getopt  
  3. ./getopt china -a beijing -b shanghai -c shenzhen -d -f 1234 -g hello  
  4.   
  5. -f optarg=1234  
  6. -g optarg=hello  
  7.   
  8. After calling getopt  
  9. ./getopt -a -b -c -d -f 1234 -g hello china beijing shanghai shenzhen  
  10.   
  11. -a       set yes  
  12. -b       set yes  
  13. -c       set yes  
  14. -d       set yes  
  15. -e       set no  
  16. -f       set yes  
  17. -g       set yes  
  18. -h       set no  
  19.   
  20. optind=9        : argv[9]=china  
  21. optind=10       : argv[10]=beijing  
  22. optind=11       : argv[11]=shanghai  
  23. optind=12       : argv[12]=shenzhen  

程序的执行结果2: 
使用-h选项来打印命令的使用方法

[plain] view plaincopy
  1. gzshun@ubuntu:~/c/getopt$ ./getopt -h  
  2. Before calling getopt  
  3. ./getopt -h  
  4.   
  5. Usage:  getopt [OPTION]  
  6.         [-abcdeh]  
  7.         [-f digit] [-g string]  


个人喜欢vi的颜色配置,贴一张出来看看,挺鲜艳:



本人不才,以上可能存在错误的认识。
由于最近写了3个Linux命令,在CSDN博客总结一下收获,虽然这种程序是菜鸟级的,但我把它记录下来,等我下次要查看,一目了然,这就是效率。希望大牛不要鄙视,我写这种无聊的程序,只是一个学习态度罢了。
0 0
原创粉丝点击