存档在 2011年1月

typedef那回事儿

2011年1月30日

typedef是一种特殊的声明方式,不过它与普通声明(详见这里)的含义取大不相同。普通声明的主角是“变量”,它或是创建一个新变量或是对外文件变量使用前的声明;而typedef声明的主角则是“类型”,通过这个声明对一种数据类型引入新的名字。从引入新名字这个角度来说,typedef声明又和宏定义有些相似:用新名字代替已有的名字。不过,在本文接下来的叙述中你会看到这两者之间的区别。

typedef是特殊的

正如本文一开始所说的那样,typedef是特殊的声明。我们最常见以及常用的方式如下:

/* 代码段1 */
struct stuinfo
{
	char id[20];
	char name[20];
	int age;
};
typedef struct stuinfo stu; /* 语句1 */

通过typedef声明为stuinfo结构体引入了一个新的名字stu。现在stuinfo结构和stu属于同一种数据类型,只不过两者在声明一个变量时使用的名字不同:

/* 代码段2 */
stu mystu1;
struct stuinfo mystu2; /* 语句2 */

通过上述两个代码段,可以再一次的理解typedef声明和普通声明的区别。代码段1通过typedef声明为stuinfo引入了一个新的名字stu;而代码段2则通过同一种数据类型的不同名称分别声明了两个同类型的变量。注意到语句1和语句2,除了语句1在声明前多了typedef关键字外,两者在形式上几乎一样,因此都可以通过上文所述的声明规则进行阅读。正是由于typedef这个关键字,使得这两种声明的含义有着巨大差异。

其实,像上面的举例那样通过typedef声明而省去一个struct并没有多大的意义。使用typedef声明的最大优点是可以简洁的表达一个指针。比如ANSI C中的signal(),它的定义如下:

void ( *signal(int signum, void (*handler)(int)) ) (int);

考验你的时刻到了!你是否能快速说出这个声明的含义?

这个复杂的语句声明了signal函数,这个函数有两个参数signum和handler;signum参数是一个整型变量。handler是一个函数指针,指向一个拥有整型参数并且返回空值的函数;signal函数的返回值是一个函数指针,该指针同样指向一个拥有整型参数并且返回空值的函数。

对于这个复杂声明的解读的确很令人费劲。但是经过typedef的改进,它的阅读过程就简化了很多:

 typedef void (*sighandler_t)(int);
 sighandler_t signal(int signum, sighandler_t handler);

通过typedef的声明,使得sighandler_t是这样一种类型:它是一个函数指针,该函数拥有一个整型参数并且返回空值。第二条语句则声明了函数signal,它拥有两个参数signum和handler。并且这个函数的返回值和参数handler都是sighandler_t类型的。

虽然这样的声明在形式上简洁许多,不过和普通声明一样,此时阅读声明时仍然要记住声明符号的优先级规则。这种困扰在C语言中是难以避免的。

typedef int x和#define x int是不一样的

typedef和宏定义看似都是文本替换,但其实质不同。typedef表面上是对已有数据类型引入新名称,实则是对数据类型的严格封装。这种封装体现在下述两个方面。

首先,经过宏定义后的类型名可以进行再次扩展,但是经过typedef引入的类型名则不能进行扩充。比如:

#define myint1 int
unsigned myint1 x; /* 正确 */

typedef int myint2;
unsigned myint2 x; /* 语法错误! */

由于typedef是一种严格的数据封装,它只引入了myint2类型而没有引入unsigned myint2类型。也就是说,通过typedef的声明,编译器只能识别myint2类型。而unsigned myint2既不是基本类型也不是经过typedef声明过的类型,编译器就无法识别。

其次,在连续的几个变量声明中,使用typedef定义的类型能够保证所有变量均为相同类型,而用宏定义的变量则无法保证统一性。比如:

#define myint1 int *
myint1 x,y; /* 经过宏替换后为: int *x,y; */

typedef int * myint2;
myint2 x,y;

由于宏定义只是直接的文本替换,因此只能保证x是整型的指针变量而y为整型变量。而typedef定义过的类型myint2则是对int *的完全封装,所以x和y均为整型的指针变量。

C语言中的名字空间

在说明名字空间之前,请先阅读下面的代码:

