Perl编程实践:文件操作与系统命令实现

内容分享5小时前发布
0 0 0

table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1rem;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
pre {
background-color: #f8f8f8;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
}

30、编写一个程序,将目录更改为输入指定的位置,然后按字母顺序列出更改目录后该目录下的文件名。(如果目录更改未成功,则不要显示文件列表,仅警告用户。)

以下是实现该功能的代码:


print "Where to? ";
chomp($newdir = <STDIN>);
chdir($newdir) || die "Cannot chdir to $newdir: $!";
foreach (<*>) {
    print "$_";
}


代码解释:

前两行用于提示用户输入并读取目录名称;

第三行尝试将目录更改为用户输入的名称,如果无法更改则终止程序;

foreach

循环遍历匹配

*

模式的文件名列表,并按字母顺序输出。

31、编写一个程序,其功能类似于

rm

命令,在程序被调用时删除作为命令行参数给出的文件。(你不需要处理

rm

的任何选项)

在 Perl 中可以使用

unlink

函数实现该功能。由于命令行参数可在程序启动时通过

@ARGV

数组获取,所以示例代码如下:


#!/usr/bin/perl
foreach my $file (@ARGV) {
    unlink($file);
}

将上述代码保存为一个

.pl

文件,例如

rm_like.pl

,然后在命令行中运行

perl rm_like.pl file1 file2 ...

即可删除指定的文件。注意,要在一个几乎为空的目录中测试此程序,以免意外删除有用的文件。

32、编写一个程序,其功能类似于

ln

命令,从第一个命令行参数到第二个命令行参数创建一个硬链接。(你不需要处理

ln

的任何选项,也不需要处理多于两个的参数。)

在 Perl 中可以这样实现:


link($ARGV[0], $ARGV[1]) || die "cannot link $ARGV[0] to $ARGV[1]";

这里

$ARGV[0]

代表第一个命令行参数,

$ARGV[1]

代表第二个命令行参数。

link

函数用于创建硬链接,如果创建失败则使用

die

输出错误信息。

33、编写一个程序,使其能够处理可选的 -s 开关。当输入 -s 开关时,程序应创建符号链接;若未输入 -s 开关,则创建硬链接。

以下为实现此功能的程序示例,此程序前几行检查第一个参数是否为

-s

,若为

-s

则将

$symlink

变量置为 1 并移除

-s

标志;中间部分处理目标为目录的情况;最后根据

$symlink

的值决定创建符号链接还是硬链接。


if ($ARGV[0] eq "-s") { # wants a symlink
    $symlink++; # remember that
    shift(@ARGV); # and toss the -s flag
}
($old, $new) = @ARGV; # name them
if (-d $new) { # new name is a directory, need to patch it up
    ($basename = $old) =~ s#.*/##s; # get basename of $old
    $new .= "/$basename"; # and append it to new name
}
if ($symlink) { # wants a symlink
    symlink($old,$new);
} else { # wants a hard link
    link($old,$new);
}

34、编写一个程序来解析date命令的输出,以获取当前的星期几。如果是工作日,打印“get to work”;否则,打印“go play”。


