Perl 语言入门 Learning Perl

Learning Perl 学习笔记 Ch5 输入与输出

2019-04-02  本文已影响0人  sakam0to
  1. Perl中的文件句柄File handler就像名字所说明的那样,是指操作文件的handler而非文件本身

  2. Perl预定义了六种文件句柄,STDIN 标准输入流、 STDOUT 标准输出流、 STDERR 标准错误流、ARGV 参数列表、 DATAARGVOUT,只要不和系统预定义的句柄重名,用户可以定义自己的文件句柄,句柄名字推荐使用全大写表示

  3. STDIN 默认指向用户的键盘,STDOUT 和STDERR 默认指向显示器,但他们都可以被重定向到指定的文件

  4. 打开文件句柄:因为Perl并不能直接操作文件,只能通过调用操作系统API操作文件,所以除了预定义的文件句柄之外,用户自己定义的文件句柄必须显式的打开才能使用。打开文件句柄的方式有以下几种:
    -- 默认的打开 (可读也可写)open TXTFILE, "test.txt"
    -- 只读的打开 open TXTFILE, "<test.txt"
    -- 覆写的打开 open TXTFILE, ">test.txt" 存在就覆盖,不存在则创建
    -- 追加的打开 open TXTFILE, ">>test.txt" 存在就追加,不存在则创建

#! /usr/bin/perl 
my $test = "test.txt";
my $success = open TXTFILE, "<$test";
if($success){
    print "success\n";
}else{
    print "failed\n";
}
./demo5-1
success
  1. 关闭文件句柄:显式地用close FILEHANDLER关闭文件句柄会通知操作系统立刻刷新输出缓冲区,然后关闭对文件的访问。用open打开一个重名文件句柄也会关闭之前已打开的文件句柄

  2. 使用文件句柄

    • 从用户定义的文件句柄中读取和从标准输入流中读取没什么区别,使用一对尖括号<>将文件句柄括起来,就像这样<TXTFILE>
    • 往用户定义的文件句柄中写入稍有不同,需要在print之后显式的指定文件句柄print TXTFILE "Write into this file from Perl successfully", 而且就像代码里展示的那样,句柄和输入的内容之间没有逗号
      demo5-2
#! /usr/bin/perl
if(! open NEWFILE, ">new.txt"){
    die "Open new.txt to write failed:$!";
}
print NEWFILE "Write into NEWFILE successfully: ".localtime();
close NEWFILE;

if(! open OLDFILE, "<new.txt"){
    die "Open new.txt for reading failed:$!";
}
@input = <OLDFILE>;
print "Read from new.txt:\n";
print "@input\n";
close OLDFILE;
./demo5-2
Read from new.txt:
Write into NEWFILE successfully: Tue Mar 26 17:02:22 2019
  1. 标准输入流:用文件句柄来理解标准输入流就简单多了,<STDIN>只不过是一个默认从键盘获取输入的文件句柄而已。标准输入流<STDIN>既可以重定向从其他文件里获取输入,用户定义的文件句柄也可以指向从键盘获取输入。所以唯一的不同似乎只有标准输入流定义了更多的默认行为,而且在使用标准输入流之前不用open来显式打开它
    • 将标准输入流重定向到文件:open STDIN, "<test.txt" 这种方式实际是复用Perl预置的文件句柄
    • 将用户定义的文件句柄指向键盘输入:open USER, "<&1"
      demo5-3:
#! /usr/bin/perl
open USER, "<&1";
print "User input:".<USER>;

当执行demo5-3时,它讲等待用户输入一行,然后回显用户的输入内容,就像使用<STDIN>一样!

  1. 当读取文件句柄(包括标准输入流)和循环结合,有两种形式的循环结构至少在结果上看是一样的:
    foreach循环:
foreach (<STDIN>){
  print $_;
}

while循环:

while (<STDIN>){
  print $_;
}

他们都将回显所有用户输入的内容,直到用户按下Ctrl-D结束输入。
但是,foreach循环是一次性将文件句柄中的所有内容存储到内存中的列表里,然后用$_遍历该列表,而while一次从文件句柄中只读取一行到内存中的$_变量,然后打印出来,当文件句柄指向的文件特别大时,这种差别所引起的性能差异将会被明显放大

  1. 钻石操作符<>, 调用参数和@ARGV数组, 在之前的例子中,钻石操作符用来从文件句柄中获取输入,不管这个句柄是用户定义的,还是Perl预置的,他们的行为都是一样的。除了标准输入/输出/错误流三个预置文件句柄外,Perl还定义了ARGV句柄和@ARGV数组来存储Perl程序的调用参数
    demo5-4
#! /usr/bin/perl
foreach(@ARGV){
  print "$_\n";
}

执行:(这里的a.out, b.cpp, c.txt是三个不存在的文件)

./demo5-4 a.out b.cpp c.txt
a.out
b.cpp
c.txt

demo5-4@ARGV中的内容逐行输出到屏幕上,而@ARGV中的内容恰好就是调用demo5-4时的参数


现在换成<ARGV>文件句柄试一下
demo5-5

#! /usr/bin/perl
foreach(<ARGV>){
  print "$_\n";
}

用同样的方式执行:(这里的a.out, b.cpp, c.txt是三个不存在的文件)

