在初始化一个SQL*Plus会话的时候对shell的访问会受到HOST命令和运行存储的SQL*Plus脚本的限制SQL*Plus不具有别名(alias)或者历史等特性也不具备把一个命令的输出通过管道传入别的命令的能力如果能将SQL*Plus的特性添加到现有的shell环境中岂不是一件美事?这里正好有一种方法可以实现这一想法
在UNIX中创建一个守护进程来将命令从独立的shell命令传入一个SQL*Plus会话是可能实现的第一步是创建一样能与SQL*Plus交互环境进行交互的东西虽然SQL*Plus是可交互的但是它仅限于STDOUT和STDIN所以它可以放入一个管道中
sqlplus /nolog < commandssql > outputlog
然而如果我们想一次发出一条SQL*Plus命令那么就需要检查SQL*Plus命令提示符SQL>来判断SQL*Plus是否在等待输入然后使用非阻塞管道这样我们可以在遇到提示符时停止读取数据而等待SQL*Plus更多的输入
一种天生可以完成这项工作的脚本描述语言是Expect它是Tcl/Tk程序设计语言的衍生物这个进程可以是守护进程或者是服务进程它接收单个的远程控制命令并将它们传递给从属SQL*Plus会话要与这个服务进程通信用法最简单的协议是UNIX Domain Protocol(UDP) socketsshell可以通过UDP客户端发出命令然后命令由服务进程接收然后其结果就通过socket发回到客户端
Expect需要一个扩展才能够使用UDP sockets与其安装该扩展到Expect不如在Perl中使用Expect因为Perl已经能够很好地处理sockets在Perl归档中可以找到Expectpm这个程序提供在Perl中使用Expect相同的功能
下面是我的简单示例服务程序使用PerlUDP和Expectpm作为我的行程控制服务程序
#!/usr/bin/perl w
# spdpl the SQL*Plus daemon server
use strict;
use Expect;
use Socket;
# set up expect
# timeout after about minutes
my $timeout = ;
# scan for the SQL*Plus prompt
my $prompt = SQL>;
my $exp = new Expect();
$exp>raw_pty();
$exp>log_stdout();
$exp>spawn(sqlplus/nolog) || die unable to spawn sqlplus: $!;
$exp>expect($timeoutex$prompt) || die $exp>error();
print $exp set sqlprompt $prompt;\n;
$exp>expect($timeoutex$prompt) || die $exp>error();
$exp>clear_accum();
my $name = /tmp/sp_$ENV{USER};
unlink($name);
socket(SPF_UNIXSOCK_STREAM) || die socket: $!;
bind(Ssockaddr_un($name)) || die bind: $!;
listen(SSOMAXCONN) || die listen: $!;
while(accept(CS))
{
# single threaded to avoid confusion
my $cmd = <C>;
$cmd =~ s/[\r\n]*$//g;
print $exp $cmd\n;
if ($cmd =~ /^exit$/mi)
{
print C exit\n;
close C;
last;
}
$exp>expect($timeout$prompt) || die $exp>error();
print C $exp>before();
close C;
}
$exp>soft_close();
close S;
unlink($name);
exit;
我把管道名叫做sp_{username}这样就允许同一个用户的两个不同会话共享同一个SQL*Plus会话我使用默认的SQL>提示符但是为了避免SQL>显示我的数据并导致数据被截断的可能性最好还是使用一个长的随机字符串
下一步是Perl客户端
#!/usr/bin/perl w
# spcpl
use Socket;
use strict;
my $cmd = join( @ARGV);
my $name = /tmp/sp_$ENV{USER};
my $proto = getprotobyname(udp);
socket(SPF_UNIXSOCK_STREAM) or die socket: $!;
select S; $| = ; select STDOUT;
connect(Ssockaddr_un($name)) or die connect: $!;
print S $cmd\n;
print while (<S>);
close(S);
exit;
这段脚本连接到UDP服务器发送一条单行命令(所有命令行参数的串联)然后将数据发回标准输出最后如果服务进程还没有运行两个Perl脚本的前面的CSH将启动服务程序然后将它们的参数传给客户端程序
#!/bin/csh
# spcsh
set f=/tmp/sp_$user
if (e $f == ) then
perl spdpl &
sleep
endif
perl spcpl $*
现在有了这个远程控制程序我就可以像下面这样来做了
$ alias sp spcsh
$ sp connect scott/tiger
Connected
$ sp select * from dual;
D
X
$ sp select sysdate from dual; | grep MAY
MAY
$ sp select * from emp; | wc
$ sp exit
最后一条命令将关闭服务程序
这个提示中的脚本非常粗糙不允许使用多行SQL语句如果一个SQL语句不完整服务进程将会挂起等待SQL提示符如果SQL提示符改变服务进程也会挂起但是在使用SQL*Plus的时候不丢掉shell环境依然非常有用对于一般的命令使用它可以创建别名和使用shell功能使用历史和替代变量或者在内嵌环境中使用SQL*Plus