[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程批处理在线视频分享
返回列表 发帖

[原创教程] [连载]《Intermediate Perl》学习笔记

本帖最后由 PerlMonk 于 2017-4-18 14:37 编辑

大骆驼书已经过了两遍,书柜上的一本《Perl 进阶》纯属情怀,中文版,这几天翻阅发现其中的翻译惨不忍睹,语无伦次。
改看英文版,顺便做一些摘要总结,这样回顾的时候方便一些。

一楼占坑

暂时使用这段 Perl 将 markdown的部分风格转 BBCode
  1. use utf8;
  2. use Encode;
  3. use IO::Handle;
  4. STDOUT->autoflush(1);
  5. my $file = encode('gbk', "Perl进阶-基础.md");
  6. open my $fh, "<:utf8", $file or die "$!";
  7. my %lang_fmt = (
  8.     'perl' => 'pl',
  9.     'python' => 'py',
  10.     'c' => 'c',
  11.     'cpp' => 'cpp',
  12.     'ruby' => 'rb',
  13. );
  14. my @topics;
  15. my $lang;
  16. my $indent = 0;
  17. while ( my $line = <$fh> )
  18. {
  19.     #代码块
  20.     if ($line=~/\`{3}(\w+)$/)
  21.     {
  22.         $lang = lc($1);
  23.         $line=~s/\`{3}(\w+)/\[code\]/;
  24.         $line=~s/perl/bash/;
  25.     }
  26.     elsif ($line=~/\`{3}$/)   #inline code
  27.     {
  28.         $line=~s/\`{3}\r?\n$/\[\/code\]/;
  29.     }
  30.     elsif ($line=~/^\s*#[^#]/)    #一级标题
  31.     {
  32.         $line=~s/^\s*#(.*)$/\[b\]\[size=5\]$1\[\/size\]\[\/b\]\n\[list\]\[list\]/;
  33.         $line = "[/list]"x2 . "\n".$line if ($indent == 1);
  34.         $line = "[/list]"x4 ."\n".$line if ($indent == 2);
  35.         $indent = 1;
  36.     }
  37.     elsif ($line=~/^\s*##[^#]/)    #二级标题
  38.     {
  39.         $line=~/^\s*##(.*)$/;
  40.         $line=~s/^\s*##(.*)$/\[b\]$1\[\/b\]\n\[list\]\[list\]/;
  41.         $line = "[/list]"x2 ."\n".$line if ($indent >= 2);
  42.         $indent = 2;
  43.     }
  44.     elsif ($line=~/\*{2}[^*]/)    #粗体 (原文书写同一段文字最好不要断行)
  45.     {
  46.         $line=~s/\*{2}(.*)\*{2}/\[b\]$1\[\/b\]/;
  47.     }
  48.     elsif ($line=~/\*[^*]/)    #倾体 (原文书写同一段文字最好不要断行)
  49.     {
  50.         $line=~s/\*(.*)\*/\[i\]$1\[\/i\]/;
  51.     }
  52.     $line=~s/\s*`(.*)`\s*$/\[code\]$1\[\/code\]\n/;
  53.     push @topics, $line;
  54. }
  55. my $all = join("", @topics);
  56. $all =~s/(\r?\n)+$//;
  57. if ( $all=~/\[list\]/ )
  58. {
  59.     $all .= '[/list]';
  60. }
  61. print encode('gbk', $all);
  62. print "\n";
  63. close $fh;
复制代码
2

评分人数

[连载]《Intermediate Perl》学习笔记 - 进阶

本帖最后由 PerlMonk 于 2017-4-18 12:43 编辑

列表操作

      ",", sort,reverse,push, pop, shift, unshift, grep, map

      使用 grep 过滤列表

          grep 的使用可以分为表达式形式和 block 形式

          筛选大于10的数并保存到另一个数组:
          1. my @input_numbers = (1, 2, 4, 8, 16, 32, 64);
          2. my @bigger_than_10 = grep $_ > 10, @input_numbers;
          复制代码
          结果为 16, 32, 64

          通过隐式引用来筛选末尾含有4的数字:
          1. my @end_in_4 = grep /4$/, @input_numbers;
          复制代码
          如果测试表达式较为复杂,可以写在一个子例程中,然后通过 grep 调用。
          在一组数字中,提取个位十位... 相加 %2 余 1 的项:
          1. my @odd_digit_sum = grep digit_sum_is_odd($_), @input_numbers;
          2. sub digit_sum_is_odd {
          3.     my $input = shift;
          4.     my @digits = split //, $input; # Assume no nondigit characters
          5.     my $sum;
          6.     $sum += $_ for @digits;
          7.     return $sum % 2;
          8. }
          复制代码
          块形式(相比调用子例程的形式,少了 return。在这里使用 return 将退出 grep ):
          1. my @odd_digit_sum = grep {
          2.     my $sum;
          3.     $sum += $_ for split //;
          4.     $sum % 2;
          5. } @input_numbers;
          复制代码

      使用 map 转换列表

          类似 grep ,但 map 用于转换而不是筛选
          1. my @input_numbers = (1, 2, 4, 8, 16, 32, 64);
          2. my @result = map $_ + 100, @input_numbers;
          复制代码
          以及 map 没有规定对于每一项只返回一个值
          1. my @result = map { $_, 3 * $_ } @input_numbers;
          复制代码
          借此可以快速从一个列表生成一组哈希映射,以便于做字典判断
          1. my %hash = map { $_, 1 } @castaways;
          2. my $person = 'Gilligan';
          3.     if( $hash{$person} ) {
          4.     print "$person is a castaway.\n";
          5. }
          复制代码

eval

      捕获错误

          示例:
          1. my $average = $total / $count; # divide by zero?
          2. print "okay\n" unless /$match/; # illegal pattern?
          3. open MINNOW, '>', 'ship.txt'
          4.     or die "Can't create 'ship.txt': $!"; # user?defined die?
          5. implement($_) foreach @rescue_scheme; # die inside sub?
          复制代码
          其中每一行都有可能出错导致程序崩溃,但在实际应用中并不意味着应该结束整个程序,Perl 通过 eval 实现错误捕获:
          1. eval { $average = $total / $count } ;
          2. print "Continuing after error: $@" if $@;
          复制代码
          当 eval 代码块运行出错时,错误信息保存到 $@,eval 之后的代码继续运行。注意 eval 不是结构语句,末尾必须加分号。

          eval 语句块也可以像函数一样 return,如果出错,返回空值(在标量环境返回 undef,在数组环境返回空列表)。现在可以安全地处理零除错误:
          1. my $average = eval { $total / $count };
          复制代码
          $average 要么是"商"要么是"undef"。

          注意
          Perl 允许 eval 镶嵌使用。
          eval 可以捕获一般的错误,但无法处理结束进程、内存溢出等情况

          Try::Tiny
          1. use Try::Tiny;
          2. my $average = try { $total / $count } catch { "NaN" };
          复制代码

      执行动态生成的代码

          eval 除了代码块的形式,还有一种字符串形式。在运行时编译运行某段字符串内的代码。这将带来一定风险,或许会执行带有攻击性的代码。
          一个简短的示例:
          1.     eval '$sum = 2 + 2';
          2.     print "The sum is $sum\n";
          复制代码
          因为 eval 能够返回最后一句代码的结果,所以不必将赋值放在待执行的字符串中
          1. foreach my $operator ( qw(+ ? * /) ) {
          2.     my $result = eval "2 $operator 2";
          3.     print "2 $operator 2 is $result\n";
          4. }
          复制代码
          和代码块形式一样,如果执行错误,有关信息将保留到 $@:
          1. print 'The quotient is ', eval '5 /', "\n";
          2. warn $@ if $@;
          复制代码
          >The quotient is
          >syntax error at (eval 1) line 2, at EOF

          提醒
          请谨慎使用 eval 执行字符串代码的形式,尽可能使用其他方法达到目的。在11章将介绍如何加载外部文件代码并执行,并使用更好的方法。


do

      do 是 Perl 语言中一个强有力但容易被忽视的工具,用于将多个表达式组织到一个代码块中,
      并像子例程一样返回最后执行的结果。

      使用 do 语句块简化赋值判断

          假设要为 $bowler 赋值,但是分为三种条件,需要写出多个 $bowler:
          1. my $bowler;
          2. if( ...some condition... ) {
          3.     $bowler = 'Mary Ann';
          4. }
          5. elsif( ... some condition ... ) {
          6.     $bowler = 'Ginger';
          7. }
          8. else {
          9.     $bowler = 'The Professor';
          10. }
          复制代码
          如果改为 do 语句块的形式,只需要一个 $bowler
          1. my $bowler = do {
          2.     if( ... some condition ... )     { 'Mary Ann' }
          3.     elsif( ... some condition ... )  { 'Ginger' }
          4.     else                             { 'The Professor' }
          5. };
          复制代码

      一次读取文件

          do 能够创建一个包围作用域,当我们要一次读取整个文件内容到变量中,可以通过 do block 私有作用域,为 $/ 和 @ARGV 创建私有副本,从而能够使用 <> 句柄读取 $filename 参数的文件内容
          1. $filename = __FILE__;
          2. my $file_contents = do {
          3.     local $/;
          4.     local @ARGV = ( $filename );
          5.     <>;
          6. };
          7. print $file_contents;
          复制代码

      加载运行其他脚本代码

          类似 eval,do 也有将字符串作为参数的形式,当传递的是字符串而非代码块时,do 假设传入的是文件,并从文件中读取代码编译执行:
          1. do "slurp.pl";
          复制代码
          类似于 `eval "type slurp.pl";` 区别参考 perldoc -f do
          缺点在于:即使执行的代码发生错误,程序仍会继续运行。并且即使是加载运行过的文件,仍会再次执行(对比 require)。基于这些原因,很少人使用 do 语句

          我们知道,使用 use 加载模块,以及 use 语句在编译时运行。其实还可以通过 require 在运行时加载模块:
          1. require List::Util;
          复制代码
          use List::Util 的实质是在 BEGIN 块中执行 require 以及 该模块的 import() 方法;
          1. BEGIN {
          2.     require List::Util;
          3.     List::Util->import( ... );
          4. }
          复制代码
          通常 use 使用模块名的形式,而 require 还可以用文件名作为参数,导入文件:
          1. require $filename;
          复制代码
          require 能够记住已经加载过的文件,对于重复的加载将不会再执行 ( 对比 do )。更多内容参考 12 章 - Creating Your Own Perl Distribution

        [Finished in 0.1s]
1

评分人数

TOP

本帖最后由 PerlMonk 于 2017-4-18 21:57 编辑

管理复杂数据结构

      使用调试器查看数据结构

          参考 PerlDebug

          示例代码:
          1. my %total_bytes;
          2. while (<DATA>) {
          3.     my ($source, $destination, $bytes) = split;
          4.     $total_bytes{$source}{$destination} += $bytes;
          5. }
          6. for my $source (sort keys %total_bytes) {
          7.     for my $destination (sort keys %{ $total_bytes{$source} }) {
          8.         print "$source => $destination:",
          9.         " $total_bytes{$source}{$destination} bytes\n";
          10.     }
          11.     print "\n";
          12. }
          13. __DATA__
          14. professor.hut gilligan.crew.hut 1250
          15. professor.hut lovey.howell.hut 910
          16. thurston.howell.hut lovey.howell.hut 1250
          17. professor.hut lovey.howell.hut 450
          18. ginger.girl.hut professor.hut 1218
          19. ginger.girl.hut maryann.girl.hut 199
          复制代码

          操作示例:

            perl -d bytecounts.pl
            Loading DB routines from perl5db.pl version 1.37
            Editor support available.
            Enter h or 'h h' for help, or 'perldoc perldebug' for more help.

            main::(bytecounts.pl:1):        my %total_bytes;
               DB<1> s
            main::(bytecounts.pl:2):        while (<DATA>) {
               DB<1> s
            main::(bytecounts.pl:3):            my ($source, $destination, $bytes) = split;
               DB<1> s
            main::(bytecounts.pl:4):            $total_bytes{$source}{$destination} += $bytes;

               DB<1> x $source, $destination, $bytes
            0  'professor.hut'
            1  'gilligan.crew.hut'
            2  1250

          在 perlDB 控制台中输入 s 执行下一句,x 后附加变量名显示对应变量的状态
          也可以直接输入代码,查看数组:`x @array`, 查看哈希字典:`x \%hash`

               DB<8> @a = (1 .. 3);
               DB<9> x @a
            0  1
            1  2
            2  3

               DB<10> %h = qw/a 1 b 2 c 3/;
               DB<12> x \%h
            0  HASH(0x2ac9e2c)
                'a' => 1
                'b' => 2
                'c' => 3

          也可以在 x 后面使用列表、哈希操作符(sort, keys, values ... )

               DB<31> %h = qw(a b c d e f);
               DB<34> x sort keys %h
            0  'a'
            1  'c'
            2  'e'

          一些总结

          s [函数名] 进入函数并逐步运行,提示符从 `DB<>` 变为 `DB<<>>`
          n [函数名] 执行函数,并且一次执行完。
          b [line|event|sub] 添加断点。
          B [line|*] 删除断点
          w [expr] 监视变量,受监视的变量在变化时将显示到终端,`w $var`
          W [expr|*] 删除变量监视器
          p 同 print
          S [[!]pat] 枚举当前加载的所有子例程名单,可以设置过滤、排除,例:

            DB<24> S main
              main::BEGIN
              main::dumpValue
              main::dumpvar
              main::test

          y 查看当前脚本的变量列表和对应的值
          c [n] 连续执行代码直到某一行,如果不带参数,会结束当前循环或者脚本。
          a [ln]+[cmd] 在某行之前执行命令,例 `a 8 print "$var\n"` ,实测有时候没有效果,最好先为该行设置断点
          A [ln|*] 删除命令


      使用 Data::Dumper 打印/导出复杂数据结构


          1. use Data::Dumper;
          2. print Dumper(\%total_bytes);
          复制代码

            $VAR1 = {
                       'thurston.howell.hut' => {
                                                  'lovey.howell.hut' => 1250
                                                },
                       'ginger.girl.hut' => {
                                              'maryann.girl.hut' => 199,
                                              'professor.hut' => 1218
                                            },
                       'professor.hut' => {
                                            'gilligan.crew.hut' => 1250,
                                            'lovey.howell.hut' => 1360
                                          }
                     };

          来看另一段代码,@data1 , @data2 互相包含对方的引用,Data::Dumper 能够正确打印他们的结构 注意这里给 Dumper 传入两个数组引用

          1. use Data::Dumper;
          2. $Data::Dumper::Purity = 1; # 声明打印的数据可能出现自引用的情况
          3. my @data1 = qw(one won);
          4. my @data2 = qw(two too to);
          5. push @data2, \@data1;
          6. push @data1, \@data2;
          7. print Dumper(\@data1, \@data2);
          复制代码

            $VAR1 = [
                       'one',
                       'won',
                       [
                         'two',
                         'too',
                         'to',
                         []
                       ]
                     ];
            $VAR1->[2][3] = $VAR1;
            $VAR2 = $VAR1->[2];

          如果使用 Perl Debugger

               DB<2> x \@data1, \@data2
            0  ARRAY(0x24a4e84)
                0  'one'
                1  'won'
                2  ARRAY(0x47f324)
                   0  'two'
                   1  'too'
                   2  'to'
                   3  ARRAY(0x24a4e84)
                      -> REUSED_ADDRESS
            1  ARRAY(0x47f324)
                -> REUSED_ADDRESS

          Data::Dump
          如果不介意数据的建,可以考虑使用 Data::Dump,输出相对简洁:
          1. use Data::Dump qw(dump);
          2. dump( \%total_bytes );
          复制代码

            {
               "ginger.girl.hut"     => { "maryann.girl.hut" => 199, "professor.hut" => > 1218 },
               "professor.hut"       => { "gilligan.crew.hut" => 1250, "lovey.howell.hut" > => 1360 },
               "thurston.howell.hut" => { "lovey.howell.hut" => 1250 },
            }

          Data::Printer
          这个模块在 ActivePerl V5.16 ppm 安装失败,但是可以从CPAN下载安装,同时需要安装 Sort::Naturally, Clone::PP

          1. use Data::Printer;
          2. p( %total_bytes );
          复制代码

            {
                 ginger.girl.hut       {
                     maryann.girl.hut   199,
                     professor.hut      1218
                 },
                 professor.hut         {
                     gilligan.crew.hut   1250,
                     lovey.howell.hut    1360
                 },
                 thurston.howell.hut   {
                     lovey.howell.hut   1250
                 }
            }



使用 Storeable 模块存储复杂数据结构


YAML


JSON


对数据的间接操作,通过  grep 和 map

      [Finished in 0.1s]

    TOP

    返回列表