使用Expect和命名管狀遠(yuǎn)程控制SQL*Plus
2024-07-21 02:34:43
供稿:網(wǎng)友
在初始化一個(gè)SQL*Plus會(huì)話的時(shí)候,對(duì)shell的訪問會(huì)受到HOST命令和運(yùn)行存儲(chǔ)的SQL*Plus腳本的限制。SQL*Plus不具有別名(alias)或者歷史等特性,也不具備把一個(gè)命令的輸出通過管道傳入別的命令的能力。 假如能將SQL*Plus的特性添加到現(xiàn)有的shell環(huán)境中豈不是一件美事?這里正好有一種方法可以實(shí)現(xiàn)這一想法。
在UNIX中,創(chuàng)建一個(gè)守護(hù)進(jìn)程來將命令從獨(dú)立的shell命令傳入一個(gè)SQL*Plus會(huì)話是可能實(shí)現(xiàn)的。第一步是創(chuàng)建一樣能與SQL*Plus交互環(huán)境進(jìn)行交互的東西。雖然SQL*Plus是可交互的,但是它僅限于STDOUT和STDIN,所以它可以放入一個(gè)管道中:
sqlplus /nolog < commands.sql > output.log
然而,假如我們想一次發(fā)出一條SQL*Plus命令,那么就需要檢查SQL*Plus命令提示符“SQL>”來判定SQL*Plus是否在等待輸入,然后使用非阻塞管道,這樣我們可以在碰到提示符時(shí)停止讀取數(shù)據(jù)而等待SQL*Plus更多的輸入。
一種天生可以完成這項(xiàng)工作的腳本描述語言是EXPect,它是Tcl/Tk程序設(shè)計(jì)語言的衍生物。這個(gè)進(jìn)程可以是守護(hù)進(jìn)程或者是服務(wù)進(jìn)程,它接收單個(gè)的遠(yuǎn)程控制命令并將它們傳遞給從屬SQL*Plus會(huì)話。要與這個(gè)服務(wù)進(jìn)程通信,用法最簡(jiǎn)單的協(xié)議是UNIX Domain PRotocol(UDP) sockets。shell可以通過UDP客戶端發(fā)出命令,然后命令由服務(wù)進(jìn)程接收,然后其結(jié)果就通過socket發(fā)回到客戶端。
Expect需要一個(gè)擴(kuò)展才能夠使用UDP sockets。與其安裝該擴(kuò)展到Expect,不如在Perl中使用Expect,因?yàn)镻erl已經(jīng)能夠很好地處理sockets。在Perl歸檔中可以找到Expect.pm。這個(gè)程序提供在Perl中使用Expect相同的功能。
下面是我的簡(jiǎn)單示例服務(wù)程序,使用Perl、UDP和Expect.pm作為我的行程控制服務(wù)程序:
#!/usr/bin/perl -w
# spd.pl - the SQL*Plus daemon server
use strict;
use Expect;
use Socket;
# set up expect
# -- timeout after about 10 minutes
my $timeout = 600;
# -- scan for the SQL*Plus prompt
my $prompt = 'SQL>';
my $exp = new Expect();
$exp->raw_pty(1);
$exp->log_stdout(0);
$exp->spawn('sqlplus','/nolog') die "unable to spawn sqlplus: $!";
$exp->expect($timeout,'-ex',$prompt) die $exp->error();
print $exp "set sqlprompt $prompt;/n";
$exp->expect($timeout,'-ex',$prompt) die $exp->error();
$exp->clear_accum();
my $name = "/tmp/sp_$ENV{USER}";
unlink($name);
socket(S,PF_UNIX,SOCK_STREAM,0) die "socket: $!";
bind(S,sockaddr_un($name)) die "bind: $!";
listen(S,SOMAXCONN) die "listen: $!";
while(accept(C,S))
{
# 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}”。這樣就答應(yīng)同一個(gè)用戶的兩個(gè)不同會(huì)話共享同一個(gè)SQL*Plus會(huì)話。我使用默認(rèn)的“SQL>”提示符,但是為了避免“SQL>”顯示我的數(shù)據(jù)并導(dǎo)致數(shù)據(jù)被截?cái)嗟目赡苄裕詈眠€是使用一個(gè)長(zhǎng)的隨機(jī)字符串。
下一步是Perl客戶端:
#!/usr/bin/perl -w
# spc.pl
use Socket;
use strict;
my $cmd = join(' ',@ARGV);
my $name = "/tmp/sp_$ENV{USER}";
my $proto = getprotobyname('udp');
socket(S,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
select S; $ = 1; select STDOUT;
connect(S,sockaddr_un($name)) or die "connect: $!";
print S $cmd,"/n";
print while (<S>);
close(S);
exit;
這段腳本連接到UDP服務(wù)器,發(fā)送一條單行命令(所有命令行參數(shù)的串聯(lián)),然后將數(shù)據(jù)發(fā)回標(biāo)準(zhǔn)輸出。
最后,假如服務(wù)進(jìn)程還沒有運(yùn)行,兩個(gè)Perl腳本的前面的CSH將啟動(dòng)服務(wù)程序然后將它們的參數(shù)傳給客戶端程序:
#!/bin/csh
# sp.csh
set f=/tmp/sp_$user
if (-e $f == 0) then
perl spd.pl &
sleep 1
endif
perl spc.pl "$*"
現(xiàn)在,有了這個(gè)遠(yuǎn)程控制程序,我就可以像下面這樣來做了:
$ alias sp sp.csh
$ sp "connect scott/tiger"
Connected.
$ sp "select * from dual;"
D
-
X
$ sp "select sysdate from dual;" grep MAY
05-MAY-03
$ sp "select * from emp;" wc
70 184 1890
$ sp "exit"
最后一條命令將關(guān)閉服務(wù)程序。
這個(gè)提示中的腳本非常粗糙;不答應(yīng)使用多行SQL語句。假如一個(gè)SQL語句不完整,服務(wù)進(jìn)程將會(huì)掛起等待SQL提示符;假如SQL提示符改變服務(wù)進(jìn)程也會(huì)掛起。但是在使用SQL*Plus的時(shí)候不丟掉shell環(huán)境依然非常有用。對(duì)于一般的命令使用它可以創(chuàng)建別名和使用shell功能,使用歷史和替代變量,或者在內(nèi)嵌環(huán)境中使用SQL*Plus。