/*
 *Author: edsionte
 *Email:  edsionte@gmail.com
 *Time:   2011/02/03
 */

#include < stdio.h >
#include < string.h >

struct id
{
	int id;
}id;

typedef struct name
{
	char name[20];
}name;

struct name name1;
name name2;

int main()
{
	id.id = 1;
	strcpy(name1.name,"hello,");
	strcpy(name2.name,"edsionte!");
	printf("id.id = %d, %s%s\n",id.id,name1.name,name2.name);
	return 0;
}
/* 运行结果 */
edsionte@edsionte-laptop:~/code/expertC$ gcc tpdef.c -o tpdef
edsionte@edsionte-laptop:~/code/expertC$ ./tpdef
id.id = 1, hello,edsionte!

你可能已经发现在上述代码中出现了多个id和name,并且这样的代码可以成功的编译。这些相同的名字标签为何可以同时出现?每个标签代表什么含义?这些问题将是下面分析的重点。

以上述代码中10到13行的代码为例,这条语句中包含三个id标签。它们分别对应C语言中三种常见的名字空间:

  • 结构标签:这种标签用于结构体、联合体和枚举类型;struct后的id即为此类型的名字空间;
  • 成员名:每个结构体或联合体内部都与属于自己的名字空间;struct内部的成员id即为此类型;
  • 标签名:声明中的标示符;比如最后一个id,他是struct id类型的变量;

由于这三种标签所处的名字空间不同,因此它们可以同时存在。但是在同一个名字空间中不能出现多个同名的标签。常见的例子就是一个结构体内不可能出现同名的变量。

根据上面对名字空间的划分,15到18行的代码的解释为:struct后的name属于结构体标签;结构体内部的name属于成员名;而最后一个name属于声明的标示符;整条语句的含义是通过typedef声明将name结构体重命名为name。

通过上面对typedef的分析,你应该对于struct name和name均可以声明一个变量不再陌生。此处我们用名字空间的来理解他们的区别,20句中的name属于结构标签,21句中的name属于一种类型的名称。

上述同名的情况在日常的代码中实属罕见,这里只是为了说明名字空间而特别的举例。一般为了提高代码的可阅读性,最好对容易产生混淆的标签加上特别标记。比如VFS中inode和dentry结构体,两者内部均有flag一字段。尽管不同的结构体内有各自的名字空间,但是实际命名时仍然采用i_flag和d_flag。

有了上面的基础,本部分一开始所举例的代码也就可以轻松阅读了。

参考:

《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;

声明那回事儿

2011年1月29日

C语言中的变量声明是让程序员比较苦恼的一件事,因为过多的优先级规则使得阅读声明并不能像自然方式那样从左至右的阅读。比如下面这个声明:

int (*(*fun)())();

对于这个声明,你能准确说出它的含义吗?这个声明涉及到本文的两大主题:什么是声明和声明的阅读规则。本文的最后将给出这个声明的准确含义。

声明和定义

在C语言中,提到声明就不得不提到定义。这里说的声明既包含变量的声明又包含函数的声明。对于函数的定义和声明易于理解,比如:

int myfun2();

int myfun2()
{
	printf("I am myfun2~\n");
	return 0;
}

所谓函数的定义就是对该函数进行具体的功能实现,而函数声明则是对该函数返回值和参数类型的说明,使得其他函数感知到这个函数的存在,以便在需要时直接调用该函数。

变量的声明具体分为两种情况:定义型声明和外部引用型声明。

定义型声明其实就等价于定义。C语言的定义是指为变量分配内存空间,并在需要时为其赋一个初值。定义型声明用于创建一个新的变量,它在定义这个变量的同时也声明了这个变量。比如下述代码就声明(定义)了一个变量num,并将0作为其初值。

int num = 0;

使用外部引用型声明是由于要在当前程序中使用定义在其他文件中的变量。也就是说这个变量是已存在的,因此这种声明并不包含变量的定义。比如:

file1:
int a = 100;
file2:
extern int a;

C语言中的变量只能有一个定义,但是它却可以有多个extern的声明。因为extern声明并不分配内存空间,只是告诉引用这个变量的函数:这个变量已经定义过了,你可以直接的使用。

声明的组成