if (`date` =~ /^S/) { print "Go play!
"; } else { print "Get to work!
"; }

35、编写一个程序,从 /etc/passwd 文件中获取所有用户的真实姓名,然后转换 who 命令的输出,将登录名(第一列)替换为真实姓名。(提示:创建一个哈希表,键为登录名,值为真实姓名。)分别尝试使用反引号和管道打开 who 命令。哪种方式更简单?

以下是使用反引号和管道打开

who

命令的两种实现方式:

使用反引号:


open(PW, "/etc/passwd");
while (<PW>) {
    chomp;
    ($user, $gcos) = (split /:/)[0, 4];
    ($real) = split(/,/, $gcos);
    $real{$user} = $real;
}
close(PW);
foreach $_ (`who`) {
    ($login, $rest) = /^(S+)s+(.*)/;
    $login = $real{$login} if $real{$login};
    printf "%-30s %s
", $login, $rest;
}

使用管道:


open(PW, "/etc/passwd");
while (<PW>) {
    chomp;
    ($user, $gcos) = (split /:/)[0, 4];
    ($real) = split(/,/, $gcos);
    $real{$user} = $real;
}
close(PW);
open(WHO, "who|") || die "cannot open who pipe";
while (<WHO>) {
    ($login, $rest) = /^(S+)s+(.*)/;
    $login = $real{$login} if $real{$login};
    printf "%-30s %s
", $login, $rest;
}

哪种方式更简单

使用反引号的方式代码更简洁,逻辑更直观,可能相对简单;使用管道的方式可以在

who

命令开始输出字符时就开始处理,更节省时间,但代码稍复杂。

36、编写一个程序来读取文件名列表,将每个文件名拆分为头部和尾部组件。(最后一个斜杠之前的所有内容是头部,最后一个斜杠之后的所有内容是尾部。如果没有斜杠,则整个文件名都在尾部。)用类似 /fred、barney 和 fred/barney 这样的文件名进行测试。结果是否合理?

以下是一个满足需求的 Perl 程序示例:


# 打开文件(这里假设文件名存储在一个文件中,每行一个文件名)
open(FILE, "filenames.txt") or die "无法打开文件: $!";

# 逐行读取文件名
while (my $filename = <FILE>) {
    chomp($filename); # 去除换行符

    my $head = '';
    my $tail = $filename;

    # 查找最后一个斜杠的位置
    if ($filename =~ m#/#) {
        ($head, $tail) = $filename =~ m#(.*)/(.*)#;
    }

    print "文件名: $filename
";
    print "头部: $head
";
    print "尾部: $tail

";
}

close(FILE);

代码解释:


文件打开

:使用

open

函数打开存储文件名的文件

filenames.txt

,如果无法打开则使用

die

函数输出错误信息。


逐行读取

:使用

while

循环和

<FILE>

操作符逐行读取文件名,使用

chomp

函数去除换行符。


拆分文件名

:使用正则表达式

m#/#

检查文件名中是否包含斜杠,如果包含则使用

m#(.*)/(.*)#

正则表达式将文件名拆分为头部和尾部。


输出结果

:打印文件名、头部和尾部。

测试示例:

假设

filenames.txt

文件内容如下:


/fred
barney
fred/barney

程序运行结果如下:


文件名: /fred
头部: /
尾部: fred

文件名: barney
头部: 
尾部: barney

文件名: fred/barney
头部: fred
尾部: barney

从结果可以看出,程序正确地将文件名拆分为头部和尾部,结果是合理的。

37、编写一个程序,打印 /etc/passwd 文件中用户的真实姓名和登录名,并按每个用户的姓氏排序。如果两个人姓氏相同,该解决方案是否有效?

可以使用以下代码实现:


open(PW, "/etc/passwd") or die "Can't open /etc/passwd: $!";
my %real;
my %last;
while (<PW>) {
    chomp;
    my ($user, $x, $uid, $gid, $real) = split(/:/);
    $real{$user} = $real;
    ($last) = (split /s+/, $real)[-1];
    $last{$user} = "L$last";
}
close(PW);

for (sort by_last keys %last) {
    printf "%30s %8s
", $real{$_}, $_;
}

sub by_last {
    ($last{$a} cmp $last{$b}) || ($a cmp $b)
}

第一个循环创建了

%last

哈希,键为登录名,对应的值为用户的姓氏,还创建了

%real

哈希,包含完整的真实姓名。姓氏都转换为小写,以便

FLINTSTONE


Flintstone


flintstone

能排在一起。

第二个循环按

%last

的值对

%real

进行排序输出,使用

by_last

子例程中的排序定义。当两个人姓氏相同时,该解决方案会按登录名进一步排序,所以是有效的。

38、创建一个由句子组成的文件,每行一个句子。编写一个程序,将每个句子的首字符转换为大写,其余字符转换为小写。(当首字符不是字母时,程序是否仍然有效?如果句子不是每行一个,你会如何处理?)

以下是一个实现该功能的 Perl 程序示例:


#!/usr/bin/perl
use strict;
use warnings;

# 打开文件
open(my $file, '<', 'your_file.txt') or die "无法打开文件: $!";

# 逐行处理文件内容
while (my $line = <$file>) {
    chomp $line; # 去除行尾换行符
    my $first_char = substr($line, 0, 1);
    my $rest_chars = substr($line, 1);
    $first_char = uc($first_char); # 首字符大写
    $rest_chars = lc($rest_chars); # 其余字符小写
    my $new_line = $first_char . $rest_chars;
    print $new_line . "
";
}

# 关闭文件
close($file);

问题解答


当首字符不是字母时,程序是否仍然有效

:有效。

uc

函数对非字母字符无影响,所以当首字符不是字母时,它会保持原样。


如果句子不是每行一个,你会如何处理

:可以先按句子的结束标志(如句号、感叹号、问号等)分割文本,再对每个句子进行处理。示例代码如下:


#!/usr/bin/perl
use strict;
use warnings;

# 读取文件内容
open(my $file, '<', 'your_file.txt') or die "无法打开文件: $!";
my $content = do { local $/; <$file> };
close($file);

# 按句子结束标志分割文本
my @sentences = split(/([.!?])/, $content);

# 处理每个句子
foreach my $sentence (@sentences) {
    if ($sentence =~ /S/) {
        my $first_char = substr($sentence, 0, 1);
        my $rest_chars = substr($sentence, 1);
        $first_char = uc($first_char); # 首字符大写
        $rest_chars = lc($rest_chars); # 其余字符小写
        my $new_sentence = $first_char . $rest_chars;
        print $new_sentence;
    }
}

39、创建一个程序来打开 sendmail 别名数据库并打印出所有条目。

以下是实现该功能的代码:


dbmopen(%ALIAS, "/etc/aliases", undef) || die "No aliases!: $!";
while (($key,$value) = each(%ALIAS)) {
    chop($key,$value);
    print "$key $value
";
}

注:系统可能将别名 DBM 存于

/usr/lib/aliases

,若上述代码不行可尝试该路径。

40、创建两个程序:一个程序从标准输入读取数据,将其拆分为单词,并更新一个 DBM 文件以记录每个单词的出现次数;另一个程序打开该 DBM 文件,并按出现次数降序显示结果。在几个文件上运行第一个程序,查看第二个程序是否能获取正确的计数。

以下是两个满足需求的 Perl 程序:


程序 1(写入程序):


dbmopen(%WORDS,"words",0644);
while (<>) {
    foreach $word (split(/W+/)) {
        $WORDS{$word}++;
    }
}
dbmclose(%WORDS);


程序 2(读取程序):


dbmopen(%WORDS,"words",undef);
foreach $word (sort { $WORDS{$b} <=> $WORDS{$a} } keys %WORDS) {
    print "$word $WORDS{$word}
";
}

运行时,先使用第一个程序处理一些文件,然后运行第二个程序查看结果。

41、编写一个表单,提供两个输入字段,当用户提交表单时,将这两个字段的值相加。

以下是实现该功能的代码:


use strict;
use CGI qw(:standard);
print header(), start_html("Add Me");
print h1("Add Me");
if(param()) {
    my $n1 = param('field1');
    my $n2 = param('field2');
    my $n3 = $n2 + $n1;
    print p("$n1 + $n2 = <strong>$n3</strong>
");
} else {
    print hr(), start_form();
    print p("First Number:", textfield("field1"));
    print p("Second Number:", textfield("field2"));
    print p(submit("add"), reset("clear"));
    print end_form(), hr();
}
print end_html();

如果没有输入,代码会生成一个包含两个文本字段的表单;如果有输入,代码会将两个字段的值相加并输出结果。

© 版权声明

相关文章

暂无评论

none
暂无评论...