对话 UNIX Squirrel可移植的 shell 和脚本语言.docx
- 文档编号:28961828
- 上传时间:2023-07-20
- 格式:DOCX
- 页数:14
- 大小:145.86KB
对话 UNIX Squirrel可移植的 shell 和脚本语言.docx
《对话 UNIX Squirrel可移植的 shell 和脚本语言.docx》由会员分享,可在线阅读,更多相关《对话 UNIX Squirrel可移植的 shell 和脚本语言.docx(14页珍藏版)》请在冰豆网上搜索。
对话UNIXSquirrel可移植的shell和脚本语言
为多个平台编写面向对象的shell脚本
如果您不满足于特定的shell仅能在某个特殊平台上运行,那么可尝试使用SquirrelShell。
SquirrelShell提供了一种高级的、面向对象的脚本语言,在UNIX®、Linux®、MacOSX™和Windows®系统上都可以良好地运行。
只需要编写一次脚本,就可以在多个平台上运行。
1799年,一名法国陆军工程师取得了一项重大发现。
不,不是鹅肝酱、卡门培尔奶酪、巴氏消毒法或沙特(Sartre)—实际上,他发现了能够破译埃及古代象形文字的钥匙——罗塞塔石碑(参见 图1)。
图1.罗塞塔石碑,1100磅重,其上使用三国语言篆刻了税收策略。
碑文展示的是减免僧侣税款的诏书。
这块石碑制作于公元前196年,篆刻了对同一段文字的三种不同语言版本—分别是象形文字、通俗体文字(埃及草书)和希腊文字。
通过对照翻译,或在不同语言版本之间寻找对应的词汇,罗塞塔石碑解读出已经失传已久的象形文字的含义。
换句话说,将罗塞塔石碑想像成Babelfish。
即使在公元前196年,就出现了使用一种以上的语言进行表达。
公元2000年末,软件开发人员面对着一个相似的问题。
有太多的语言和方法可以用来表达同一内容。
即使对于命令行,也有许多类似的内容可供选择,包括各种shell和不同的命令组合。
通常来讲,多样性是件好事,但是它也会让人觉得害怕。
应该选择哪种解决方案?
这种技术是否能够跟上需求的变化?
时间和精力方面的投入能否得到回报?
这些编写良好的代码(或Perl代码)是否会过时?
更糟糕的是,是否需要针对其他环境转换(重写)所有内容?
如果您不希望局限于Fishshell、Bashshell、Zshell、Windowsoperatingsystem的cmd.exe或其他一些shell脚本语言的特性,那么请尝试使用SquirrelShell。
SquirrelShell提供了一种高级的、面向对象的脚本语言,在UNIX、Linux、MacOSX和Windows系统上都可以良好地运行。
您只需要编写一次脚本,就可以在任意平台上运行。
更妙的是,您需要做的工作非常简单。
获得Squirrel
根据GNUPublicLicenseversion3(GPLv3)的条款,SquirrelShell很容易获得并且可以免费使用。
最新的版本为2008年10月11日发布的1.2.2。
SquirrelShell的创建者和维护者是Constantin"Dinosaur"Makshin。
SquirrelShell的下载页面(参见 参考资料)提供了针对32位和64位Windows的源代码和二进制代码。
如果您使用UNIX或Linux,请检查发行版附带的库,寻找合适的二进制文件或从头构建SquirrelShell。
从头构建SquirrelShell非常简单。
下载并提取源代码tarball文件,放到源代码目录,然后使用非常典型的构建shell,如 清单1 所示。
清单1.从头构建SquirrelShell
$./configure--with-pcre=system&&make&&sudomakeinstall
CheckingCPUarchitecture...x86
Checkingforinstall.../usr/bin/install
...
Configurationhasbeencompletedsuccessfully.
Buildforx86CPUarchitecture
Installationprefix:
/usr/local
Allowdebugging:
no
Buildstaticlibraries
UsesystemPCRE6.7library
InstallMIMEinformation:
auto
Createsymboliclink:
no
CompileCcodewith'gcc'
CompileC++codewith'g++'
Createstaticlibrarieswith'arrc'
Createexecutablesandsharedlibrarieswith'g++'
Installfileswith'install'
要查找与包有关的选项列表以进行配置,需在命令行中输入 ./configure--help。
为方便起见,SquirrelShell打包了PerlCompatibleRegularExpression(PCRE)库的源代码,这些内容在程序中被大量使用。
如果系统缺少PCRE,打包后的代码可以使构建变得简单快捷。
然而,如果系统已经有了PCRE,那么可以通过指定 --with-pcre=system 选项来使用它。
另一种方法是指定 --with-pcre=auto 以链接到更新的系统库或SquirrelShell的副本。
构建的结果是得到一个新的二进制文件,名为 squirrelsh。
假设此文件被安装到PATH变量的某个目录中,比如/usr/local/bin,那么输入squirrelsh 以启动该shell。
在命令行提示符下,输入命令 printl(getenv("HOME")); 以输出主目录的路径:
$squirrelsh
>printl(getenv("HOME"));
/home/strike
>exit();
SquirrelShell基于Squirrel编程语言(参见 参考资料 获得更多信息的链接)。
该语言类似于 C++,并且提供了非常类似于Python和Ruby等面向对象脚本语言的特性。
SquirrelShell纳入了Squirrel中的所有特性和数据类型,并添加了一些专门为常见shell脚本任务编写的新功能,比如复制文件和读取环境变量。
尽管SquirrelShell的语法对于日常的命令行使用过于繁杂—echo$HOME 是和SquirrelShell的 printl("~") 具有等效功能的Bash命令—但是它拥有出色的脚本。
您只需要编写一次,就可以到处运行,而不需要针对UNIX和Windows分别编写。
正如Dinosaur这样评价他的工作,“SquirrelShell主要是充当一个脚本翻译器”。
回页首
使用Squirrel编写脚本
让我们看一看一个SquirrelShell脚本的示例。
清单2 展示了文件listing2.nut,此脚本将递归地列出您的主目录的内容。
清单2.listing2.nut
#!
/usr/bin/envsquirrelsh
functionreveal(filedir){
if(!
exist(filedir)){
return;
}
if(filename(filedir)==".."||filename(filedir)=="."){
return;
}
if(filetype(filedir)==FILE){
printl(filename(filedir,true));
return;
}
printl("directory:
"+filename(filedir,true));
localnames=readdir(filedir);
foreach(index,nameinnames){
reveal(name);
}
}
localprevious=getcwd();
chdir("~");
reveal(getcwd());
chdir(previous);
exit(0);
按照规定,每个shell脚本的第一行将向操作系统表明要启动哪个程序来解释脚本。
通常,这一行会显示 #!
/usr/bin/bash 或 #!
/bin/zsh以从某个位置启动特定shell或解释器。
#!
/usr/bin/envsquirrelsh 有一些不同。
它启动了一个特殊的程序 env,此程序又启动PATH变量中找到的第一个 squirrelsh 实例。
因此,可以修改PATH变量以支持某个程序的本地版本—即您自己的、修改后的squirrelsh副本,位于$HOME/bin/squirrelsh—而不要修改shell脚本的内容。
注意:
这个技巧适用于所有解释器。
例如,#!
/usr/bin/envruby 将按照PATH设置的指示,调用您喜欢的Ruby版本。
总之,如果计划发布所编写的任何shell脚本,在第一行中使用 #!
/usr/bin/env application 表单,因为它的“移植性”更强:
它将运行用户 在他/她的PATH变量中已经配置好的应用程序版本。
清单2的其余部分应该比较熟悉,至少对于方法是这样。
函数 reveal() 是递归的:
∙如果为 reveal() 传递一个无效的路径或“小圆点”(.,当前目录)或“两个小圆点”(..,父目录),那么递归将结束。
∙否则,如果参数 filedir 是一个文件,代码将输出其名称并返回,并再一次停止进一步的递归。
函数 filename() 可以接受一到两个参数。
如果只有一个参数,或者第二个参数为 false,那么将忽略扩展文件名。
如果提供 true 作为第二个参数,将返回完整的文件名。
∙如果参数是一个目录,代码将输出其名称,然后扫描内容(不需要执行深度优先处理,因为目录内容并没有按特定的顺序排列。
下一个示例将改进输出)。
需要注意一点:
由于对 reveal() 的调用是同一个函数中的最后一条语句,Squirrel虚拟机(VM)—运行脚本代码的引擎—可以通过称为尾递归(tailrecursion)的技术将递归改为迭代。
实际上,尾递归消除了对递归使用调用栈的需要;因此,可以实现任意深度的递归并且可以避免栈溢出。
Squirrel的语法相当简单,因此使用这种语言编写代码非常快捷,特别是如果您曾经使用过 C、C++ 或任何更高级的语言编写过代码的话,这一点则体现得更充分。
最妙的是,这个shell代码是可移植的。
将它转移到Windows机器上,在其上安装SquirrelShell,然后就可以运行您的代码。
回页首
改进表
与典型shell相比,Squirrel的优秀特性之一就是它丰富的数据结构。
如果数据可以进行良好地组织,那么即使是复杂的问题通常也能够快速得到解决。
Squirrel提供了真正的对象、异构数组和关联数组(在Squirrel中称为 表)。
一个Squirrel表由一些 slot 或 (键-值)对组成。
除Null以外的任何值都可以充当一个键;任何值都可以被分配给一个slot。
您将使用“箭头”操作符创建一个新的slot(<-)。
让我们对 清单2 的代码稍加改进,在将目录转变为任何子目录之前展示它的内容。
使用什么方法?
使用一个本地表在单独的slot中存放文件和子目录,然后相应地处理两个类别。
清单3 展示了新的代码。
清单3.增强后的清单2将首先输出目录的内容,然后递归到子目录
#!
/usr/bin/envsquirrelsh
functionreveal(filedir){
localtally={};
tally[FILE]<-[];
tally[DIR]<-[];
if(!
exist(filedir)){
return;
}
if(filename(filedir)==".."||filename(filedir)=="."){
return;
}
localnames=readdir(filedir);
foreach(index,nameinnames){
tally[filetype(name)].append(name);
}
foreach(index,fileintally[FILE]){
printl(file);
}
foreach(index,dirintally[DIR]){
printl(filename(dir)+"/");
}
foreach(index,dirintally[DIR]){
reveal(dir);
}
}
localentries=readdir((__argc>=2)?
__argv[1]:
".");
exit(0);
在这里非常适合使用表这种数据结构。
reveal() 中的表有两个slot:
一个用于文件,另一个用于目录。
filetype(name) 函数的返回值—常量FILE 或常量 DIR—将文件系统中的每一项整理到相应的slot中。
此外,每个slot是一个数组,由 tally[FILE]<-[] 和 tally[DIR]<-[]; 这两条语句创建。
([] 是一个空数组)。
由于 tally 是函数内的本地变量,它将在每次调用时重新创建并清空范围,并且在每个调用被返回时自动销毁。
数组函数 append( arg ) 将 arg 添加到数组的末尾,从而在此过程中形成了一个列表。
在执行完 foreach(index,nameinnames) 循环后,所有项都被添加到这两个slot中其中一个的列表中。
函数其余部分的代码将输出文件,接着输出目录,然后是递归。
当然,如果没有命令行参数的话,shell脚本的价值就没有那么大了。
特殊SquirrelShell变量 __argc 和 __argv 分别以字符串数组形式包含命令行参数的计数和参数列表。
根据约定,__argv[0] 始终都作为shell脚本的名称;因此,如果 __argc 的值至少为2,那么将提供额外的参数。
为了简单起见,这个脚本只处理第一个额外参数 argv[1]。
作为参考,清单4 展示了一个Ruby脚本(作者为Mr.Makshin),此脚本的功能与清单3相同。
即使该脚本已像Ruby那样简洁,但它在简洁性方面仍然逊色于SquirrelShell代码。
清单4.使用Ruby重新实现清单3
!
/usr/bin/ruby
#Listdirectorycontents.
path=ARGV[0]==nil?
".":
ARGV[0].dup
#Removetrailingslashes
whilepath=~/\/$/
path.chop!
end
entries=Dir.open(path)
forentryinentries
unlessentry=="."||entry==".."
filePath="#{path}/#{entry}"
fileStat=File.stat(filePath)
iffileStat.directory?
puts"dir:
#{filePath}"
elsiffileStat.file?
puts"file:
#{filePath}"
end
end
end
entries.close()
有关Squirrel语言的更多信息,请参阅 SquirrelProgrammingLanguageReference(参见 参考资料 获得链接)。
巧妙的是,SquirrelShell中的几乎所有函数都去掉了底层操作系统的细节,因此您的代码可以尽可能保持通用。
例如,filename() 函数(在前两个清单中使用)将引导路径(leadingpath)从文件路径名中分离—比如,将/home/example/some/directory/file.txt简化为file.txt—而不管您使用的是何种平台。
类似地,readdir() 和 filetype() 允许您不必了解真实的、底层操作和文件系统的圈套和陷阱。
通常,普通的shell并不能提供这种抽象(较为高级的脚本语言则可以)。
其他有用的、独立于平台的功能包括 convpath() 和 run(),前者可以将路径名转换成本地路径名格式,而后者可以调用另一个可执行文件。
convpath() 函数可以执行双向转换,因此对于编写跨平台脚本非常有用。
回页首
正则表达式
Shell脚本通常用于自动化系统管理和维护工作。
实现这种自动化主要依靠正则表达式,它是用来查找、匹配和分解字符串的一组真正的象形文字。
如前所述,SquirrelShell需要PCRE库,这种库在Perl、PHP、Ruby和其他许多解释器和程序中都可找到。
PCRE是用于数据处理的重要武器。
尽管非常完整,SquirrelShell的正则表达式实现有一些不同,可能会令您想起PHP实现。
要在SquirrelShell中使用正则表达式,需要先定义正则表达式,对其进行编译,进行比较,然后再迭代结果(如果有的话)。
清单5 展示的示例程序演示了SquirrelShell中的正则表达式(代码由Mr.Makshin编写并且得到使用许可)。
清单5.演示SquirrelShell中的正则表达式
#!
/usr/bin/envsquirrelsh
//Matcharegularexpressionagainsttext
print("Text:
");
localtext=scan();
print("Pattern:
");
localpattern=scan();
localre=regcompile(pattern);
if(!
re)
{
printl("Failedtocompileregularexpression-"+regerror());
exit
(1);
}
localmatches=regmatch(re,text);
if(!
matches)
{
printl("Failedtomatchregularexpression-"+regerror());
regfree(re);
exit
(1);
}
regfree(re);
printl("Matchesfound:
");
foreach(matchinmatches)
printl("\t\""+substr(text,match[0],match[1])+"\"");
在这里,scan() 从标准输出中读取一些文本和一个模式,但是并不包含通常用于确定正则表达式的起始和结束部分的前斜杠(/)字符。
对于一个模式,函数 reqgcompile() 将编译此模式,这将提高匹配的速度。
您可以对 reqgcompile() 函数使用一个标记以启用或禁用区分大小写的功能(等同于PCRE /i 修饰符),并且可以使用另一个选项针对一行或多行进行匹配(等同于PCRE /m 选项)。
如果没有对正则表达式执行编译,那么所有匹配将失败。
regmatch(re,text) 函数将比较正则表达式和文本,如果没有匹配的话就生成Null值,否则生成一个由成对整数组成的数组(双元素数组)。
每一对中的第一个整数表示匹配的开始;第二个整数表示匹配结束。
这解释了最后一行代码中 substr(text,match[0],match[1]) 的使用。
执行完比较后,可以迭代结果。
如果在任何时候不再需要编译后的正则表达式,则使用 regfree() 删除它。
还有一个 regfreeall() 函数可以处理所有已编译表达式所持有的所有资源。
回页首
SquirrelShell的限制
在理想情况下,相同的编程逻辑将应用到UNIX、Linux和Windows中,并且效率至少和以前一样高,这样程序员会更加高兴。
可惜操作系统各不相同,您经常需要为了某个特定系统而求助于定制代码。
在这些情况下,无论是SquirrelShell还是您都无法脱离平台,SquirrelShell提供了一个方便的函数来探测操作系统,这样代码就可以适当的执行。
清单6 展示了如何使用 platform() 函数作出决策。
该函数始终返回一个值,但是该值可能是 unknown。
清单6.platform()函数生成操作系统类型
print("Madeby...");
localplatform=platform();
switch(platform){
case"linux":
printl("Linus.");
break;
case"macintosh":
printl("Steve.");
break;
case"win32":
case"win64":
printl("Bill.");
break;
default:
printl("Unknown");
}
您可以通过SquirrelShell环境变量PLATFORM查找当前平台的类型:
>printl(PLATFORM);
linux
环境变量CPU_ARCH生成处理器,shell将针对该处理器进行编译:
>printl(CPU_ARCH);
x86
结束语
SquirrelShell的其他函数将管理文件、处理环境和执行策略。
实际上,它的三角学内置函数就有20余种。
Version2.0目前正在规划之中,并且将包含更多类、对Unicode的支持、改进的交互模式,以及一个模块化的插件架构。
SquirrelShell并不算得上一种交互式shell,但是这没关系。
在这方面已经出现了很多选择。
作为一种脚本运行程序,SquirrelShell要比其同类出色许多。
其数据结构要比传统shell更加强大,它的语法简单易懂,其底层虚拟引擎支持从枚举类型到线程等所有内容。
Squirrel引擎也很小巧,不超过6000行代码。
您甚至可以将完整的Squirrel嵌入到另一个应用程序中。
当您需要为两个平台编写代码时,请尝试使用SquirrelShell!
它使您能够轻松编写自己的代码。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 对话 UNIX Squirrel可移植的 shell 和脚本语言 Squirrel 移植 脚本语言