gyn

Win32下的Perl,无用的select,停滞的Tk,结束吧....

pl/sql异常处理(2)

Oracle:pl/sql 异常处理( 2

pl/sql 提供了强大而灵活的手段来捕捉和处理程序产生的异常,从而使 oracle 的用户远离一些令人烦恼的 bug

 

异常定义

 

在一个异常产生、被捕获并处理之前,它必须被定义。 Oracle 定义了几千个异常,绝大多数只有错误编号和相关描述,仅仅命名了若干个最常被用到的异常。这些名字被储存在 STANDARD UTL_FILE DBMS_SQL 这几个系统包中,详情请见 oracle:pl/sql 异常处理( 1 )。

出自之外的绝大多数异常需要程序员命名。有 2 种命名异常的方法:

 

1 :声明一个自定义异常

STANDARD 中的命名了的异常基本山是与系统的错误相关的(当然那些只有 errorcode 的异常也是这样),但在实际的应用中我们经常需要与特定的应用程序相关的异常,由程序员声明的异常就是用于处理这种情况的。

Oracle 异常处理模块的方便的地方在于,它并没有区别对待自定义的与预定义的异常。这使得我们可以像对待预定义异常一样,捕捉和处理自定义异常,只是在此之前需要声明它;同时对于一个自定义的异常,我们需要用 RAISE 来手动产生。

下面是一个声明的例子:

       procedure calc_ammul_sales

              (company_id_in in company.company_id%tye)

       is

              invalid_company_id exception;

              negative_balance excrption;

              duplicate_company Boolean;

       begin

              /*body of executable statement*/

       exception

              when invalid_company_id

              then /*handle exception*/

              when no_data_found

              then /*handle exception*/

              /*…..*/

       end;

需要注意的是处理定义的时候,只有两个地方会出现自定义的异常:

ü         raise exception

ü         when exception then

 

2 :为非预定义异常关联一个名字

仅仅 21 个预定义异常对我们来说实在是太少了,还有几千个异常只有 errorcode 和描述。另外,程序员也可以用 RAISE_APPLICATION_ERROR 定义一个含 errorcode 和描述的异常。

当然,只用 errorcode 也可以很好地完成工作,只要你不担心会忘了那串数字代表的意思就行。比方说 ;

       exception

              when others

              then

                     if sqlcode=-1843 then  /*sqlcode 是内建的用于返回最近一次错误编号的函数 */

                     …..

这的确是一段让人感到晦涩的代码,还是给它关联个名字吧。

我们要用到的是 pragma exception_init(exception,integer) ,然后就可以像对待预定义异常一样对待它了,我是说没必要像上面的那种一样用 raise Exception_init 是一个编译时运行的函数,它只能出现在代码的声明部分,而异常名字必须在此之前被定义。下面用一个匿名过程举个例子:

       declare

              invalid_company_id exception;

              pragma exception_init(invalid_company_id, -1834);

要注意的时:

ü         不可以用 -1403 no_data_found ),用 100 ,事实上 exception_init 中的 integer 对应的是 sqlcode 返回的值。

ü         不能为 0 ,不能大于 100 ,不能小于 -1000000

一个例子:

       procedure delete_company(company_id_in in number)

       is

              still_have_emplyee exception;

              pragma exception(still_have_employee, -2293);

       begin

              delete from compamy

              where company_id=company_id_in;

       exception

              when still_have_employee

              then dbms_output.put_line(‘delete employees for company first’);

       end;

在一下两种情况下,我们有必要使用 exception_init

ü         一个非预定义异常是经常要被用到的。

ü         我们将用 raise_applocation_error 产生了一个自定义的 errorcode 时。

一种简便的方法是将以上两种情况中的异常定义在一个包中,这样我们就没有必要每次都重复定义了。

       Create or replace package dynsql

       Is

              Invalid_table_name exception;

              Pragma exception_init(invalid_table_name, -903);

              Invalid_column_name exception;

              Pragma exception_init(invalid_column_name, -904);

              En_too_young const number:=-200001;

              Exc_too_young exception;

              Pragma exception_init(exc_too_young, -20001);

       End;

有了上面这个包,就可以方便的处理异常了 ;

       procedure validate_emp(birthdate in date)

       is

              min_tear const pls_integer:=18;

       begin

              if add_month(sysdate,min_year*12*-1)<birthdate_in

              then

raise_application_error(dynsql.en_too_young, ‘employee must be’ || min_year ||‘old’);

              end if;

       end;

