作者:gzshun. 原创作品,转载请标明出处!
来源:http://blog.csdn.net/gzshun


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

1.使用方法

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

gzshun@ubuntu:~/c/getopt$ e2fsck -x
e2fsck: invalid option -- 'x'
Usage: e2fsck [-panyrcdfvtDFV] [-b superblock] [-B blocksize]
                [-I inode_buffer_blocks] [-P process_inode_size]
                [-l|-L bad_blocks_file] [-C fd] [-j external_journal]
                [-E extended-options] device

Emergency help:
 -p                   Automatic repair (no questions)
 -n                   Make no changes to the filesystem
 -y                   Assume "yes" to all questions
 -c                   Check for bad blocks and add them to the badblock list
 -f                   Force checking even if filesystem is marked clean
 -v                   Be verbose
 -b superblock        Use alternative superblock
 -B blocksize         Force blocksize when looking for superblock
 -j external_journal  Set location of the external journal
 -l bad_blocks_file   Add to badblocks list
 -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头文件中,有以下一些相关函数和全局变量:

#include <unistd.h>
extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;
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.

源程序:

/*****************************************************
** Name         : getopt.c 
** Author       : gzshun
** Version      : 1.0
** Date         : 2012-01
** Description  : getopt test program
**
** This file may be redistributed under the terms
** of the GNU Public License.
******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;
extern int getopt(int argc, char * const argv[], const char *optstring);

#define OPTION_A        (1<<0)
#define OPTION_B        (1<<1)
#define OPTION_C        (1<<2)
#define OPTION_D        (1<<3)
#define OPTION_E        (1<<4)
#define OPTION_F        (1<<5)
#define OPTION_G        (1<<6)
#define OPTION_H        (1<<7)

const char *programe_name = "getopt";

static void usage(void);
static int PRS(int argc, char **argv, int *opts);

static void usage(void)
{
	fprintf(stderr, 
		"Usage:  %s [OPTION]\n"
		"\t[-abcdeh]\n"
		"\t[-f digit] [-g string]\n",
		programe_name);
	exit(1);
}

static int PRS(int argc, char **argv, int *opts)
{
	int retval, prog_num;
	char ch;

	*opts &= 0x00000000;

	/*******************************************************
	optstring: "abcdef:g:h"
	这个字符串是给getopt指定需要的选项.
	abcdeh : 这几个选项后面没有跟选项参数
	f:g:   : 在一个选项的后面多一个:号,说明这个选项后面是有参数的.
	        这个选项参数可以通过optarg全局变量获取到.
	*******************************************************/
	while ((ch = getopt(argc, argv, "abcdef:g:h")) != -1) {
		switch (ch) {
			case 'a':
				*opts |= OPTION_A;
				break;
			case 'b':
				*opts |= OPTION_B;
				break;
			case 'c':
				*opts |= OPTION_C;
				break;
			case 'd':
				*opts |= OPTION_D;
				break;
			case 'e':
				*opts |= OPTION_E;
				break;
			case 'f':
				*opts |= OPTION_F;
				printf("-f optarg=%s\n", optarg);
				break;
			case 'g':
				*opts |= OPTION_G;
				printf("-g optarg=%s\n", optarg);
				break;
			case 'h':
				*opts |= OPTION_H;
				usage();
				break;
			default:
				fprintf(stderr, 
						"our: %s: invalid option -- '%c'\n",
						programe_name, optopt);
				exit(1);
				break;
		}
	}

	return 0;
}

int main(int argc ,char **argv)
{
	int i, retval;
	int opts;

	/*调用getopt之前打印argv数组*/
	printf("Before calling getopt\n");
	for (i = 0; i < argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n\n");

	/**********************************************************
	该函数是编写一个Linux命令最基本也最重要的一个操作,函数里面
	调用getopt()函数,函数作用是用来解析命令的操作选项:
	比如:cp -a -r src dst
	getopt()用来分析参数选项,具体可以参考:
	(UNIX环境高级编程 第21章 与网络打印机通信 p619)
	***********************************************************/
	retval = PRS(argc, argv, &opts);
	if (retval < 0) {
		exit(1);
	}
	printf("\n");

	/*调用getopt之后打印argv数组*/
	printf("After calling getopt\n");
	for (i = 0; i < argc; i++) {
		printf("%s ", argv[i]);
	}
	printf("\n\n");

	/*这里来打印一下选项的设置情况*/
	printf("-a \t set %s\n", opts&OPTION_A ? "yes" : "no");
	printf("-b \t set %s\n", opts&OPTION_B ? "yes" : "no");
	printf("-c \t set %s\n", opts&OPTION_C ? "yes" : "no");
	printf("-d \t set %s\n", opts&OPTION_D ? "yes" : "no");
	printf("-e \t set %s\n", opts&OPTION_E ? "yes" : "no");
	printf("-f \t set %s\n", opts&OPTION_F ? "yes" : "no");
	printf("-g \t set %s\n", opts&OPTION_G ? "yes" : "no");
	printf("-h \t set %s\n", opts&OPTION_H ? "yes" : "no");
	printf("\n");
	
	/**********************************************************
	当执行完getopt后,argv二位数组里面的字符串已经被排序,选项部分
	会被排在前面,非选项部分的会被排在最后面,但原来的顺序还是没有
	发生变化;
	为什么要加这个循环,因为在很多情况都要另外加一些参数,比如:
	cp -a -r src1 src2 src3 src4 dst
	这条复制命令, -a -r是选项, src[1-4] dst这几个就是其余的参数,
	若需要这些,使用以下循环即可获得.
	***********************************************************/
	for (i = optind; i < argc; i++) {
		printf("optind=%d\t: argv[%d]=%s\n", i, i, argv[i]);
	}

	return 0;
}

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

gzshun@ubuntu:~/c/getopt$ ./getopt china -a beijing -b shanghai -c shenzhen -d -f 1234 -g hello
Before calling getopt
./getopt china -a beijing -b shanghai -c shenzhen -d -f 1234 -g hello

-f optarg=1234
-g optarg=hello

After calling getopt
./getopt -a -b -c -d -f 1234 -g hello china beijing shanghai shenzhen

-a       set yes
-b       set yes
-c       set yes
-d       set yes
-e       set no
-f       set yes
-g       set yes
-h       set no

optind=9        : argv[9]=china
optind=10       : argv[10]=beijing
optind=11       : argv[11]=shanghai
optind=12       : argv[12]=shenzhen

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

gzshun@ubuntu:~/c/getopt$ ./getopt -h
Before calling getopt
./getopt -h

Usage:  getopt [OPTION]
        [-abcdeh]
        [-f digit] [-g string]


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



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

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