声明确定了变量的基本类型和相应的初值(如果需要的话)。一个完整的声明包括三部分:一个类型说明符,一个或多个声明器(declarator)和一个分号。

类型说明符用于描述所要声明变量的类型;分号说明了声明的结束;声明器是标示符以及和它组合在一起的指针符、函数括号和数组下标等,有时候也将初始化内容放在声明器中。多个声明器用逗号隔开。关于声明和声明器的关系可参考下图:

该图所示的语句声明了四个变量,其基本类型都是整型。由于四个变量分别对应着不同的声明器,则最终的变量类型就有所不同。整型变量a的声明器即为标示符a;第二个声明器为*b=NULL,它声明b是一个指针变量,其初值为NULL。由于类型说明符为int,则说明了b是一个指向整型的指针;第三个声明器为(*c)[20],它声明c是一个数组指针,该指针指向拥有20个元素的数组。由类型说明符得知该数组的元素都是整型的;最后一个声明器为*j[20],其说明j是一个指针数组,该数组有20个元素,每个元素都是指向整型变量的指针。

优先级规则

了解了声明的组成后,到了该给出声明优先级规则的时候了。C语言中声明的优先级规则如下:

1.声明从最左的标示符开始,然后按照下面的优先级规则依次读取;

2.具体的优先级为:

  • 2.1 被括号括起来的那部分;
  • 2.2 后缀部分;如果后缀为( ),表明这是一个函数;如果后缀为[ ],表明这是一个数组;
  • 2.3 前缀部分;*表示指向…的指针;

3.如果const或volatile关键字后紧邻基本类型说明符,则它作用于该类型的变量;否则,const和volatile作用于仅靠它左边的星号,即作用于指针变量;

上述规则需要参考实际的声明来慢慢理解。下面通过两个简单的声明举例来说明上述优先级规则:

const int (*p)();
const int *p();

声明1:

  • 首先找到标示符p,由于声明1中的p和*被包括起来,根据规则2.1得知p是一个指针。该指针指向什么类型是接下来读声明的关注对象;
  • 标示符的后缀部分是( ),根据规则2.2得知该指针指向一个函数。这个函数返回值是什么类型是接下来读声明的关注对象;
  • 读完上述声明符号后,剩下了const int,根据规则3得知这个函数的返回值是一个只读型的整数;

综合上面的几部分可得知该声明的含义:p是一个指针,它指向一个函数。这个函数没有参数,返回一个只读型的整数。

声明2:

  • 首先找到标示符p,它与*没有被包括在一起,因此可以排除p是一个指针;
  • 由于标示符的后缀部分是( ),根据规则2.2得知p是一个函数。该函数返回什么类型是接下来读声明的关注对象;
  • 由于p标示符前有*,则说明该函数的返回值是一个指针。至于该指针指向什么样的数据是接下来对声明的关注对象;
  • 通过队则3可得知该指针指向一个只读型的整型变量;

通过上述的分步分析可得知该声明的含义:p是一个函数,它没有参数,返回值是一个指针。该指针指向一个只读型的整型变量。

通过上述的优先级规则,可以轻松的阅读任何一个声明。从上述两个举例中也可总结出阅读声明的大致方法,首先判别该声明是声明一个函数还是一个变量;再根据具体的声明类型切换接下来读声明的关注对象;最后未读的基本类型就是最后一次关注对象的类型。上述的大致方法可详细总结如下:

  • 1.找到最左边的标示符,表示已被阅读;
  • 2.若已阅读符号右方紧邻[,则从左至右阅读到与之配对的 ] 为止。这一段符号表示 当前的关注对象是一个数组,该数组的元素类型未知;接下来的关注对象是数组的元素的类型,转4;否则转至3;
  • 3.若已阅读符号右方紧邻(,则从左至右阅读到与之配对的 )为止。这一段符号表示当前的关注对象是一个函数,该函数的返回值类型未知;接下来的关注对象是函数的返回值类型;顺序执行4;
  • 4.如果已阅读符号左边的符号是(,则寻找与之配对的 ),这一段符号表示已经阅读过;转至2;否则转至5;
  • 5.如果已阅读符号左边的符号是const,volatile和*之一,则不同符号代表不同的含义。const表示只读;volatile表示禁止编译器优化;*表示一个指针,该指针指向那种类型是接下来的关注对象,转4;否则转至6;
  • 6.剩下的未阅读的符号为基本的数据类型,即为当前关注对象的数据类型;