除了 standard 包中的 21 个预定义异常外,还有一些包也定义了一些异常。但与 standard 包中异常不同的是,在使用这些异常时,需要带上包的名字。如:

       when dbms_lob.invalid_argval then ……

 

非常有用的一点是,可以在最外层的 pl/sql 块的异常处理模块中加入 others ,这样就可以把从内部传递出来的未被处理的剩余异常全部处理掉了。

       Exception

       When others

              Then ….

posted @ 2006-04-23 21:31 gyn_tadao 阅读(755) | 评论 (0)编辑 收藏

pl/sql异常处理(1)

Oracle:pl/sql 异常处理( 1

pl/sql 提供了强大而灵活的手段来捕捉和处理程序产生的异常,从而使 oracle 的用户远离一些令人烦恼的 bug

 

pl/sql 异常处理的概念和术语

 

oracle 中所有的错误都被认为是不应该发生的异常。一个异常可能是以下 3 种情况的一种:

u       由系统产生的错误(“ out of memory ”或“ duplicate value in index ”)

u       用户行为导致的错误

u       应用程序给用户的一个警告

pl/sql 用一种异常句柄的结构来捕捉和响应错误。正是有了异常句柄的存在,我们能很方便的分离异常处理代码与可执行代码。与线性的代码相比,为了处理异常,异常句柄提供了一种类似事件驱动的模式;换句话说,就是不管一种特定的错误在何时何地发生,它都将被同一个代码处理。

当一个错误出现后,无论它是系统还是程序产生的,都将导致一个异常。之后,可执行程序被中断,控制权转移给异常处理代码。处理完异常后,程序将不会回到先前被中断的位置,相反的,控制权被交给了当前程序的外围模块(可能是程序,也可能是系统)。

       procedure jimmy

       is

              new_value varchar(35)

       begin

|--------new_value:=old_value || ‘-new’;

|          if new_value like ‘like%’

|          then

|                 …..

|          end if;

|   exception

|----- à        when value_error

              then

                     …..

       end;

因为 old_value 是一个未被定义的变量,所以将产生一个错误,并将给异常处理模块处理。

 

从异常的可应用范围出发,可将异常分为两类:

系统异常:

oracle 定义并由 pl/sql runtime 引擎在检测到错误时产生的异常。一些系统异常有名字,比如 NO_DATA_FOUND ,然而大多数的异常仅仅只有数字编号和描述。这些异常无论在哪个 pl/sql 中程序都能被应用。

共有 21 个命名的系统异常:

命名的系统异常

产生原因

ACCESS_INTO_NULL

未定义对象

CASE_NOT_FOUND

CASE 中若未包含相应的 WHEN ,并且没有设置 ELSE

COLLECTION_IS_NULL

集合元素未初始化

CURSER_ALREADY_OPEN

游标已经打开

DUP_VAL_ON_INDEX

唯一索引对应的列上有重复的值

INVALID_CURSOR

在不合法的游标上进行操作

INVALID_NUMBER

内嵌的 sql 语句不能将字符转换为数字

NO_DATA_FOUND

使用 select into 未返回行,或应用索引表未初始化的元素时

TOO_MANY_ROWS

执行 select iotn 时,返回超过一行

ZERO_DIVIDE

除数为 0

SUBSCRIPT_BEYOND_COUNT

元素下标超过嵌套表或 VARRAY 的最大值

SUBSCRIPT_OUTSIDE_LIMIT

使用嵌套表或 VARRAY 时,将下标指定为负数

VALUE_ERROR

赋值时,变量长度不足以容纳实际数据

LOGIN_DENIED

Pl/sql 应用程序连接到 oracle 数据库时,提供了不正确的用户名或密码

NOT_LOGGED_ON

Pl/sql 应用程序在没有连接 oralce 数据库的情况下访问数据

PROGRAM_ERROR

Pl/sql 内部问题,可能需要重装数据字典& pl./sql 系统包

ROWTYPE_MISMATCH

宿主游标变量与 pl/sql 游标变量的返回类型不兼容

SELF_IS_NULL

使用对象类型时,在 null 对象上调用对象方法

STORAGE_ERROR

运行 pl/sql 时,超出内存空间

SYS_INVALID_ID

无效的 ROWID 字符串

TIMEOUT_ON_RESOURCE

Oracle 在等待资源时超时

 

由程序员定义的异常:

程序员在程序中定义的异常,它只是在特定的程序种有效。可以使用 EXCEPTION_INT 这个 pragma 将一个无名字的系统异常与一个程序员定义的名字相关联。或者用 RAISE_APPLICATION_ERROE 来自己定义一个异常的数字编号和描述。

 

按异常生成方式可分为:

预定义异常:

       就是上面表中的 21 种有名字的系统异常。

非预定义异常:

       没名字的系统异常,可以用 pragma exception_int 给它关联一个名字。

自定义异常:

       需要用 RAISE RAISE_APPLICATION_ERROR 生成的异常。

 

下面是一些要用到的属于 ;

Exception section (异常处理模块)

它是 pl/sql 语句块种包含一个或多个异常句柄的部分。 Exception section 的结构基本上与 case 相似。

Raise (产生)

通过通知 pl/sql runtime 引擎有错误来中止当前程序的运行。也可通过显式的请求,如: RAISE RAISE_APPLICATION_ERROR RAISE 一个异常。

Handle (句柄,某一个异常处理的代码)

exception section 中捕捉错误。可以在 handle 中编写程序来处理异常,比如将错误记入 log 中,显示一个错误信息,将异常传出当前程序快。

Scope (作用范围

       一个异常从产生、被捕捉到处理整个过程所处的程序部分。

Propagation (传递)

如果一个异常没有被处理,那么它将被传递到但前块的上一级,它有可能是另一个代码快,也可能是系统。

Unhandled exception (未被处理的异常)

如果一个异常没有被处理,并一直被传递道理系统中,那么它被称为 unhandled exception

Un-named or anonymous excepttion (匿名异常)

       (在异常类型中有介绍)

Named exception (命名异常)

       包括系统异常中有名字的那部分和用户定义的名字。

posted @ 2006-04-23 15:36 gyn_tadao 阅读(1255) | 评论 (1)编辑 收藏

plsql trunc(with date)函数的介绍

(网上有很多荒谬的中文介绍,可以去google看看。)

在pl/sql中,trunc函数返回由指定测量单位截取的时间值。
trunc函数的语法如下:
      trunc(date, [format])
date就是要被截取的时间值。
format是用于截取的测量单位。如果省略的话date将被截取到day的精度。
format参数表:
   unit                                          valid format parameters
   Year                              SYYY,YYYY,YEAR,SYEAR,YYY,YY,Y
   ISO YEAR                                       IYYY,IY,I
   Quarter                                                      Q
   Month                                       MONTH,MON,MM,RM
   Week                                                      WW
   IW                                                            IW
   W                                                             W
   Day                                                      DDD,DD,J
   Start day of week                                 DAY,DY,D
   Hour                                                HH,HH12,HH24
   Minute                                                    MI
例子:
trunc(to_date("22-AUG-03"), "YEAR")      返回‘01-JAN-03’
trunc(to_date("22-AUG-03"), "Q")      返回‘01-JUL-03’
trunc(to_date("22-AUG-03"), "MONTH")      返回‘01-AUG-03’
trunc(to_date("22-AUG-03"), "DDD")      返回‘22-AUG-03’
trunc(to_date("22-AUG-03"), "DAY")      返回‘17-AUG-03’

详情请见http://www.techonthenet.com/oracle/index.php

posted @ 2006-04-16 16:20 gyn_tadao 阅读(5454) | 评论 (2)编辑 收藏

eclips和active perl建立perl开发环境

听说有人用eclips在写perl,好像不错的样子。于是去http://www.eclipse.org上下了个28M的platform版的(先要装perl的,我的是5.6.1)。按照http://e-p-i-c.sourceforge.net/里的说明文档,单击help=>点击software updates=>点击find and install=>选择search new feature to install=>单击new remote site=>name中填写Epic,URL中填写http://e-p-i-c.sourceforge.net/updates/=>然后一路NEXT,搞定。很漂亮的开发环境,不过没看出方便来,反而感到麻烦,基本属于华而不实。
o_eclips.jpg

posted @ 2006-04-14 17:03 gyn_tadao 阅读(661) | 评论 (0)编辑 收藏

perl编程: 浅谈typeglob

浅谈 typeglob

本文是对《 advanced perl programming edition 2 中有关 typeglob 的叙述,但不是一字一句的直译,又不能讲就是意译,因为加了一点个人浮浅的理解,所以叫浅谈。

 

 

符号表

当程序使用一个全局变量时, perl 解析器会在一个符号表中查找这个变量名。可以这么认为:符号表完成了变量的名字到实际存储区域的映射。

                           Symbol table

                           |    A     | -------- à | 3 |

                           |    B     |--------- à | 2 | 

                           |    C     |

                           |    D     |

1

请注意,是变量的名称而不是变量,这一点很特别,可以说符号表中 a 映射了一个到 $a 的内存区域,实际的情况更复杂。 perl 中有几种基本数据结构: $a @a %a &a 和文件或目录句柄 a ,它们都有同样的变量名称只是前缀不一样,于是就有了 glob 的概念。

o_2.jpg

         2

正如上图所示符号表把 b 映射一个 glob 。对 glob 的描述大概可以这么讲:它是包含了各个名为 b 的变量的引用的 hash 结构,它叫做 *b

*b{SCALAR}=\$b;

*b{HASH}=\%b;

*b{ARRAY}=\@b;

…….

够诡异的了!

 

别名

很明显 glob 把名称与引用无情地分开了,好处大概就是可以很方便地取别名。简单地把 *b 赋值给 *c ,就是 *c=*b ,产生地结果就是这样:

o_3.jpg
                                     3

两个名称指向了同一个 glob ,现在这个 glob 既叫 *b 又叫 *c

现在应该有些思路了,举个例子说: %c 地解析过程可以这么认为, perl 先在符号表中找到 c 所映射的 glob ,然后在 glob 中找到前缀为 % 的引用,最后返回存储位置。最常见体现这个思想的代码如下。

package Some::Module;

  use base ‘Exporter’;

  our @EXPORT=qw(useful);

  sub useful{42;}

Exporter 的作用就是将 useful 从包中传出到调用者那里。基本的工作原理如下。

  package Some::Module;

  sub useful{42;}

  sub import{

  no strict ‘refs’;

  *{call().”::useful”} = *useful;

  }

import 子程序在包被 use 时将自动被调用。在调用者代码中的 useful 子程序最终将被指向 Some::Module::useful

o_4.jpg
                                4

之所以要用 no strict ‘ref’; 是因为如果调用者的代码中使用了 use strict 的话,将产生错误。把上面的代码再简单点比喻一下就是:

  $answer =42;

  $variable = “answer”;

  print ${$variable};

一个道理。

 

分解 glob

在上面的这个例子中,我们将 useful 的名称映射到了 Some::Module::useful glob (或者说为 Some::Module::useful glob 取了个别名),这会带来一些影响。比如说:

  use Some::module;

  our $useful=”Some handy string”;

  print $Some::Modile::useful;

因为 useful Some::Modile::useful 映射同一个 glob 的关系,输出的结果是 ”Some handy string” 。但实际的情况是,我们只希望 useful 指向子程序 Some::Modile::useful ,而不是整个 glob Perl 提供了映射部分引用的办法。如果是希望仅仅映射标量或数组的话,可以这样:

  ${caller(  )."::useful"} = $useful;

  @{caller(  )."::useful"} = @useful;

但是对于子程序,如果也这么做:

  &{caller(  )."::useful"} = &useful;

首先 perl 会运行 &usefule 得到 42 ,然后将 42 赋值给运行 &{caller(  )."::useful"} 所产生的结果,但 &{caller(  )."::useful"} 根本是不存在的,于是错误产生了。为了解决这个问题, glob 这个诡异的机构提供了一个重载过的“ = ”方法。像 *b=\@c 的操作可以给 *b 添加一个到 @c 的引用。 @b @c 任何一方的变化都将反映对方上。

o_5.jpg

5

*b 中除了又一个引用指向 @c 外,其他的引用都没有变化。正是有了“ = ”这个方法,我们可以随心所欲在 glob 中指定引用。

  *a = \"Hello";

  *a = [ 1, 2, 3 ];

  *a = { red => "rouge", blue => "bleu" };

  print $a;        # Hello

  print $a[1];     # 2

  print $a{"red"}; # rouge

后一个对 *a 的赋值并没有替换前面的赋值,只是加入了个不同类型的引用。有一个不得不讲的例外,如果 *a 被加入了一个到常量的引用,那么该变量的值将是不能改变的。

  *a=\1234;

  $a=10;  

这么做是徒劳的, perl 将返回“ modification of a ready-only value attempt ”的错误信息。

现在可以着手解决本节开始时遇到的那个问题了:

  sub useful{42}

  sub import{

no strict ‘refs’;

*{caller().”::userful”} = \&useful;

}

这已经跟 Exporter 的实际工作原理很接近了。

 

Exporter 核心代码的分析

  my $pkg = shift;

  my $callpkg = caller($ExportLevel); $ExportLevel = 0

  foreach $sym(@imports){

(*{${callpkg}::$sym}) = \&{“${pkg}::$sym”}, next)

  unless $sym =~ s/^(\W)//;

  $style = $1;

  *{${callpkg}::$sym}) =

$style eq ‘&’ ? \&{“${pkg}::$sym”} :

$style eq ‘$’ ? \${“${pkg}::$sym”} :

$style eq ‘@’ ? \@{“${pkg}::$sym”} :

$style eq ‘%’ ? \%{“${pkg}::$sym”} :

$style eq ‘*’ ? *{“${pkg}::$sym”} :

do {require Carp; Carp::croak(Can’t export symbol:$sym)};

  }

