zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    JS原生带小白点轮播图
    JS原生轮播图
    Vue.js小案例(2)
    Vue.js小案例(1)
    Vuejs入门级简单实例
    Vue.js简单入门
    微信登录oauth2.0
    PHP四维数组、三维数组封装遍历
    常用linux命令30个
    好架构是进化来的,不是设计来的
  • 原文地址:https://www.cnblogs.com/fangwenyu/p/1851643.html
Copyright © 2011-2022 走看看