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两旁的()去掉了!!!





--------------------------------------
Regards,
FangwenYu
原文地址:https://www.cnblogs.com/fangwenyu/p/1851643.html