我们通过 @Export 数组传入需要输出的变量。如果变量不带前缀,那么直接在最顶层调用名称的 glob 中加入被引用模块相应子程序的引用。如果有前缀,那么将前缀去掉。

  (*{${callpkg}::$sym}) = \&{“${pkg}::$sym”}, next)

  unless $sym =~ s/^(\W)//;

去掉的前缀将被赋值给 $style ,然后检验 $style 的类型,并具此返回相应类型的引用。

  $style = $1;

  *{${callpkg}::$sym}) =

$style eq ‘&’ ? \&{“${pkg}::$sym”} :

$style eq ‘$’ ? \${“${pkg}::$sym”} :

$style eq ‘@’ ? \@{“${pkg}::$sym”} :

$style eq ‘%’ ? \%{“${pkg}::$sym”} :

$style eq ‘*’ ? *{“${pkg}::$sym”} :

do {require Carp; Carp::croak(Can’t export symbol:$sym)};

如果传入的值与以上各种情况都不匹配,那么就调用 croak 中止顶层程序。

 

使用 glob 建立子程序

glob 分配一个到匿名子程序的引用是别名技术在高级 perl 编程中的一个普遍应用。举个例子:有一个叫 Data::BT::PhoneBill 的模块,它被用于在英国电信公司的电话帐单服务中检索数据。这个模块将一个电话中的信息按照逗号分开,并将它们对象化。

  package Data::BT::PhoneBill::_Call;

  sub new{

  my ($Class,@data) = @_;

  bless \@data, $Class;

}

