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
的任何选项)
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
的任何选项,也不需要处理多于两个的参数。)
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();
如果没有输入,代码会生成一个包含两个文本字段的表单;如果有输入,代码会将两个字段的值相加并输出结果。