ksh 是 UNIX/Linux 下流行的 shell 语言,ksh93 是 ksh 的增强型版本;相比于 ksh,ksh93 提供了更接近于高级语言的特性,包括 c 风格的 for 循环,关联数组,名字引用,复合变量和更方便的字符串操作,本文讨论了这些高级特性并说明它们的应用。
ksh 是各种 UNIX 下的主要 shell 编程语言,许许多多的 UNIX 开发人员每天都在使用 ksh 做为它们日常的工作工具,当前比较流行的 ksh 版本主要有两个,ksh88 和 ksh93 ;相对于 ksh88,ksh93 提供的特性让 shell 编程人员的效率更加高效,下面我们逐一讨论这些特性。
c 风格的 for 循环
传统的 shell for 循环使用 for i in set 形式:
for i in 1 2 3 4 5 do echo $i done |
ksh93 提供了对 c 风格 for 循环的支持,如下所示:
for((i=0; i<5; i++)) do echo $i done 对于在“ (( ”,“ )) ”中出现的 shell 变量,不需要在这些变量前加 $ 符号,比如 |
这项功能的加入也许是为了适应 c 编程人员众多这个事实,所以受到 c 程序员的广泛欢迎。
count=5 for((i=0; i<count; i++)) do echo $i done |
关联数组
普通数组使用整数做为下标,关联数组则是使用字符串作为下标。使用关联数组可以很容易的将两方相互联系起来,这两方在关联数组中表示为数组字符串下标和数组值。
要使用关联数组,首先要声明它,下行语句声明 dict 为关联数组。
typeset -A dict |
有两种方法可以给 dict 赴初值;这是第一种,
dict[tree]="a lot of leaves" dict[apple]="eclipse fruites" |
这是第二种,
dict=( [tree]="a lot of leaves" [apple]="eclipse fruit" ) |
要遍历 dict,使用如下代码
for i in ${!dict[*]}; # ${!dict[*]} 返回关联数组 dict 的所有下标 do echo ${dict[$i]}; # $i 是 dict 下标; ${dict[$i]} 取出 dict 的每个元素的值 done |
名字引用
名字引用使函数参数传递,引用和修改更加简单。在没有引入名字引用之前,为了能在函数内部访问实参,一种可选的方法是使用 eval 来实现间接变量引用,ksh93 使用名字引用使得这类代码实现更加直观和易读。下例在函数 foo 中用“ typeset -n larray=$1 ”定义了局部变量 larray,它是 foo 的的第一个参数的名字引用;也可以使用“ nameref larray=$1 ”得到相同的效果。
array=(1 2 3) function foo {typeset -n larray=$1 # larray是形参;typeset i for i in ${larray[*]} do echo $i done # 对形参 larray 做赋值操作,也就改变了实参 larray[0]=3; larray[1]=2; larray[2]=1; } |
运行 foo,将数组 array 作为参数
> foo array # array 是实参 1 2 3 > echo ${array[*]} # array 确实被 foo 改变了 3 2 1 |
复合变量
复合变量提供了类似于高级语言中结构的功能。
以下 ksh 语句使用复合变量定义了 desk 变量,它有三个域 name,id 和 price 。
desk=( typeset name=yijia integer id=1 float price=340.5 ) |
使用 ${desk.name} 来引用 desk 的 name 域
> echo ${desk.name} yijia |
将复合变量和间接变量引用相结合就能用 ksh93 实现教科书上线性链表等标准数据结构。
以下例子给出长度为 2 的一个线性链表,读者可以用类似方法模仿树的实现。
注意,我们称 next 为指针,仅是借用了 c 语言指针的说法,它和 c 中的指针在实现上是不一样的,但是概念类似,即都是属于间接访问这个类型。
#!/bin/ksh # /tmp/link.sh second=( # 复合变量 second,next 指针为空 data=1 next=null ) first=( # 复合变量 first,next 指针为 second data=2 next=second ) p=first # 将指针 p 初始化为 first while [[ $p != "null" ]] # 如果指针 p 为空,退出循环 do eval data=\${${p}.data}; echo "data=${data}"; # 取出和打印元素 data eval p=\${${p}.next}; echo "p=$p"; # 将 p 指向下一个元素 done > /tmp/link.sh # 执行 link.sh data=2 p=second data=1 p=null |
更方便的字符串操作
程序员日常工作中是经常遇到的操作之一就是字符串操作,ksh93 自然不会放过这方面的增强。
表 1 总结了 ksh93 在字符串处理方面的加强,假设 string 等于 abc123abc 。
表 1. 更强的字符串处理
功能 | 语法 | 样例 |
求起始位置为 index 的子串 | ${param:offset} | > echo ${string:3} 123abc |
求起始位置为 index 和长度 num 的子串 | ${param:offset:num} | > echo ${string:1:3} bc1 |
替换第一个出现的 pattern为 repl | ${parm/pattern/repl} | > echo ${string/abc/def} def123abc |
替换所有出现的 pattern 为 repl | ${parm//pattern/repl} | > echo ${string//abc/def} def123def |
替换开头的 pattern | ${parm/#pattern/repl} | > echo ${string/#abc/def} def123abc |
替换结尾的 pattern | ${parm/%pattern/repl} | > echo ${string/%abc/def} abc123def |