通过上述方法即可阅读一个声明,并理解它具体的含义。现在来解决本文一开始的那个声明:

int (*(*fun)())();

其步骤如下:

  • 找到标示符fun,fun已被阅读;
  • 步骤2,3,4不满足,转到步骤5;fun的左方为*,说明fun是一个指针。当前的关注对象是fun指向什么类型的数据,转到第4步;
  • *fun的左方为(,则(*fun)表示已被阅读的符号。当前的关注对象仍然是fun指向什么类型的数据,转到第2步;
  • (*fun)的右方紧邻(,则(*fun)()为已阅读的符号。它表示fun指向一个函数,该函数的返回值类型成为接下来的关注对象,转到第4步;
  • 步骤4不满足,转到步骤5;(*fun)()的左方为*,说明返回值类型为一个指针,该指针指向什么类型是接下来的关注对象。当前已读过的符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向什么类型未知;转至4;
  • *(*fun)()左方为(,则(*(*fun)())表示已被阅读。转至第2步;
  • (*(*fun)())的右方为(,则(*(*fun)())()为已阅读的符号,它表示当前的关注对象是一个函数。目前为止已阅读的符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向一个函数,该函数返回值类型未知;转至4;
  • 步骤4,5不满足转至6;int表示当前的关注对象为整型,目前所有的符号已阅读完毕。所有的声明符号表示fun是一个指针,它指向一个函数,该函数返回一个指针,该指针指向一个函数,该函数返回一个整型变量;

大功告成!

上述的分析过程看起来可能有些复杂和呆板,当你熟悉了整个分析过程后,这个过程就变得轻而易举。

举例

结合上面的理论分析,可以阅读下面的代码。这个代码一方面可以帮助你巩固上面的阅读规则,另一方面可以帮助你理解函数声明和指向函数指针的声明两者之间的区别。

/*
 * Author: edsionte
 * Email:  edsionte@gmail.com
 * Time:   2011/02/01
 */
#include 

int (*myfun1())();
int myfun2();

int (*myfun1())()
{
	//myfun1 return a pointer which point a function;
	//The return function hasn't argument and reurun a int value;

	int (*fun2)();
	printf("I am myfun1~\n");
	fun2 = myfun2;
	return fun2;
}

int myfun2()
{
	printf("I am myfun2~\n");
	return 0;
}

int main()
{
	int (*(*fun1)())();
	int (*fun2)();

	fun1 = myfun1;
	fun2 = fun1();
	fun2();

	return 0;
}

参考:

《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;

shell编程中的判断语句

2011年1月28日

在shell编程中,通过if命令来实现判断语句;if命令中的判断条件则通过test命令来完成。因此,本文将集中介绍if命令和test命令的使用方法。

1. 退出状态

对于每一条命令而言,它在执行完毕后就有相应的返回状态,这个返回状态保存在特殊变量?中。当一条命令成功执行后,该命令的返回状态为0;否则,该命令的返回状态为非0。比如:

edsionte@edsionte-laptop:~/shelltest$ cat nothisfile
cat: nothisfile: 没有那个文件或目录
edsionte@edsionte-laptop:~/shelltest$ echo $?
1
edsionte@edsionte-laptop:~/shelltest$ echo $?
0

由于cat命令执行失败,通过echo命令可获得cat命令的返回值为1;再次echo特殊变量?的值可以看到返回状态为0,这正好说明了第一次echo命令成功执行。

2.if命令

if命令的的使用方法如下:

if command_t
then
	commands
fi

command_t是用来进行逻辑判断的命令。如果该命令的的返回状态为0,则执行then和fi之间的命令。除了上述的基本使用方法外,if命令还有以下两种结构。

//if-else
if command_t
then
	commands
else
	commands
fi
//if-elif-else
if command_t1
then
	commands
elif command_t2
then
	commands
else
	commands
fi

上述两种结构理解起来不会很难,其使用方法可参看test命令中的举例。

3. test命令

正如本文一开始说的那样,test命令常用作if命令中的逻辑判断。其使用方法如下:

test expression

其中expression表示要测试的条件,如果该条件为真,则test命令的返回状态为0;否则返回非0。

字符串操作符

操作符=用来判断两个字符串是否相等,其使用方法如下: test str1 = str2 注意test命令的三个参数必须用空格隔开,这样test才能正确的接收到三个参数。现在,我们可以改进上文的lu程序,增加参数判断功能:

edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
if test "$1" = ""
then
	echo "usage: ./lu arg"
else
	grep "^$1:" info
fi
edsionte@edsionte-laptop:~/shelltest$ ./lu
usage: ./lu arg

如果未输入参数,则提示使用方法。由于=用来判断两个字符串是否相等,因此该判断符对于字符串中的空格很敏感。比如:

edsionte@edsionte-laptop:~/shelltest$ name="edsionte "
edsionte@edsionte-laptop:~/shelltest$ test "$name" = "edsionte"
edsionte@edsionte-laptop:~/shelltest$ echo $?
1

由于对name赋值时edsionte后有一个空格,因此这两个字符串不相等。test命令的返回值为1。 在上述的test命令中,只要涉及到对变量的引用都会加上双引号。通过下述举例可以看到这种使用的好处。

edsionte@edsionte-laptop:~/shelltest$ name=
edsionte@edsionte-laptop:~/shelltest$ test $name = "edsionte"
bash: test: =: 需要单个参数
edsionte@edsionte-laptop:~/shelltest$ test "$name" = "edsionte"
edsionte@edsionte-laptop:~/shelltest$ echo $?
1

当给name赋值为空时,如果没有使用双引号引用该变量,则shell在执行test命令时会认为缺少了第一个字符串参数。

另一种格式

由于if命令在shell编程中频繁使用,因此也诞生了一种简易的test使用格式:

[ expression ]

其实上述格式中的[和]就等价于命令名。值得注意的是,expression前后需要和相应中括号用空格隔开。比如上述举例:

edsionte@edsionte-laptop:~/shelltest$ [  "$name" = "edsionte"  ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
1

整数操作符

test命令除了有对字符串进行的操作符外,还有一些列针对整数的操作符。下列的文字描述只针对返回值为0时的情况:

int1 -eq int2 :int1等于int2
int1 -ne int2 :int1不等于int2
int1 -ge int2 :int1大于等于int2
int1 -gt int2 :int1大于int2
int1 -le int2 :int1小于等于int2
int1 -lt int2 :int1小于int2

如果在上述判断条件前加上逻辑非符号!,则表示对当前返回值取反。也就是说,如果该判断条件不满足时,返回值为0。比如:

edsionte@edsionte-laptop:~/shelltest$ [ 1 -ge 3 ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
1
edsionte@edsionte-laptop:~/shelltest$ [ ! 1 -ge 3 ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
0

我们还可以这么改进上文中的lu程序:

edsionte@edsionte-laptop:~/shelltest$ ./lu
usage: ./lu arg
edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
if [ $# -ne 1 ]
then
	echo "usage: ./lu arg"
else
	grep "^$1:" info
fi

逻辑操作符

上述所举的例子都是针对一条测试条件的,如果test命令的测试条件超过一条时,就应该使用逻辑操作符-a和-o。前者标示逻辑与,而这标示逻辑或。其使用方法可参考下例:

edsionte@edsionte-laptop:~/shelltest$ [ ! 1 -ge 3 -a 3 -ge 1 ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
0
edsionte@edsionte-laptop:~/shelltest$ [ ! 1 -ge 3 -a 3 -le 1 ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
1
edsionte@edsionte-laptop:~/shelltest$ [ ! 1 -ge 3 -o 3 -le 1 ]
edsionte@edsionte-laptop:~/shelltest$ echo $?
0

关于test命令更多的使用方法可以参考man手册。

shell编程中的参数传递

2011年1月27日

在shell程序中引入参数,shell程序就会灵活很多。本文首先简单介绍参数传递中的一些基本知识点,再通过一个通讯录的举例来加深对shell编程中参数传递的理解。

1. 参数的引用

当运行shell程序时,shell会自动将命令行中第一个参数保存在变量1中,第二个参数保存在变量2中,依次类推。因此,当需要在shell程序中使用这些参数时,便可以通过$1和$2这样的方法来引用。比如:

edsionte@edsionte-laptop:~/shelltest$ cat myargs
echo "\"$1\" \"$2\" was passed"
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte wu
"edsionte" "wu" was passed
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte
"edsionte" "" was passed
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte wu me
"edsionte" "wu" was passed

通过上述举例可以看到,命令行中的第n个参数会自动传递给相应的特殊变量n。通过$n就可以在shell程序中对其进行引用;当引用未赋值的特殊变量时,其结果将会显示空。

2. #变量

每当shell程序运行时,特殊变量#中就会保存此次shell程序中键入的参数个数。比如:

echo "There are $# arguments passed"
echo "\"$1\" \"$2\" was passed"
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte
There are 1 arguments passed
"edsionte" "" was passed

命令行中的参数是以空格作为间隔。因此,下面这种结果也很合理:

edsionte@edsionte-laptop:~/shelltest$ ./myargs "edsionte wu"
There are 1 arguments passed
"edsionte wu" "" was passed
edsionte@edsionte-laptop:~/shelltest$ name="edsionte wu"
edsionte@edsionte-laptop:~/shelltest$ ./myargs $name
There are 2 arguments passed
"edsionte" "wu" was passed

3. *变量

特殊变量*中会保存shell程序运行时用户键入的所有参数。在参数不确定或参数数目易变时,该变量就很有用。

edsionte@edsionte-laptop:~/shelltest$ cat myargs 
echo "There are $# arguments passed"
echo "\"$*\" was passed"
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte wu is me
There are 4 arguments passed
"edsionte wu is me" was passed
edsionte@edsionte-laptop:~/shelltest$ ./myargs edsionte wu
There are 2 arguments passed
"edsionte wu" was passed

4. 通信录举例

查找用户

现在需要通过用户姓名从通信录info中查找一个用户的个人信息。我们通过编写shell程序lu来实现这个功能。最简单的程序为:

edsionte@edsionte-laptop:~/shelltest$ cat info
edsionte wu:male:xian:18717375182
sen wang:female:hanzhong:15678675545
ting li:male:guangzhou:15784859345
jedsionte:male:xian:13556567884
edison:male:hongkong:1384742488
yun zhang:female:beijing:18654737473
edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
grep $1 info
edsionte@edsionte-laptop:~/shelltest$ ./lu edsionte
edsionte wu:male:xian:18717375182
jedsionte:male:xian:13556567884

这个结果似乎并不令人满意,因为我们想找用户名为edsionte的用户。不过这个问题很好解决,我们可以这样改进程序:

edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
grep ^$1 info
edsionte@edsionte-laptop:~/shelltest$ ./lu edsionte
edsionte wu:male:xian:18717375182

如果我们想通过全名edsionte wu来查找用户信息,就应该用双引号将全名作为一个整体引入其中。这样看似很完美,可是结果却不尽人意:

edsionte@edsionte-laptop:~/shelltest$ ./lu "edsionte wu"
grep: wu: 没有那个文件或目录
info:edsionte wu:male:xian:18717375182

这是因为虽然edsionte wu作为一个参数传递给特殊变量1,但是shell将其替换后,grep命令并不认为edsionte wu是一个参数,而是将wu作为目标文件之一。为了避免这种情况,我们应该这样改进程序:

edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
grep "^$1" info
edsionte@edsionte-laptop:~/shelltest$ ./lu "edsionte wu"
edsionte wu:male:xian:18717375182

这样就可以满足我们要求了。但是,如果info中的信息如下所示,那么上述lu程序还是不算完美。比如:

edsionte@edsionte-laptop:~/shelltest$ cat info
edsionte wu:male:xian:18717375182
sen wang:female:hanzhong:15678675545
edsionte wuee:male:xian:18717375182
ting li:male:guangzhou:15784859345
jedsionte:male:xian:13556567884
edison:male:hongkong:1384742488
yun zhang:female:beijing:18654737473
edsionte@edsionte-laptop:~/shelltest$ ./lu "edsionte wu"
edsionte wu:male:xian:18717375182
edsionte wuee:male:xian:18717375182

因此,我们也应该在参数的尾部也做一些“手脚”,使得grep命令只匹配我们所传递的参数:

edsionte@edsionte-laptop:~/shelltest$ cat lu
# look someone up in the info book
grep "^$1:" info
edsionte@edsionte-laptop:~/shelltest$ ./lu "edsionte wu"
edsionte wu:male:xian:18717375182

至此,查找程序完成。

添加用户

当向通讯录中增添新用户信息时,我们必须再编写一个add程序。增加用户程序很简单:

edsionte@edsionte-laptop:~/shelltest$ cat add
# add someone to the info book
echo "$1:$2:$3:$4" >> info
edsionte@edsionte-laptop:~/shelltest$ ./add elva female taibei 1335676567
edsionte@edsionte-laptop:~/shelltest$ ./lu elva
elva:female:taibei:1335676567

>>使得新的信息会追加到原文件的末尾。在上述命令的基础上,我们还可以再增加按姓名排序的功能:

edsionte@edsionte-laptop:~/shelltest$ cat add
# add someone to the info book
echo "$1:$2:$3:$4" >> info
sort -o info info
edsionte@edsionte-laptop:~/shelltest$ cat info
edison:male:hongkong:1384742488
edsionte wuee:male:xian:18717375182
edsionte wu:male:xian:18717375182
elva:female:taibei:1335676567
jedsionte:male:xian:13556567884
sen wang:female:hanzhong:15678675545
ting li:male:guangzhou:15784859345
yun zhang:female:beijing:18654737473

添加程序结束。

删除用户

有了上述两个程序的基础,删除用户程序则变得容易很多。首先通过sed命令将完全匹配给定人名的用户信息从info中删除;因为sed命令并不改变info文件本身的内容,因此需要将sed的输出结果暂存至缓冲区;最后通过mv命令替换文件名即可。

edsionte@edsionte-laptop:~/shelltest$ cat del
# delete the given user from info
sed "/^$1:/d" info > /tmp/info
mv /tmp/info ./info
edsionte@edsionte-laptop:~/shelltest$ ./del elva
edsionte@edsionte-laptop:~/shelltest$ cat info
edison:male:hongkong:1384742488
edsionte wuee:male:xian:18717375182
edsionte wu:male:xian:18717375182
jedsionte:male:xian:13556567884
sen wang:female:hanzhong:15678675545
ting li:male:guangzhou:15784859345
yun zhang:female:beijing:18654737473

随着对shell编程的深入理解,上述通讯录程序还可以不断完善。

shell编程中的引用

2011年1月24日

shell编程语言中有四种引用符号,分别是单引号(”),双引号(“”),反单引号(“)和反斜杠。前三种符号必须成对出现来使用,而后者则单独使用。有时候我们需要shell将某些字符进行特殊解释,此时引用符号就派上了用场。

单引号

1.整体赋值

在对变量进行赋值时,使用单引号将中间有空格的字符引用后赋值给变量后,shell会将这些字符看作成一个整体来处理。比如,我们需要在name文件中查找edsionte wu这个姓名:

edsionte@edsionte-desktop:~/shelltest$ cat name
edsionte
zhanyu
miao
edsionte wu
edsionteli
edsionte@edsionte-desktop:~/shelltest$ grep edsionte wu name
grep: wu: 没有那个文件或目录
name:edsionte
name:edsionte wu
name:edsionteli

此时grep命令产生了错误信息。由于edsionte和wu字符之间有空格,grep将edsionte视为要查找的字符,而wu和name都是所要查找的文件。如果我们将edsionte和wu放入单引号内,就可以正确得到结果:

edsionte@edsionte-desktop:~/shelltest$ grep 'edsionte wu' name
edsionte wu

通过使用单引号,shell从上面的那条命令中提取到edsionte wu和name两个参数,再将这两个参数传递给grep命令。

2.保留字符

使用单引号将字符引用后,shell会原封不动的保留单引号内的字符,比如:

edsionte@edsionte-desktop:~/shelltest$ echo edsionte              wu
edsionte wu
edsionte@edsionte-desktop:~/shelltest$ echo 'edsionte              wu'
edsionte              wu

不使用单引号时,shell会将字符之间的多个空格缩减至一个;如果使用了单引号,那么shell会原封不动将单引号中的内容提取出来,送至echo命令。 同样,由于单引号原封不动的特性,处于单引号内部的特殊字符不会被shell解释。比如:

edsionte@edsionte-desktop:~/shelltest$ ls
gender  name  number
edsionte@edsionte-desktop:~/shelltest$ echo *
gender name number
edsionte@edsionte-desktop:~/shelltest$ echo '*'
*

可以看到,特殊字符*并不会被单引号所解释。接下来的这个例子会让你更深入的理解上述两个功能。比如:

edsionte@edsionte-desktop:~/shelltest$ files='I have these files: *'
edsionte@edsionte-desktop:~/shelltest$ echo $files
I have these files: gender name number

可能你会有疑问:处于单引号内的*为什么会被shell解释呢?如果你理解了这里的单引号所起的作用就回消除上面的疑惑。对于echo $files命令来说,shell会先将$file替换为I have these files: *,也就是说这里的单引号是为了将上述字符整体赋值给files变量;然后shell会将*替换为当前目录下的所有文件名;最后将I have these files: gender name number作为一个整体传给echo命令。

双引号

双引号和单引号的功能几乎相同,只不过双引号对个别几个特殊字符会做解释,而不像单引号那样完全忽略所有特殊字符。这几个特殊字符为:美元符号($),反引号(“),反斜杠(/)。比如:

edsionte@edsionte-laptop:~/shelltest$ ls
info  name  nf  status  test  whos
edsionte@edsionte-laptop:~/shelltest$ x=*
edsionte@edsionte-laptop:~/shelltest$ echo $x
info name nf status test whos
edsionte@edsionte-laptop:~/shelltest$ echo '$x'
$x
edsionte@edsionte-laptop:~/shelltest$ echo "$x"
*

可以看到,shell对位于双引号内的$进行了解释,也就是将其解释为对x变量的引用。除此之外,双引号和单引号并无太大差异。比如:

edsionte@edsionte-laptop:~/shelltest$ x="edsionte    wu"
edsionte@edsionte-laptop:~/shelltest$ echo $x
edsionte wu
edsionte@edsionte-laptop:~/shelltest$ echo "$x"
edsionte    wu
edsionte@edsionte-laptop:~/shelltest$ echo '$x'
$x

对于第一条的赋值语句,不管使用单引号还是双引号,效果是一样的,都是将edsionte wu作为一个整体赋值给变量x。

反引号

在命令A中使用反引号的目的并不是希望shell保留某些特殊字符的本意,而是将其他的命令B包含起来,并把命令B的输出插入到命令B所在的位置。其格式为:
cmdA `cmdB`

比如通过反引号的引用在echo命令中使用date命令:
edsionte@edsionte-desktop:~/shelltest$ echo 'Now is `date`'
Now is `date`
edsionte@edsionte-desktop:~/shelltest$ echo "Now is `date`"
Now is 2011年 01月 25日 星期二 16:48:40 CST

由于单引号对任何字符都不作特殊解释,因此反引号在单引号中并不起作用。但是在双引号中,反引号中的date命令被shell解释了。
由于双引号

反斜杠

1.消除特殊含义

如果在一些特殊字符前面加上反斜杠,则会消除这些字符的特殊含义。因此\c的功效相当于’c’。比如:

edsionte@edsionte-desktop:~/shelltest$ x=*
edsionte@edsionte-desktop:~/shelltest$ echo $x
gender name number
edsionte@edsionte-desktop:~/shelltest$ echo \$x
$x

可以看到,在$前面加入反斜杠可以去除$的引用含义而将$本身显示出来。

2.续行

当反斜杠位于一行的最后一个字符时,它会起到续行的作用。比如:

edsionte@edsionte-desktop:~/shelltest$ file=edsion\
> te
edsionte@edsionte-desktop:~/shelltest$ echo $file
edsionte

使用反斜杠进行续行后,反斜杠前后的字符之间并没有分隔符。

3.在双引号中使用 反斜杠

如果我们希望shell保留双引号中的特殊字符(即$、“和\)的含义,那么反斜杠就派上了用场。比如:

edsionte@edsionte-desktop:~/shelltest$ x=5
edsionte@edsionte-desktop:~/shelltest$ echo "The value of \$x is $x"
The value of $x is 5

end

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key