Perl学习记录(四)

第五章 输入与输出

读取标准输入

简写:

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

读取一行标准输入,看它是不是为真(通常是)。若是真,就进入while循环,并在下次循环时忘记刚刚读入的那一行,进入下一行!就好比执行了下面的代码:

while (defined($_ = <STDIN>)) {
print "I saw $_ ";
}

如果你将行输入操作符放在其他任何地方(特别是自成一行的语句),它并不会读取一行输入并自动存入默认变量 。 唯 独 w h i l e 循 环 的 条 件 表 达 式 里 只 有 行 输 入 操 作 符 的 前 提 下 , 这 个 简 写 才 起 作 用 。 行 输 入 操 作 符 ( < S T D I N > ) 和 P e r l I 的 默 认 变 量 ( _。唯独while循环的条件表达式里只有行输入操作符的前提下,这个简写才起作用。 行输入操作符(<STDIN>) 和PerlI的默认变量( while(<STDIN>)PerlI(_ )之间并没有什么关联,只是在这个简写里,输入的内容会恰好存储在默认变量中而已。然而,如果在列表上下文中调用行输入操作符,它会返回一个列表,其中包含(其余)所有的输入内容,每个列表元素代表一行输入内容: .

foreach (<STDIN>) {
print "I saw $_ ";
}

不同之处:while循环里,Perl会读取一行输入,把它存入某个变量并且执行循环的主体,接下来它会回头去寻找其他的输入行。但是在foreach循环里,,它必须先将输入全部读进来。因此最好的做法通常是尽量使用while循环的简写,让它每次处理一行。

来自钻石操作符的输入

另外一种读取输入的方法,使用钻石操作符<>。
程序的调用参数通常是命令行上跟在程序名后面的几个“单词”
$ ./my_ program fred barney betty
执行my_ program命令(位于当前目录),然后它应该会处理文件fred,接着是文件barney,最后是文件bety。

while (defined($line = <>) {
   chomp($line);
   print "It was $line that I saw!\n";}

假设这个程序运行时的调用参数是fred、barney和betty, 输出结果就会是“It was [从文件fred取得的一行内容] that 1 saw!"、“It was [从文件fred取得的另一行内容] that I saw!"等,直到我们遇到文件fred的结尾为止。接下来,它会自动切换到文件barney,逐行输出它的内容,然后再换到文件betty。请注意,在切换到另一个文件时中间并没有间断,因为使用钻石操作符时就好像这些文件已经合并成一个很大的文件一样。钻石操作符只有在碰到所有输入的结尾时才会返回undef(然后我们就会跳出while循环)。
既然这只是行输入操作符的一种特例,其简写:

while(<>) {
chomp;
print "It was $_ that I saw!\n";
}

使用了chomp的默认用法:不加参数时,chomp会直接作用在 上 。 由 于 钻 石 操 作 符 通 常 会 处 理 所 有 的 输 入 , 所 以 一 旦 看 到 它 出 现 在 程 序 中 的 多 处 时 , 通 常 都 是 错 误 的 。 初 学 者 在 程 序 中 放 入 第 二 个 钻 石 操 作 符 时 , 往 往 是 想 读 取 下 一 行 输 入 , 其 实 _上。由于钻石操作符通常会处理所有的输入,所以一旦看到它出现在程序中的多处时,通常都是错误的。 初学者在程序中放入第二个钻石操作符时,往往是想读取下一行输入,其实 _才是他们想要的东西。请记住,钻石操作符是用来读取输入的,而输入的内容可以在$_中找到。

调用参数

钻石操作符其实不会去检查命令行参数,它的参数其实不过是来自@ ARGV数组。这个数组是由Perl解释器事先建立的特殊数组,其内容就是由命令行参数组成的列表。
钻石操作符会查看数组@ARGV,然后决定该用哪些文件名,如果它找到的是空列表,就会
改用标准输入流;否则,就会使用@ARGV里的文件列表。
我们可以处理三个特定的文件,不管用户在命令行参数中指定了什么:

@ARGV = qw# larry moe curly #; # 强制让钻石操作符只读取这三个文件
while (<>) {
chomp;
print "It was $_ that I saw in some stooge-like file!\n";
}

输出到标准输出

print操作符。加空白、结尾加换行符:

$name = "Larry Wall";
print "Hello there, $name, did you know that 3+4 is ", 3+4, "?\n";

直接使用数组和使用数组内插在打印:

print @array; #把数组的元素打印出来
print "@array"; #打印出一个字符串(数组元素内插的结果)

第一个print语句会一个接一个地打印出数组中所有的元素,元素之间不会有空格。而第二个则不同,它只打印一个字符串,也就是@array在双引号中内插形成的字符串,也就是数组@array所有元素(以空格分隔)组成的字符串。
一般来说,如果数组里的字符串包含换行符,那么只要直接将它们输出来就好了:

print @aIray;

要是它们不包含换行符,你通常会想在结尾补上一个(通常在使用“ ”的场合,字符串后面最好都加上\n)

print "@array\n";

假如print的调用看起来像函数调用,它.就是一个函数调用。在函数调用里,函数名后面必须紧接着一对括号,里面包含了函数的参数,就像这样:print (2+3)。它会输出5,但它和其他函数一样返回某个值。print的返回值不是真就是假,代表print是否成功执行。除非发生了I/O错误,否则它一定会成功。所以在下面的语句里,$result通 常会是1:

$result = print("hello wor1d!\n");

#假设你决定将返回值乘以4:
print (2+3)*4; # 糟糕!当Perl看到这行程序代码,它会遵照你的要求输出5。接着,Perl会 从print取得返回值1,再将它乘以4。

没有括号的时候,print是列表操作符,会把其后的列表里的所有东西全都输出来。但是假如print后面紧跟着左括号,它就是一个函数调用,只会将括号内的东西输出来。

用printf格式化输出

print f操作符的参数包括“格式字符串”及“要输出的数据列表”。格式字符串好像用来填空的模板,代表你想要的输出格式:

printf "Hello, %s; your password expires in %d days !\n",
$user, $days_to_die;

格式字符串里可以有多个所谓的转换(conversion) 。每种转换都会以百分比符号(%)开头,然后以某个字母结尾。而后面的列表里元素的个数应该和转换的数目一样多,如果数目不对,就无法正确运行。
printf可用的转换格式很多,所以我们在这里只会说明最常用的部分。
要输出恰当的数字形式,可以使用%g,它会按需要自动选择浮点数、整数甚至是
指数形式:

printf "%g %g %g\n", 5/2, 51/17, 51 ** 17; # 2.5 3 1.0683e+29

%d格式则代表十进制整数,它会舍去小数点之后的数字(无条件截断,而非四舍五入):

printf "in %d days!\n", 17.85; #输出: in 17 days!

字段式的数据输出,大多数的转换格式都可以让你指定宽度。如果数据太长,字段会按需要自动扩展:

printf "%6d\n", 42; # 输出结果看起来像“.“42 (“ 符号代表空格)
printf "%2d\n", 2e3 + 1.95; # 2001

%s代表字符串格式,所以它的功能其实就是字符串内插,只是还能设定字段宽度:

printf "%10s\n", "wilma"; # 看起来像`````wilma

向左对齐%-15s(适用于上述各种转换) : 向右对齐%15s

printf "%-15s\n", "flintstone"; # 看起来像flintstone .

%f转换格式(浮点数)会按需要四舍五人,甚至还可以指定小点数之后的输出位数:

printf "%12f\n", 6 *7 + 2/3;#看起来像“42.66667
printf "%12.3f\n", 6 * 7 + 2/3; # 看起来像“....42.667
printf "%12.0f\n", 6 * 7 + 2/3; # 看起来像.....43

要输出真正的百分号,请使用%%,它的特殊之处在于不会输出(参数)列表中的任何元素

printf "Monthly interest rate: %. 2f%%\n"
5.25/12; # 运算后的值看起来像"0.44%"

数组和printf
把格式字符串存进变量 :

my @items = qw( wilma dino pebbles );
my $format = "The items are:\n" .("%10s\n" x @items);
##打印"the format is >>$format<<\n"; #用于调试
printf $format, @items;

这段程序用了x操作符复制指定的字符串,复制的次数与@items的元素个数相同。这样一来,产生的格式字符串就和直接写“The items are: \n%10s \n%10s\n%10s\n”一样。程序会先输出标题,接着将每个元素显示成独立的一行,每行都靠右对齐,字段一律10个字符宽。我们可以把它们全都组合在一起:

printf "The items are:\n" . ("%10s\n" x @items), @items;

请注意,我们在标量上下文中用了一次@items以取得它的长度,然后又在列表上下文中
用了一次以取得它的内容。上下文的重要性可见一斑。

文件句柄

文件句柄(filehandle) 就是程序里代表Perl进程(process) 与外界之间的I/O联系的名称。也就是说,它是“ 这种联系”的名称,不是文件的名称。在Perl 5.6之前,所有的文件句柄名称都是裸字(bareword) ,而从Perl5.6起, 我们可以把文件句柄的引用放到常规的标量变量中。
给文件句柄起名就好比其他标识符起名一样,必须以字母、数字及下划线组成,但不能以数字开头。建议你使用全大写字母来命名文件句柄。但有6个特殊文件句柄名是Perl保留的,它们是: STDIN、 STDOUT. STDERR、 DATA、 ARGV以及ARGVOUT
文件句柄STDIN就是Perl进程和它的输入源之间的联系,也就是俗称的标准输入流(standard input stream)。它通常是用户的键盘输入,除非用户要求别的输入来源,像从文件读取输入或是经由管道(pipe) 读取另一个程序的输出。当然还有标准输出流(standard output stream)STDOUT.默认情况下它会输出到用户的屏幕,但用户也可以把它送到文件或另一个程序,我们稍后会看到这种范例。

$ cat fred barney | sort | ./your_ program | grep something| lpr
意思是由cal命令将文件fred的每一行输出, 再加上文件barney里的每一行。之后,将以上输出作为sort命令的输入,对所有的行进行排序,继而把排序结果交给your_program处理。your. program完成相应操作后,再将输出数据送到grep,由它过滤掉数据中的某些行,并将剩下的数据输出到lpr这个命令,让它负责把最终结果传送给打印机打印出来。
用户还是可以用下面这样的shell命令将错误信息转向某个文件:
$ netstat | ./your. program 2 >/tmp/my errors

打开文件句柄

当你需要其他文件句柄时,请使用open操作符告诉Perl,要求操作系统为你的程序和外
界架起一道桥梁。来看几个具体例子

open CONFIG,' dino';
#打开名为CONFIC的文件句柄,让它指向文件dino。它会打开(已存在的)文件dino,文件中的任何内容都能从文件句柄CONFIG被读到我们的程序中来。
open CONFIG,' <dino' ;
# 用<声明文件只可读,不可写入,打开文件句柄的默认模式就是读取数据
open BEDROCK, ' >fred' ;
#可以>用来创建一个新的文件:它会打开文件句柄BEDROCK并输出到新文件fred
open L0G, ' >>1ogfile' ;
#用>>,如果文件原本就存在,那么新的数据将会添加在原
有文件内容的后面,如果它不存在,就会创建一个新文件

你可以使用任意一个标量表达式来代替文件名说明符。不过,你通常会想要明确指定输入或输出的方向:

my $selected_output = 'my_ output' ;
open LOG, "> $selected_ output" ;

注意大于号后的空格,这个空格能防止意外发生。如果$selected_output的值是">passwd"而之前又没有空格的话,就会变成以替换方式写入,而非追加方式写入文件。

在相对新版的Perl (5.6版之后) 里,open还有一种三个参数形式的写法:

open CONFIG, '<', 'dino';
open BEDROCK, '>', $file_ name;
open LOG, '>>', &1ogfile_ name();

其优点在于语法上可以很容易区分模式(第二个参数)与文件名本身(第三个参数)。
如果你预先知道要读取的文件是UTF-8编码的,则可以在文件操作模式后面加上冒号,然后写上编码名称: .

open CONFIG, '<:encoding(UTF-8)', 'dino';

反过来,如果要以特定编码写数据到某个文件,也可以这么用:

open BEDROCK, '>:encoding(UTF-8)', $file_name;
open LOG, '>>:encoding(UTF-8)', &logfile_name();

简便写法,不用每次键入encoding(UTF-8),只要写上:utf8就可以了。简写方式不会考虑输入或输出的数据是否真的就是合法的UTF-8字符串。如果用encoding(UTF-8)的话,它是会确认编码是否正确这一点的。即便如此,还是有很多人喜欢这么用:

open BEDROCK,' >:utf8', $file_ name; #可能会有问题

使用encoding()的形式,还能指定其他类型的编码。我们可以通过下面这条单行命令打
印出所有perl能理解和处理的字符编码清单:

% perl -MEncode -le "print for Encode- >encodings(' :all')"

这个列表中出现的名字应该都可以拿来用在读取和写入文件时指定编码。但有些编码方式可能在其他机器上无法使用,关键还是要看相应的编码系统是否安装在系统中。
如果想要litte-endian版本的UTF-16字符串,可以这样写:

open BEDROCK, ' >:encoding(UTF-16LE)', $file_ name;
#或者是Latin-1字符集:
open BEDROCK, '> :encodiog(iso-8859-1)', $file_ name;

除了转换字符编码之外,数据输入或输出过程当中还有其他层(layer)可以控制数据的转换操作。比如说,有时候你拿到的文件采用DOS风格的换行符,也就是文件中每行都以回车换行(carriage-return/inefeed, 简写为CR-LF)对(也常写作"\r\n")结尾。而Unix风格的换行符则只是一个"\n"。不管把谁当作谁,弄错了的话出来的结果就会很怪异。借助:crlf层,我们就可以自动解决这个问题。如果想要确保得到的文件每行都以CR-LF结尾,就得在操作该文件时使用这个特殊层:

open BEDROCK, '>:crlf', $file_ name;

现在每行写入文件时,该层就会把每个换行符转换为CR-LF。不过请注意,如果原本就是CR-LF风格的话,转换后会多出一个换行符。
读取DOS风格的文件时也可以这样转换:

open BEDROCK, '<:cr1f', $file_ name;

读入文件的同时,Perl会 把所有CR-LF都转换为Unix风格的换行符。

以二进制方式读写文件句柄
用binmode关闭换行符相关的处理:

binmode STDOUT; #不要转换行符
binmode STDERR; #不要转换行符

从Perl5.6开始,你可以在binmode的第二个参数的位置上指定层。如果你希望输出Unicode到STDOUT,就要确保STDOUT知道如何处理它拿到的数据(如果不这么写的话,会得到警告信息):

binmode STDOUT, ' :encoding(UTF-8)';

不管输入还是输出,都可以用binmode指定特定行为。如果传到标准输入的是UTF-8编码的字符,那么应该事先告诉Perl按照UTF-8的方式处理:

binmode STDIN, ':encoding(UTF-8)';

有问题的文件句柄
如果从有问题的文件句柄(即没有正确打开的文件句柄或关闭的网络连接)读取数据,会立刻读到文件结尾(“文件结尾” 在标量上下文中是undef,在列表上下文中则是空列表)。
open的返回值也能告诉我们它的执行结果成功与否:返回真表示成功,返回假则表示失败。所以,程序可以写成这样:

my $success = open LOG, *>>', 'logfile'; # 捕获返回值
if(!$success){
# open操作失败
}

当然,你可以照搬这种写法,不过稍后我们还会看到更简单流畅的写法。

关闭文件句柄
当你不再需要某个文件句柄时,可以用close操作符来关闭它:

close BEDROCK;

当你重新打开某个文件句柄时,Perl会自动关闭原先的文件句柄。在程序结束时,Perl也会自动关闭文件句柄。若你想要写得工整些,请为每个open搭配- -个close。最好是在每个文件句柄用完之后就立刻关闭它,哪怕程序马上就结束了

用die处理致命错误

当Perl遇到致命错误时,你的程序应该立刻中止运行,可以用die函数来实现。
所以,我们可以将前面的例子改写成这样: .

if ( ! open LOG, '>>', '1ogfile') {
die "Cannot create 1ogfile: $!";
}

如果open失败,die会终止程序的运行,并且告诉我们无法创建日志文件。可是冒号后面的$ !代表什么呢?那就是可读的系统错误信息。
die会自动将Perl程序名和行号附加在错误信息的后面,因此你就可以轻易判断出程序里的哪个die函数才是造成程序过早结束运行的原因。
如果你不想显示行号和文件名,请在die函数中的错误信息尾端的加上换行符。也就是
说,die的另一种用法就是加上结尾的换行符,形式如下所示:

if (@ARGV < 2) {
die "Not enough arguments\n";
}

如果命令行参数不足两个,范例程序会显示这行信息并中止运行。因为行号在此处对用户并没有用处,所以这里并不会显示程序名和行号。
请记得检查open的返回值,因为之后的程序代码必须在文件打开成功时才能顺利运行。

用warn输出警告信息

warn 函数的功能就是产生类似于PerI的内置警告信息的信息。使用和die函数差不多,不同之处在于最后一步,它不会终止程序的运行。

自动检测致命错误
从Perl 5. 10开始,autodie编译指令已经成为标准库的一部分。像下面这个例子,原来的写法是自己检查open的返回值并处理错误:

if ( ! open LOG, '>>', 'logfile') {
die "Cannot create 1ogfile: $I";
}

每次打开一个文件句柄都要这么写一遍的话,无疑是十分繁琐的。现在有了autodie编译指令,这部分工作便得以解放。如果open失败,它会自动启动die:

use autodie;
open LOG, '>>', ' 1ogfile' ;

使用文件句柄

一旦文件句柄以读取模式打开后,便可以从它读取一行行数据,就像从STDI N读取标准
输入流中的数据一样。来看读取Unix系统密码文件的例子:

if ( ! open PASSWD, "/etc/passwd") {
  die "How did you get logged in? ($!)";}
  while (<PASSWD>) {
   chomp;
}

在这个例子里,die的信息中用了一对括号围住$!。它们只不过是括住输出信息的括号而已。正如你看到的,所谓的“行输入操作符”是由两部分组成的:一对尖括号(真正的行输入操作符)以及里面用来输入的文件句柄。
以写入或添加模式打开的文件句柄可以在print或printf函数中使用。使用时,请直接将它放在函数名之后、参数列表之前:

print LOG "Captain's 1og, stardate 3.14159\n"; # 输出到文件句柄LOG
printf STDERR "%d percent complete. \n", $done/$total * 100;

改变默认的文件输出句柄
默认情况下,假如你不为print (或是printf)指定文件句柄,它的输出就会送到STDOUT。不过,你可以使用select操作符来改变默认的文件句柄。请看下面的例子,我们把默认输出改成BEDROCK这个文件句柄: .

select BEDROCK;
print "I hope Mr. Slate doesn't find out about this.\n";
print "Wilma!\n";

一旦选择(select)了输出用的默认文件句柄,程序就会直往那里输出。但是,这么做很容易使余下的程序变得混淆,所以这并不是一个好办法。因此,当你所指定的默认文件句柄使用完毕之后,最好把它设回原先的默认值STDOUT

重新打开标准文件句柄

Perl只有在成功打开新的句柄连接时,才会关闭默认的系统文件句柄。

用say来输出

Perl 5.10从借来了say这个函数。它的功能和print函数差不多,但在打印每行内容时会自动加上换行符。所以下面这几种写法的最终输出结果都一样:

use 5.010; .
print "Hello!\n"; .
print "Hello!", "\n";
say "Hello!";

如果只是要打印某个变量值并在末尾附带换行符,其实不必额外构造字符串,也不必给print函数提供数据列表,只要直接say这个变量就可以了。在你想输出某些内容并换行时,这个函数非常好用:

use 5.010;
my $name ='Fred';
print "$name\n";
print $name, "\n"; .
say $name;

但在内插数组时,最好还是用引号将它括起来,以便用空格隔开数组中的每个元素:

use 5.010;
my @array = qw(ab C d);
say @array;
#打印"abcd\n'
say "@array"; #打印"a b C d\n";

#和print函数-一样,你可以为say指定一个文件句柄:
use 5.010;
say BEDROCK "Hello!";

标量变量中的文件句柄

从Perl 5.6开始,我们已经可以把文件句柄存放到标量变量中,而不必非得使用裸字。成为标量变量后,文件句柄就可以作为子程序的参数传递,或者放在数组、哈希中排序,或者严格控制它的作用域。当然,有关裸字的用法还是需要谨记在心的,很多时候我们写的都是应急的短小脚本,用裸字更快捷,没必要用变量存储文件句柄。.
在open函数中原来使用裸字的地方写上不含有任何值的变量,那么文件句柄就会存放到那个变量中。人们一般都会使用词法变量以确保该变量预先是空的,有些人喜欢在变量
名后面添上_fh表示这是用来保存文件句柄的变量:

my $rocks_fh;
open $rocks_fh, '<''rocks .txt'
or die "Could not open rocks.txt: $I";

甚至于你还可以把这两步并作一步,直接在open函数中声明词法变量:

open my $rocks_fh, '<', ' rocks.txt'
or die "Could not open rocks.txt: $!";

得到保存文件句柄的变量之后,只要把原来使用裸字的地方改成用这个变量就可以了:

while( <$rocks_fh> ) {
chomp;
}

输出信息到某个文件句柄也可以使用这个变量。在原来使用裸字的地方使用这个标量变量,便能以适当的模式打开该文件句柄:

open my $rocks_fh, '>>'' rocks. txt'
or die "Could not open rocks.txt: $!";
foreach my $rock ( qw( slate lava granite ) ) {
say $rocks_fh $rock
}
print $rocks_fh "imestone\n" ;
close $rocks_fh;

这种写法仍然不需要额外的逗号。如果跟在print后面的第一个参数之后没有逗号,就说明它是一个文件句柄,即此处的$fh。要是误加了逗号,那么打印出来的东西会看起来怪怪的,这当然不是你所希望的:

print $rocks_fh, "limestone\n"; #错误

下面这两条语句实质上是不同的:

print STDOUT;
print $rock_fh; # 错误,这应该不是你的本意

在第-一个 例子中,Perl知道STDOUT是文件句柄,因为它就是个裸字。由于其后没有任何参数,所以它会打印默认变量 中 的 内 容 。 而 在 第 二 个 例 子 中 , P e r l 无 法 预 先 判 断 _中的内容。而在第二个例子中,Perl无法预先判断 Perlrock__fh是否为文件句柄,只有运行到这条语句时才知道变量里面保存的是不是文件句柄,所以它只好假设标量变量$rock_ fh是要输出的字符串变量。要解决这样的问题并不难,只要用花括号围住文件句柄,PerI就能明白它的正确含义,即便这个文件句柄保存在数组或哈希中也没关系:

print { $rock_fh }; #默认打印$_中的内容
print { $rocks[0] } "sandstone\n" ;

习题

  1. [7]写一个功能跟cat相似的程序,但将各行内容反序后输出(有些操作系统会有一个名为tac的类似工具)。假如用./tac fred barney betty来运行你的程序,它的输出结果应该是betty文件的最后一行到第一行, 接着是文件barney与fred,同样是由最后一行到第一行。(如果你将此程序取名为tac, 请一定要在运行时加上./,这样才不会运行你的系统中现有的同名程序! )
    参考答案:
print reverse <>;
  1. [8]写一个程序,要求用户分行键入各个字符串,然后以20个字符宽、向右对齐的方式输出每个字符串。为了确定输出结果在适当的字段中,请一并输出由数字组成的“标尺行(rule line)”(只是为了方便调试)。请确定自己没有误用19个字符宽的字段!比如输入hello good-bye后 应该会得到下面这样的输出结果:
    123456789012345678901234567890123456789012345678901234567890
    hello
    good-bye

参考答案:

print "enter some lines, then press ctrl-D:\n";
chomp(my @line = <STDIN>);
print "1234567890"x7,"12345\n"
foreach(@lines){
printf "%20s\n",$_;
}
  1. [8]修改上一个程序,让用户自行选择字符宽度,因此在键入30的时候,hello、good-bye (在不同行上)应该会向右对齐到第30个字符(提示:关于如何控制变量的内插,请参阅第二章中的“字符串中的标量变量内插”一节)。附加题:根据用户键入的宽度,自动调整标尺行的宽度。
    3
    参考答案:
print "what column width would you like?";
chomp(my $width = <STDIN>);
print "enter some lines, then press ctrl-D:\n";
chomp(my @line = <STDIN>);
print "1234567890"x(($width+9)/10),"\n"
foreach(@lines){
printf "%${width}s\n",$_;
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页