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

  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" 存在就追加,不存在则创建

  • 文件名也可以用变量来替代:
    demo5-1
#! /usr/bin/perl 
my $test = "test.txt";
my $success = open TXTFILE, "<$test";
if($success){
    print "success\n";
}else{
    print "failed\n";
}
  • 运行结果:(先创建一个名为test.txt的空文件)
./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" 回显用户输入,说明使用符号`<`时发生了系统重定向
  • 第一种调用方式因为有参数,所以ARGV并没有读取标准输入流的内容
  • 第二种方式实际是无参数调用Perl程序,并且系统将标准输入流重定向到text.txt文件。这里是用到了Unix的管道功能,把test.txt的内容通过管道传递给了Perl程序,而这个过程对Perl是透明的,所以除非在代码里显式的将STDIN指回键盘,否则就会从管道中读取,而在这个过程中,Perl一直在读取标准输入流中的内容,虽然它并不清楚标准输入流中的内容来自哪里。
  1. 格式化输出printf :
  • "%10s", 10表示占位,s表示字符串,printf基本是从C语言移植过来的,所以基本语法相同
  • 可以用一个标量存储格式化内容,再用printf输出,比如:
$input = <STDIN>
$num = 10;
$format = "%${num}s";
printf $format, $input

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

  • %%和% 似乎没什么区别,
  • print “@array” 会在元素之间加上空格, 这里其实是在标量上下文中应用数组,所以数组在被转成标量时在数组元素之间加上了空格,print @array则会一个接一个的输出数组中的元素,这就是print在列表上下文中的标准做法
  1. die
  • $0 程序名:实际上是命令行的第一个参数,只不过它一般是程序名,而且不保存在@ARGV 参数列表里
  • $! 系统诊断信息
  • die默认会打印行号,除非在最后加上换行符\n

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.

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • 文件句柄的概念 在Perl中,文件句柄就是一个程序与外界文件间的I/O联系的名称,又可以理解为一种通道。即一个文件...
    樱雨楼阅读 1,317评论 0 2
  • Perl 哲学 Perl 是一种能“干实事”的语言。它灵活、宽容、可塑。在一名编程能者的手中,它可以 完成几乎所有...
    firefive阅读 1,349评论 1 11
  • 本文内容非原创,你可以点击此处查看内容来源声明 输入/输出流 在Java API中,可以从其中读出一个字节序列的对...
    _gitignore阅读 2,482评论 0 0
  • The Cat and the Mouse in Partnership 从前,有一只猫认识了一只老鼠。她不停地对...
    爱食加译阅读 437评论 0 0
  • 大学生这个词好像是意味着摆脱高中学习的压力,踏入社会之前的历练。在我们的想象中我们应该在大学学会如何和他人相...
    5235513b7b19阅读 162评论 0 1