Some pieces of "Scripting"

Long time no blogging, what a shame!

最近一直在OT....做着一些有些无聊却又是very time-consuming的工作,  不是一般的蛋疼啊...

最近工作上的事情没有啥值得写一写的其实,不过这么久没有更新过了,不写点实在过不去啊,但是又没有啥主题可直奔的,因此这个标题就不知取何名字了,对于我这样一个地道的标题党来说,真的是相当纠结, 相当蛋疼!

想了想,就总结下最近被逼写的一些“脚本”吧,就是公司的产品要发布了,因此怎么也要有个所谓的“database installer”吧,所以脚本也是不可避免的。下面简单总结下遇到的问题吧...

1. EXP/IMP 相关

因为考虑到可能需要提供给客户一些sample data,可以让customer来play around with这个东东一番 以方便他们可以快速上手。因此最初想法就是用exp导出相关表的数据生成一个dump,然后再用imp来进行导入。都是基于windows平台,因此需要写点batch脚本来调用exp/imp。 奈何没写过batch, 而且batch写起来异常ugly, 灰常灰常滴不给力,擦擦。 不过找了些例子仿照了下,倒也可以跌跌撞撞起步了。

用Batch的目的很单纯,就是包装下exp的调用过程,因为exp调用需要提供一些参数,比如说userid, dump file, 之类。 因此batch脚本应该提示用户输入这些信息,然后利用这些参数信息进而调用exp工具。因为我是想导出一些表中的数据,为了方便“配置”导出哪些表(这可是个“不安分"因子),因此我想用通过一个参数文件(parfile)来包含所有的参数信息,这样只需要调用 exp parfile=<exp_parfile.par>就可以了。每次只需要更改parfile,就可以了,而不用更改很不友好的batch文件了。 

因此parfile的雏形如下所示,


USERID
=< systemstring > @ < dbname >
FILE =< dumppath > \ < dbuser > .dmp
LOG =< dumppath > \ < dbuser > . log
constraints
= Y
COMPRESS
=
statistics = NONE 
grants
=
buffer
= 10000000  
direct
= true
TABLES
=
(       
        
< dbuser > .table1
        
< dbuser > .table2
        ...
        
< dbuser > .tablen
                
)

很显然这里面的<>里面的应该都是需要用户输入的参数,需要从batch文件里面获取来的参数。

在列出我要导出的表的时候,我发现这些表可以进行简单的分类,比如说这几张表输入这个group,那几张表属于另外一个group。因此,我想在这部分加上些注释来区分下。

那么我遇到的第一个问题出现了:在parfile里面加怎么加注释呢? 

这个问题不是很难解决,搜了下,发现这里面的注释是用#来表示,因此我的想法可以实现成如下这个样子了(注意高亮部分)...


USERID
=< systemstring > @ < dbname >
FILE =< dumppath > \ < dbuser > .dmp
LOG =< dumppath > \ < dbuser > . log
constraints
= Y
COMPRESS
=
statistics = NONE 
grants
=
buffer
= 10000000  
direct
= true
TABLES
=
(       
    
table   group   1
    
< dbuser > .table1
    
< dbuser > .table2
    
    
table   group   2
    
< dbuser > .table3
    ...
    
< dbuser > .tablen 
)

这个问题解决之后,下面自然就是解决batch脚本如何提示/接受用户输入参数的问题了。

最开始注意的就是在batch脚本的起始行应该加入下面这么一行,不然在运行batch文件的时候,会发现command line总是会出现annoying的当前文件路径信息! 

@echo off

  和 另外还注意到可以用%CD%来获取当前路径,这真是蛮方便的。没办法,对于我这个菜鸟,这么点小东西都让我感觉很神奇。 可以事先对一些参数设置默认值,如果用户没有输入对应的参数信息,就是用预定义的默认值,这个是很人性化的。在batch里面可以通过如下方式实现,注意set 和 set /P


rem 
***************************************
rem 
DEFAULT  VALUE  for  systemstring
rem 
***************************************
set  systemstring = system / a


rem 
***************************************
rem Ask 
for  user's input
rem 
***************************************
:fromsystemstring
set / P sysstring = "System string connection ( default = ' %systemstring% ' ) : "
if   not  defined systemstring  goto  fromsystemstring


如此这番,就可以把需要的几个参数 <dumppath>, <dbuser>, <dbname>, <systemstring> 都搞定。 接下来要做的,自然就是把这些参数传给exp命令。 因为我想通过parfile的方式调用exp,因此需要某种方式将在batch文件里面获取的几个参数传递到parfile里面去。 这是我遇到的一个很纠结的问题,尝试了很多方法都没有找到门路。

刚开始,我的朴素单纯的大脑让我尝试如下的方式,(注意可以通过call 命令来执行exp)


set  cmdline = exp  parfile = exp_parfile.sql  % systemstring %   % dbuser %   % dumppath %   % dbname %
echo 
% cmdline %
call  % cmdline %

但是很不幸的是,这种方式是行不通的!因为exp 会把 %system%, %dbuser%, %dumppath%, %dbname% 等同于parfile当做exp的参数名,很显然exp不支持这些“稀奇古怪”的参数名。

那该怎么搞呢? 最后想到,是否可以把parfile参数动态修改,将里面的<systemstring>, <dbname>这些标记都替换成用户输入的值,最后生成一个新的parfile,用这个新的parfile来执行exp。这种方法理论上是行得通的,但是怎么去把我之前写的parfile模板中的标记动态替换呢,写batch的话不晓得怎么搞。后来一个同事让我试下sed把,应该很容易搞。但是sed这个东东是Linux下的工具,我更是不懂呀。后来这个热心的同事帮我搞了个windows上的sed, 可以在网上下载。包含一个sed.exe和这个sed.exe依赖的3个dll文件(如下),加起来大小1.2M左右,还可以接受。总不至于为了用这个sed,还去装一个windows版的sed吧。

 

 那么接下来就借助sed来替换parfile文件中的标记生成一个新的parfile, 

sed  - e "s /< dbname >/% dbname %/ g"  - e "s /< dbuser >/% dbuser %/ g"  - e "s /< systemstring >/% systemstring %/ g"  - e "s /< dumppath >/% dumppath %/ g" exp_parfile_template.sql  >  exp_parfile.sql

用法还是蛮简单的, sed -e "s/<to_be_replaced_content>/<replace_content>/g" source_file 然后通过 重定向操作符> 操作将结果输出到destination_file中,这里面就是exp_parfile.sql. 

注意这里面用forward slash (/)用作sed -e "..."中的分隔符,一般情况下可以正常work. 但是如果输入参数中包含了(/),比如说<dbname> 很可能是如下这种形式 10.12.14.15/orcl, 那么这种情况下,刚才的sed命令会遇到如下的错误, 

sed: -e expression #1, char 27: unknown option to `s' 

解决方法不是很难,可以用#来作为分隔符,如下所示, 

sed  - e "s# < dbname > # % dbname % #g"  - e "s# < dbuser > # % dbuser % #g"  - e "s# < systemstring > # % systemstring % #g"  - e "s# < dumppath > # % dumppath % #g" exp_parfile_template.sql  >  exp_parfile.sql

最后,还有个问题需要注意下,就是要对路径分隔符back slash(\)进行转义,否则最后得到的路径dumppath中的‘\’都没有了。 可以简单做如下操作...

 set dumppath=%CD:\=\\%

 就是将当前路径中的\替换成\\. 

关于batch,最后还有个小点注意下,就是pause命令。刚开始我还是用set /p方式来让用户随便输如隔啥再退出,后来发现可以用pause。无知真的很可怕。

好不容易是把dump给整出来了,接下来就是写batch来调用IMP导入dump,有了之前的经验教训,写起来问题倒是不大,主要碰到了一个IMP的问题,如下,

IMP-00015: following statement failed because the object already exists: 

 "CREATE TABLE xxx ... 

 默认在导入的时候会进行表的创建,但是我只是想导入数据,并不需要创建表。 查看了下IMP的参数,发现可以加上IGNORE=Y来解决这个问题, 

set  command = imp userid = ' %systemstring% '  fromuser =% fromuser %  touser =% touser %   file =% file :" = ' % grants=no constraints=y statistics=none buffer=16384000 log="%file%.imp.log"  ignore=y
echo %command%
cmd /C %command%

2. SQLPLUS 相关 

因为不是所有的东东都放到dump里面来导入,有大部分的东西是要通过script来运行,自然少不了在batch脚本中调用sqlplus了。下面总结下遇到的跟sqlplus相关的一些问题...

最开始遇到的问题也是如何进行参数的传递问题。因为入口是batch脚本,一些提示用户输入的参数都是在batch脚本里面设置的,如何在sqlplus基本(sql文件)里面捕获呢? 比如说如下这个问题,

用户提供了一些数据库连接信息 (用户名,密码, DB Name),自然是需要首先尝试进行数据库连接,看看用户输入的信息是不是正确的。那该如何解决呢? 

假如说我写了个TEST_CONNECT.sql脚本,这个脚本的功能很简单,就是进行数据库连接尝试,如果连接正常就返回‘OK'提示信息,如果连接不正常,说明用户提供的信息有误,就直接退出,很显然这个脚本是需要接受batch脚本传递过来的数据库连接信息的,我们知道sqlplus里面用’&‘来定义绑定变量,在运行的时候会自动提示用户进行输入。但是如果用户在调用sqlplus的时候传递进来参数,可否直接接受呢,就像下面这样, 这里&1自然就是要接收的数据库连接信息。

-- TEST_CONNECT.sql

WHENEVER SQLERROR
EXIT 1
set feed off echo off time off timing off heading off
connect
& 1
select ' OK ' from dual;
exit 0

注意这里面设置很多参数, feed off, echo off, etc, 目的就是为了如果连接正常,结果只需要显示OK就可以了,不用显示其他东东,比如heading之类。

这个测试连接情况的sqlplus脚本写好,下面的问题自然就是batch文件改怎么写,怎么调用这个TEST_CONNECT.sql, 如何捕获测试连接情况结果加以分析呢? batch脚本应该需要完成以下几个功能, 

(1) 如果连接失败,应该提示用户继续输入正确的参数,直到测试通过或者用户自己强行退出。

 (2)   以正确的方式调用sqlplus, 运行脚本test_connect.sql, 将运行结果保存到一个日志文件中以便于进行进一步分析处理。

关于如何调用sqlplus, 可以用 sqlplus -H 查看帮助文档,如下

C:\ > sqlplus - H

SQL
* Plus: Release 10.2 . 0.4 . 0 - Production

Copyright (c)
1982 , 2007 , Oracle. All Rights Reserved.

Usage
1 : sqlplus - H | - V

- H Displays the SQL * Plus version and the
usage help.
- V Displays the SQL * Plus version.

Usage
2 : sqlplus [ [<option> ] [ <logon> ] [ <start> ] ]

< option > is : [ -C <version> ] [ -L ] [ -M "<options>" ] [ -R <level> ] [ -S ]

- C < version > Sets the compatibility of affected commands to the
version specified
by < version > . The version has
the form "x.y
[ .z ] ". For example, - C 10.2 . 0
- L Attempts to log on just once , instead of
reprompting
on error.
- M " < options > " Sets automatic HTML markup of output. The options
have the form:
HTML
[ ON|OFF ] [ HEAD text ] [ BODY text ] [ TABLE text ]
[ ENTMAP {ON|OFF} ] [ SPOOL {ON|OFF} ] [ PRE[FORMAT ] { ON | OFF }]
- R < level > Sets restricted mode to disable SQL * Plus commands
that interact
with the file system. The level can
be
1 , 2 or 3 . The most restrictive is - R 3 which
disables
all user commands interacting with the
file system.
- S Sets silent mode which suppresses the display of
the SQL
* Plus banner, prompts, and echoing of
commands.

< logon > is : ( < username > [ /<password> ][ @<connect_identifier> ] | / )
[ AS SYSDBA | AS SYSOPER ] | / NOLOG

Specifies the
database account username, password and connect
identifier
for the database connection. Without a connect
identifier, SQL
* Plus connects to the default database .

The
AS SYSDBA and AS SYSOPER options are database administration
privileges .

The
/ NOLOG option starts SQL * Plus without connecting to a
database .

< start > is : @ < URL >|< filename > [ .<ext> ] [ <parameter> ... ]

Runs the specified SQL
* Plus script from a web server (URL) or the
local
file system (filename.ext) with specified parameters that
will be assigned
to substitution variables in the script.

When SQL * Plus starts, and after CONNECT commands, the site profile
(e.g. $ORACLE_HOME
/ sqlplus / admin / glogin.sql) and the user profile
(e.g. login.sql
in the working directory) are run. The files may
contain SQL
* Plus commands.

Refer
to the SQL * Plus User ' s Guide and Reference for more information.

发现有两个选项比较有意思-S 和-L。 前者是以silent方式来调用sqlplus,这样就不会显示平时调用sqlplus,会显示一大串sqlplus 数据库相关的版本信息啥, 后者则限制sqlplus只会调用一次。默认情况下sqlplus会提示3次,如果没有成功登陆oracle数据库的话。很显然,这个是我们batch脚本需要的! 另外还注意到/nolog选项,因为是在TEST_CONNECT.sql里面真正去连接数据库 connect &1; 因此在batch里面并不是要真正去登陆数据库,因此这个选项也是需要的。

于是,最后调用TEST_CONNECT.sql的batch脚本就出来了, 注意数据库连接字符串参数 "%SYSTEM_CONNECTION% AS SYSDBA" 直接放到@"TEST_CONNECT.sql"后面作为第一个传入参数,正好被TEST_CONNECT.sql中的 &1 catch 住。

-- Batch file to call TEST_CONNECT.sq1

@echo off
:sys_connection
echo.
set TEST_CONNECT_SYS = KO
set SYS_LOGIN = SYS
set SYS_PASSWORD = a
set DB = orcl

set / P SYS_LOGIN = "SYS login or equivalent ( default : % SYS_LOGIN % ) : "
set / P SYS_PASSWORD = "SYS password ( default : % SYS_PASSWORD % ) : "
set SYS_CONNECTION =% SYS_LOGIN %/% SYS_PASSWORD % @ % DB %
echo.
echo Checking sys connection. Please wait...
echo.
sqlplus
-SL /nolog @"TEST_CONNECT.SQL" "%SYS_CONNECTION% AS SYSDBA">TEST_CONNECT_SYS.LOG
for / F %% L in (TEST_CONNECT_SYS. LOG ) do set TEST_CONNECT_SYS =%% L
if not " % TEST_CONNECT_SYS % " == "OK" (
echo ERROR: SYS connection
is NOT OK.
goto sys_connection
)
echo.
echo SYS connection OK
echo.
pause

虽然在TEST_CONNECT.sql这样的脚本里面用&1, &2, ect来接收传进来的第一个,第二个,等等参数是可以work的,但是缺点也很明显,就是很容易就会忘记&1, &2..之类的究竟指代哪个参数,很不友好。有没有方法可以改进呢,答案自然是肯定的,可以用define,下面会有所展示。

再后来又碰到一个问题,就是如何将参数级联传递,也就是说我现在在batch里面定义了一些参数,然后我调用sqlplus运行一个脚本,将这些参数传递进去,不过这个脚本里面又需要调用另外一个脚本(权且叫第二个sqlplus脚本),如何让第二个sqlplus脚本也可以访问到在batch文件里面定义的参数呢? 其实想了想,不管是第一个sqlplus脚本文件,还是第二个(抑或是第N个)sqlplus脚本,大家都是在同一个sqlplus session里面,这些参数自然是可以共享的嘛。 

现在看下第一个sqlplus脚本,作用很简单,就是将batch脚本传递过来的参数重新"define"下,然后紧接着调用第二个sqlplus脚本文件...

-- SQL_SCRIPT_1.sql
 
/* ---------------------------------------------------------------------------------------
RE-DEFINE Variables
-----------------------------------------------------------------------------------------
*/
define DB_DIR
= & 1
define SYS_CONNECT
= & 2
define CENTRAL_NAME
= & 3
define CENTRAL_PWD
= & 4
define CENTRAL_INSTANCE
= & 5
define CENTRAL_CRYPTED_PWD
= & 6
define TBS_DATA
= & 7
define TBS_INDEX
= & 8
define TBS_TEMP
= & 9
define LOG_FILE
= & 10
define SEMANTIC
= & 11

spool
& LOG_FILE
set verify on echo on feed on


/* ---------------------------------------------------------------------------------------
CONNECT FOR MODEL SETUP
-----------------------------------------------------------------------------------------
*/

connect
& CENTRAL_NAME / " & CENTRAL_PWD"@ & CENTRAL_INSTANCE
alter session set events ' 38024 trace name context forever, level 1 ' ;
alter session set NLS_DATE_LANGUAGE = ' AMERICAN ' ;
alter session set NLS_NUMERIC_CHARACTERS = ' ., ' ;
alter session set NLS_LENGTH_SEMANTICS =& SEMANTIC;
alter user & CENTRAL_NAME default role none;

REM Call another script
IN CURRENT sqlplus SESSION
@@SQL_SCRIPT_2 .SQL

spool
off
exit

这个脚本大体上分成3个部分,最上面是将捕获的参数(&1-&11)重新define成有意义的变量名,这样在接下来的进行数据库的连接的时候,就不用conn &3/"&4"@&5这样了,而是用可读性更高的connect &CENTRAL_NAME/"&CENTRAL_PWD"@&CENTRAL_INSTANCE 方式调用。然后进行一些参数的设置(自然是在sqlplus里面执行),最后再调用另外一个script -- SQL_SCRIPT_2.sql 


For completeness 以下是batch脚本里面调用SQL_SCRIPT_1.sql的语句,

sqlplus / nolog @SQL_SCRIPT_1 .sql % DB_DIR % % SYS_LOGIN %/% SYS_PASSWORD % @ % DB % % CENTRAL_LOGIN % % CENTRAL_PASSWORD % % DB % % ENCRYPT_CENTRAL_PWD % % TBS_DATA % % TBS_INDEX % % TBS_TEMP % % OUTPUT_DIR % \ sql_script . log % SEMANTIC %

前面也提到了,SQL_SCRIPT_2.sql自然可以像SQL_SCRIPT_1.sql这样直接用这些参数,比如&CENTRAL_NAME之类。这里想多说一句的是,可以用sqlplus里面的NEW_VALUE来重新”define"变量名,如下所示, 

col CENTRAL_NAME new_value CENTRAL_NAME_NEW noprint
prompt
prompt Name
of central account to be created
select ' Using ' || ' &&CENTRAL_NAME ' || ' as central account name '
,
upper ( ' &CENTRAL_NAME ' ) CENTRAL_NAME
from sys.dual;

通过这样一个小技巧, 既可以起到提示的作用,又可以重新定义了一个参数名。 注意noprint将column CENTRAL_NAME不显示出来。 通过new_value将列central_name的值绑定到CENTRAL_NAME_NEW,那么可以在接下来的脚本中用&CENTRAL_NAME_NEW来指代central name了。 

最后顺便提个遇到的小问题, 也是关于&的,因为sqlplus在遇到&会提示输入变量值,除非像上面这样已经进行“绑定”了 (传递参数)。如果在像一个表中插入一个字符串中包含了&这个字符,而我们确实是希望有这个&符号的,而不让sqlplus来提示输入变量值,改怎么办呢,一个很简单的方法就是对sqlplus设置 set define off 开关,很显然这样做是“一棒子打死”的做法,所以要谨慎用之。 

最后再再顺便提个小问题,也是关于引用&绑定变量的。如果像下面这样定义一个变量TARGET_USER,值为数据库的一个schema, 这里是frank. 我的意图是要向frank.frank_test里面插入一条数据, 但是&TARGET_USER.frank_test 最后得到的却不是frank.frank_test, 而是frankfrank_test, 因此在这种情况下需要多加一个“.",像这样&TARGET_USER..frank_test

define TARGET_USER = frank

-- Warning! Should be &TARGET_USER..frank_test
INSERT INTO & TARGET_USER.frank_test VALUES ( ' frank ' );

好了,没有最后了,ending...

----------------------------------------

Update on 2011-3-16

---------------------------------------

在写一个很简单的bat文件的时候,遇到一个问题,bat文件如下...

:TEST_ACTIVE_SESSIONS
echo .
echo Please wait while verify whether there are some active sessions connected by %CENTRAL_LOGIN%
echo .
sqlplus -SL
/ nolog @ " TEST_ACTIVE_SESSIONS.SQL " " %SYS_CONNECTION% AS SYSDBA " " %CENTRAL_LOGIN% " > TEST_ACTIVE_SESSIONS . LOG
for / F %%L in ( TEST_ACTIVE_SESSIONS . LOG ) do set TEST_ACTIVE_SESSION = %%L
if not %TEST_ACTIVE_SESSION% == 0 (
echo .
echo ============================================================================================================
echo WARNING: There are %TEST_ACTIVE_SESSION% active session(s) connected using %CENTRAL_LOGIN% . Please logout these sessions and try again .
echo ============================================================================================================
color 0E
goto end_error
)
echo .
echo No active session detected . Upgrade is starting now ...
echo .

做的事情很简单,就是通过sqlplus连接到指定的数据库上,判断有没有通过给定schema登陆数据库的session存在,如果有的话,就打印出warning message,说明有多少session当前正在连接中, 因为session数有可能是1个也可能多于1个,因此我在写warning message的时候用了一个()把s括起来,表示有可能多于一个session, 也就是session(s)

但是没有想到的是,就是这个我认为很“专业”地处理这个细节的方法,让我吃了不少苦头。因为bat遇到这个if 判断的时候直接就退出了,并没有打印出warning message, 也没有继续往下执行!很是奇怪,搞了半天,才发现就是这个()惹得的祸,因为这个()位于if()的内部,这么多出来个()倒是batch语法不对,所以没有办法执行!! 真是崩溃啊!!! 有木有!!!!!最后只能把我只好把s两旁的()去掉了!!!


转载于:https://www.cnblogs.com/fangwenyu/archive/2010/10/16/1851643.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值