./demo5-5 a.out b.cpp c.txt
Can't open a.out: No such file or directory at ./demo5-5 line 2.
Can't open b.cpp: No such file or directory at ./demo5-5 line 2.
Can't open c.txt: No such file or directory at ./demo5-5 line 2.

很显然,demo5-5尝试去打开我传入的文件名,而且失败了。这说明 <ARGV>句柄不同与@ARGV数组,@ARGV只是单纯地将调用参数保存起来,而<ARGV>则认为你的调用参数是你指定的输入源,并且尝试将其打开,这是因为<ARGV>的本质是文件句柄File Handler,所以它的默认动作就是操作文件,不管这个“文件”是存储在磁盘上还是用户的键盘和显示器。
这种差别的意义在于:如果我想写一个类似cat的程序将调用参数指定的文件内容输出到显示器上,那我应该使用<ARGV>,如果我错误的使用了@ARGV,那我只能在显式器上看到我输入的参数。但如果我想写一个汇率计算程序,将调用时传入的美元换算成人民币, 那我就应该使用@ARGV,因为$109.9并不是一个合法的文件名,<ARGV>无法打开它,而且我们需要的是调用参数的字面值。


现在修改demo5-1使用过的test.txt文件,新增如下内容

This is a test file.
This is the second line of the file.

然后再次运行demo5-5

./demo5-5 test.txt
This is a test file.

This is the second line of the file.

注意到这里的换行,因为程序里print 命令打印了换行符,而文件内也有换行符,如果只想打印一次换行,需要在循环体内用chomp删掉文件里的换行符

  1. 文件句柄的默认行为:当钻石操作符没有指定文件句柄时,默认指向ARGV,而没有调用参数时ARGV则默认指向标准输入流。
    demo5-6:
#! /usr/bin/perl
foreach(<>){
  print "$_\n";
}

用如下四种方式调用:

1. "./demo5-6" 程序将读取键盘的输入(未重定向的标准输入流),直到按下ctrl+D来结束,然后依次在屏幕打印键盘的输入内容

2. "./demo5-6 test.txt" 和"./demo5-5 test.txt"的效果相同,程序将读取test.txt文件中的内容,然后逐行打印到屏幕上,并加上额外的换行符。这里指定了test.txt作为程序新的输入源,而不再使用标准输入流(也就是键盘)

3. "./demo5-6 <test.txt" 程序读取test.txt中的内容,然后打印到屏幕上

4. "./demo5-6 -" 和无调用参数的"./demo5-6"效果相同,ARGV的参数列表里"-"就表示标准输入流,可见ARGV的默认行为是标准输入流

将此程序改写为
demo5-7:

#! /usr/bin/perl
foreach(<STDIN>){
  print "$_\n";
}

这里指定了要从标准输入流STDIN中进行读取,同样用上面的三种方式进行验证:

1. "./demo5-7" 和之前相同,程序将读取键盘的输入,直到按下ctrl+D来结束,然后依次在屏幕打印键盘的输入内容

2. "./demo-7 test.txt" 程序读取键盘的输入,然后打印到屏幕上。虽然这里传入了test.txt作为调用参数,但因为程序里使用的是标准输入流STDIN而不是ARGV,所以仍然使用标准输入流(也就是键盘)作为输入源

3. "./demo5-7 <test.txt" 不同于第2种方式,程序没有读取键盘的输入,而是读取了test.txt中的内容,然后打印到屏幕上。这里用test.txt取代键盘,成为标准输入流,对于Perl程序来说,依然是从标准输入流中获取输入,只是操作系统将标准输入流的内容替换了,相当于实现了隐式的标准输入流重定向,而这个过程对Perl程序是透明的。

但是在./demo5-6 <test.txt时发生了什么呢?
修改demo5-6
demo5-8:

#! /usr/bin/perl
open STDIN, "<&1";
foreach(<>){
  print "$_\n";
}

在代码第一行把标准输入流STDIN重定向到键盘输入"<1&",
再尝试调用

1. "./demo5-8 test.txt" 打印文件内容,说明此时并没有读取标准输入流STDIN

2. "./demo5-8 <test.txt" 回显用户输入,说明使用符号`<`时发生了系统重定向
  1. 格式化输出printf :
$input = <STDIN>
$num = 10;
$format = "%${num}s";
printf $format, $input

$format = "%${num}s";使用了变量内插
需要注意$input = <STDIN>没有调用chomp函数,如果是从键盘读取输入,此时$input里包含换行符,在printf进行格式化输出时也会占用占位符的位置

  1. die

demo5-9:

#! /usr/bin/perl -w
use strict;
if(@ARGV == 0){
die "No way Man\n";
}
if(@ARGV == 1){
die "$0 Cannot run";
}
our $success = open FILE, "$ARGV[1]";
if(! $success){
die "fail open file $ARGV[2]:$!";
}
foreach(<FILE>){
print "Line:",$_;
}

用三种方式调用的结果如下:
无参调用:

# ./demo5-9
No way Man

一个参数调用

# ./demo5-9 test.txt
./demo5-1 Cannot run at ./demo5-1 line7.

两个参数调用

#  ./demo5-9 test.txt test.txt
Line: This is a test file.
Line: This is the second line of the file.

这里没有使用钻石运算符<>,而是使用了一个文件句柄打开文件输入,效果是一样的。

上一篇 下一篇

猜你喜欢

热点阅读