sub installation {shift->[0]}

sub line {shift->[1]}

…..

这样做的结果是不大好维护。如果我们打算在在数据的开头处添加一个新的条目,那么就需要修改大半块代码,将数组编号集体下移。为了避免这种情况的发生,应用 hash 来替换不方便的 array

  our @field = qw(type installation line chargecard _data time destination _number duration _rebat cost);

  sub new{

my ($class @data) = @_;

bless {map {$field[$_] => $data[$_]} 0..$#field => $class;}

  }

  sub type {shift->{type}}

  sub installation {shift->{installation}}

  ….

只需要在代码中加入重复的 3 个词,我们就可以为模块添加一个新的条目。这比上面那个方便不少,但是如果待加入的条目名称是这样的: friend_and_family_duscount ,那么重复 3 遍的工作也够麻烦的了。于是就要用到 glob

  Foreach my $f(@field){

    no strict ‘refs’;

*f = sub { shift->{$f}};

  }

posted @ 2006-04-13 09:23 gyn_tadao 阅读(3473) | 评论 (2)编辑 收藏

perl编程: 用SOCKET来传输文件的PERL脚本

#SERVER
use strict;
my $port=$ARGV[0];
my $file=$ARGV[1];
my $PF_INET=2;
my $SOCK_STREAM=1;
my $local_addr=pack('SnC4x8',$PF_INET,$port,192,168,138,105);
socket(FILE_TRANS_SERV,$PF_INET,$SOCK_STREAM,getprotobyname('tcp')) or die("socket failed for $!");
bind(FILE_TRANS_SERV,$local_addr) or die("bind failed for $!");
listen(FILE_TRANS_SERV,3);
open(FILE,$file) or die("open failed for $!");
binmode(FILE);
seek(FILE,0,0);
for(;my $remote_addr=accept(FILE_TRANS_CLIENT,FILE_TRANS_SERV);close(FILE_TRANS_CLIENT)){
while(!eof(FILE)){
read(FILE,my $buffer,100);
send(FILE_TRANS_CLIENT,$buffer,0);
}#while
}#for
close(FILE_TRANS_SERV);
close(FILE);
1;
#CLIENT
use strict;
my $port=$ARGV[0];
my $file=$ARGV[1];
my $PF_INET=2;
my $SOCK_STREAM=1;
my $remote_addr=pack('SnC4x8',$PF_INET,$port,192,168,138,105);
socket(FILE_TRANS_CLIENT,$PF_INET,$SOCK_STREAM,getprotobyname('tcp')) or die("socket failed for $!");
open(SFILE,">$file") or die("file creation failed for $!");
binmode(SFILE);
connect(FILE_TRANS_CLIENT,$remote_addr) or die("connection failed for $!");
while(<FILE_TRANS_CLIENT>){
print SFILE $_;
}
close(FILE_TRANS_CLIENT);
close(SFILE);
1;
my用得有点乱,还好运行一些正常。
在网上没找不到这个主题的perl代码,所以自己写了个。
至于为什么用在while里用read(FILE,my $buffer,100),是怕如果一次全读入并传过去,内存会受不了。

posted @ 2006-04-06 16:03 gyn_tadao 阅读(1566) | 评论 (1)编辑 收藏

一次奇怪的网络瘫痪事件的始末

   2006年3月31日上午8点起,陆续有人通知上不了网,一一解决。
   中午12点10分,新闻部的采编系统瘫痪,大约有20台电脑无法进入采编系统,也无法上网。经检查,无法ping通网关。于是停用网卡,又重新启用,无效。更改ip地址,恢复正常。后发现重新启动也可回复正常。由此推断,问题可能出在交换机上,于是将连接新闻部的一个接入层交换机华为2403重启,为保险起见也重启了核心交换机华为3526E。20台电脑均恢复正常。
   以为就此一切恢复,但事情并未向想象的方向发展。20分钟后,故障重新出现,并在1小时之内扩展到了整个大楼和另外一幢大楼的某些楼层,大约波及150台左右电脑。
   既然问题的原因并非交换机,于是将对象锁定于isa2004防火墙上。首先立即扫描计算机清楚病毒,但未发现一个病毒。重启防火墙,仍然没用。查看防火墙日志,发现一个非法ip的欺骗攻击,还有一些外网的全端口扫描,没放在心上。
   由此开始,陷入迷茫中。4点左右,经过冷静分析,发现一个重要线索,所有问题计算机都处于同一网段中。回想isa2004中的记录,很明显,该网段有电脑在不断抢占ip地址,产生大量ip冲突,导致网络瘫痪。
   于是立即针对平时出问题较多的几台电脑,进行杀毒。5点下班,网络回复正常,这更坚定了我的判断,因为下班后那台罪魁祸首定是被关闭了,ip冲突结束。但找不到那台电脑,问题依然存在。
   周末休息,看来只有等到星期一了。
   周六晚在网上请教了几个朋友,众说纷纭。后来有一人的说,可以查看一下交换机日志.....于是按照所讲方法查看交换机。令人感到无比高兴的是,日志里显示了无数的collision,经分析断定问题出在e0/2中一台mac地址是00e04c4596c1的电脑上。查看arp,发现该mac地址相关的ip地址一直在不断地变换。问题根源找到,可以安心睡觉了。
   周一上午一上班,立即排查e0/2口的病毒网段电脑的mac地址,很快确定了是一台广告部的电脑。将其断线。现在网络恢复正常。

posted @ 2006-04-03 15:49 gyn_tadao 阅读(552) | 评论 (1)编辑 收藏

利用批处理文件和vbs脚本实现网站视频自动录制

利用批处理文件和 vbs 脚本实现网站视频自动录制

关键字: helix vbs 脚本,批处理,压码参数文件

 

现在电视台基本上都有了自己的门户网站,我们可以充分利用网络的优势来扩大本地电视台的影响。事实上,将本地电视台的每日的王牌节目挂在网站上的做法已经成为建立电视台网站的必要组成步骤之一了。但随之而来的问题是:每天定时需要的手工录制过程,将给管理者带来极大的不便。在 windows 定时任务的基础上,将批处理程序与脚本程序有机的集合,就可以有效地解决这一问题。

 

1 :建立压码任务

我们将使用一台带有视频采集卡的电脑来完成压码过程。虽然压码不是本文的重点,但还是有必要交代一下。主要就是使用 helix producer plus9 建立一个压码参数文件,事实上它只是一个带有 rpjf 后缀的 xml 文件(图 1 )。需要注意的是,音频输入选项有线路输入与 microphone 输入两种,这需要与实际的接入线路相吻合;另外,不必选择 2 pass vedio encoding 选项,选择它除了显示一个 warning 外不会有任何效果;再者,因为现阶段绝大多数的网络接入模式为拨号和 dsl ,所以只需要生成 56k 384k 码率的视频文件;最后,录制完成的视频文件,其名称最好用日期来标识,比如: 20060301.rm

<?xml version="1.0" encoding="UTF-8"?>
<job xmlns="http://ns.real.com/tools/job.1.0.1">
  <enableTwoPass type="bool">false</enableTwoPass>
  <clipInfo>
    <entry>
      <name>Author</name>
      <value type="string">ZSTV</value>
    </entry>
    <entry>
      <name>Copyright</name>
      <value type="string">(c) ZSTV2006 </value>
    </entry>
    <entry>
      <name>Keywords</name>
      <value type="string">ZSNews[2006-03-21]</value>
    </entry>
    <entry>
      <name>Title</name>
      <value type="string">ZSNews[2006-03-21]</value>
    </entry>
  </clipInfo>
  <input>
    <captureInput>
      <audioDeviceID type="string">Intel(r) Integrated Audio</audioDeviceID>
.................................... 

我们可以建立一个名为 shixian.bat 的批处理文件来的调用上面所示的参数文件,内部具体如下:

producer -j "d:\shixian\shixian.rpjf" -daw -lc "e,i"

默认的录制过程会实时监控音频变化,并将它记录到一个叫 producer.log 的文件之中,但是产生的 wanging 记录很多,很快就使 log 文件变得巨大而且可能引起录制过程的意外中断,所以我们可以使用 -daw 参数关闭音频监视,再用 lc “e,i” 指明只记录错误和信息。这样做之后,录制过程就比较稳定了。

只要在计划中添加每日执行 shixian.bat 文件的任务,我们就会每天得到一个相同文件属性和名称的 rm 文件。在这里将每天产生一个带有 2006-03-21 属性的、名为 20060321.rm 文件。听起来很拗口,更糟糕的是这并没有减轻管理者的工作,因为他不得不每天打开服务器为其改名称。而且即便有幸雇佣了一名勤奋的管理员(他不介意每天做这样重复的事情),但他还是没办法更改文件中显示日期的属性,在这录制过程中已经决定了。除非 …….

 

2 :建立修改压码参数文件的任务

原理很简单:每天更新压码参数文件中与日期相关的 xml 字段。在这个例子里是文件显示日期的属性和文件名。 下面就是用于更新日期字段的脚本代码:

dim regOR

set regOR=new regexp           建立一个正则表达式对象

regOR.ignorecase=True           忽略大小写

regOR.global=True              搜索应用于整个字符串

regOR.pattern="^([0-9]{1})$"     建立搜索模式为单数字表达式

dim mytime

mytime=date                   date 函数达到系统日期,并赋值给 mydate

yy=year(mytime)                提取系统日期中的年份

mm=month(mytime)             提取系统日期中的月份

dd=day(mytime)                提取系统日期中的日期

if regOR.test(mm) then           检测月份是否为单数字(一月到九月)

mm=regOR.replace(mm,"0$1")    如果是,则在月份前添加 0 使其成为两个数字

end if

if regOR.test(dd) then            检测日期是否为单数字(一日到九日)

dd=regOR.replace(dd,"0$1")      如果是,则在日期前添加 0 使其成为两个数字

end if

mytime=yy & "-" & mm & "-" & dd   合并年月日为 ”yy-mm-dd” 形式

dest=yy & mm & dd                合并年月日为 ”yymmdd” 形式

pattern1="(.*[^0-9])([0-9]{4}\-[0-9]{2}\-[0-9]{2})([^0-9].*)"

设定模式变量 pattern1 ”%d%d%d %d- %d %d -%d %d”

pattern2="(.*[^0-9])([0-9]{8})([^0-9].*)"  设定模式变量 pattern2 8 个连续数字

regOR.pattern=pattern1           建立搜索模式为 pattern1

dim tempstring

Set fso = CreateObject("Scripting.FileSystemObject")   建立系统文件对象

Set f = fso.CreateTextFile("d:\shixian\temp.txt", True)   新建名为 temp 的文本文件

Set fr = fso.GetFile("d:\shixian\shixian.rpjf")          得到编码参数文件 shixian.rpjf

Set ts = fr.OpenAsTextStream(1,-2)                 将参数文件以只读文本形式打开

do until ts.AtEndOfStream                 建立循环,直到参数文件读取完毕

tempstring=ts.readline                    以行为单位读取参数文件

if regOR.test(tempstring) then              搜索改行参数是否存在日期字段

tempstring=regOR.replace(tempstring,"$1" & mytime & "$3")  如存在,则更新

end if

regOR.pattern=pattern2                   建立搜索模式为 pattern2

if regOR.test(tempstring) then              搜索改行参数是否存在日期字段

tempstring=regOR.replace(tempstring,"$1" & dest & "$3")  如存在,则更新

end if

regOR.pattern=pattern1                  重新建立搜索模式为 pattern1

f.writeline(tempstring)                   将更新后的数据写入 temp.txt 文件

loop

ts.close                               ‘以下是关闭和注销各个对象

f.close

set regOR=nothing

set ts=nothing

set fr=nothing

set f=nothing

set fso=nothing

要说明的是为什么要在单数字的月份和日期前加 0 。打个比方比较容易说明:如果不加 0 ,那么对于一个名为 2006111.rm 的文件,它究竟是指 2006 1 11 日还是 2006 11 1 日呢?加 0 后变成 20060111.rm 就一目了然了,是前者。

以上这段脚本代码的具体功能是将参数文件中的数据以行为单位导入到一个临时的文本文件中,实时监视与时间相关的 xml 字段并加以更新,最后在生成一个 temp.txt 的文件。之后我们要做的无非就是用一个批处理文件调用脚本,再删除旧的参数文件,最后将 temp.txt 重命名为参数文件。

这就是 update.bat 批处理文件中的代码:

@echo off

d:\shixian\update.vbs

if exist "d:\shixian\shixian.rpjf" del "d:\shixian\shixian.rpjf"

if exist "d:\shixian\temp.txt" rename d:\shixian\temp.txt shixian.rpjf

和第一节中一样,还是要将这个 update.bat 的批处理文件添加到计划与任务中,以便每日定时启动。

 

3 :建立自动传输任务

为了确保稳定,通常压码服务器与网站服务器是分离的,这就需要我们每日将录制的视频文件传输到网站服务器上。批处理文件中的 ftp 命令可以完美的实现这一功能。

以下就是 transfer.bat 批处理中的代码:

@echo off

ftp -s:media.txt

if exist "d:\shixian\shixian20060321.rm" del d:\shixian\shixian20060321.rm

考虑到 SCSI 硬盘惊人的价格,压码服务器的硬盘空间不必太大,最后一行的作用就是删除压码服务器中的视频文件,以节省空间。 Media.txt 是一个 ftp 参数文件,其中记录了 ftp 登陆服务器的名称、用户名、密码和相关的操作,具体形式如下:

open / 服务器名称或 IP 地址 /

/ 用户名 /

/ 用户密码 /

put / 要传输的文件名 /

quit

很明显,以上的各段代码中不乏需要更新的日期文字表达式。同样的道理,我们也需要使用一个类似的更新脚本,具体代码就不重复了。

 

4 :一些改进的意见

1 :使用 media player 也可实现压码的功能。

2 :在更新脚本中,用在声明和注销对象上的代码有好几行,如果使用 perl 可以大为简化。因为是 perl 提供了一系列操作符号来实现正则表达式的功能,而事实上 perl 就是为了处理文本而生的。
open(RPJF,"<c:/code/10161.rpjf") or die("can not open because of $!\n");
open(TEMP,">c:/code/temp.txt") or die("can not open because of $!\n");
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime();
$mday=sprintf("%.2d",$mday);
$mon=sprintf("%.2d",$mon+1);
$year+=1900;
$timerF="$year"."$mon"."$mday";
$timerS="$year"."-"."$mon"."-"."$mday";
while(<RPJF>){
chomp();
$_=~s/(.*)([0-9]{8})(.*)/$1$timerF$3/;
$_=~s/(.*)([0-9]{4}\-[0-9]{2}\-[0-9]{2})(.*)/$1$timerS$3/;
print TEMP "$_\n";
}
close(RPJF);
close(TEMP);
3
:使用磁盘映射能够更加方便的实现文件的传输,不过需要考虑安全问题。

posted @ 2006-04-03 14:50 gyn_tadao 阅读(825) | 评论 (0)编辑 收藏

仅列出标题
共11页: First 3 4 5 6 7 8 9 10 11 
<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

导航

统计

常用链接

留言簿(15)

随笔分类(126)

随笔档案(108)

相册

搜索

最新评论

阅读排行榜

评论排行榜