版权声明:转载请说明出处! https://blog.csdn.net/qq_39556143/article/details/85270106
Perl目录操作
1. 工作目录
1.1 当前工作目录
程序运行的时候总有一个相应的工作目录,后续要做的事情都是从这个目录开始的。借助标准模块的Cwd模块,我么可以查看当前目录。
#!/usr/bin/perl
#code1
use v5.10;
use Cwd;
say "The cuurent working directory is:",getcwd();
#打印的路径应该就是程序的保存位置,和在Shell中执行pwd的结果相同。
#code2
#如果使用相对路径(没有提供文件系统树顶端的路径)打开文件,perl会按照当前目录定位这个路径。
#当前目录/home/fred,运行以下代码读取文件:
open my $fh,'<','relative/path.txt';
#perl会打开/home/fred/relative/path.txt文件
1.2 修改工作目录
如果不希望当前工作目录在程序所在目录,可以使用 chdir 操作符修改,它的用法和 shell 中的 cd 一致
- chdir ‘/etc’ or die “Can’t change to /etc: $!”
- 由Perl程序启动的所有进程都会继承Perl程序的工作目录。我们可以改变perl的工作目录,但是不能改变其父进程,即shell的工作目录。
- chdir 不加参数,Perl 会认为我们需要回到用户主目录,和在 shell 中使用不带参数的 cd 一致。
1.3 文件名通配
一般而言,shell 会将命令行中的文件名模式展开为所有匹配的文件名,这就叫做文件名匹配 (glob),比如吧*.pm这个参数交给 echo, shell 将会将它展开为所有相匹配的文件列表。
#!/usr/bin/perl
$ echo *.pm # $表示在命令行输入
#打印
banery.pm fred.pm dino.pm
#这里的echo命令并不知道此刻使用了文件名通配,因为shell会先把*.pm展开为一系列的文件名,然后再交给echo处理。
#以下程序只是简单地输出所有命令行参数
foreach $arg (@ARGV) { #程序不进行文件名统配,shell已经展开并传入@ARGV,perl只时是用数组而已
print "One arg is $arg\n";
}
#我们在命令行执行以下命令
$ perl show_arg *.pm
#打印
One arg is banery.pm
One arg is fred.pm
One arg is dino.pm
- 不过有时候需要在程序内部使用类似 ‘*.pm’ 的模式我们可以使用 glob 操作符;
- glob操作符的效果和 shell 统配的结果完全相同。
#!/usr/bin/perl
my @all_file = glob '*'; #取得当前目录中的所有文件并按照字母进行排序,不包括以点号开头的文件
my @pm_file = glob '*.pm'; #得到的列表和之前使用的*.pm时的相同;
#如果要匹配不同的模式,可以在参数中用 空格 隔开各个模式
my @all_file = glob '.* *'; #取得当前目录中所有的文件并按照字符进行排序,包括以点号开头的文件
1.4 文件名通配的隐式语法
- 在 glob 操作符出现之前,程序可以使用尖括号语法调用相同的功能,看起来和读取文件文件句柄类似;
#!/usr/bin/perl
my @all_perl = <*>; #效果和 my @all_file = glob '*'; 一致
#perl会把尖括号内出现的变量替换成它的值,类似于变量内插,这表示使用文件名通配之前,perl会把变量展开
my $dir = '/etc';
my @all_file = <$dir/* $dir/.*>; #先展开 $dir 的值,然后尽心文件名通配,最后得到 /etc 目录下的所有文件
- 那么,Perl如何区分文件名通配操作和读取文件呢?
- 如果<>内时满足Perl标识符条件的,就可以按照文件句柄进行处理 (字母+下划线+数字)
- 除此之外,使用文件名通配操作
- Perl 在编译极端就决定了使用文件名通配还是从文件句柄读取,因此与变量内容无关。
#!/usr/bin/perl
my @files = <FRED/*>; #文件名通配
my @files = <FRED>; #从文件句柄读取
my @files = <$FRED>; #从文件句柄读取,如果<>内时一个简单地标量变量,那么就是间接文件句柄读取
my $name = 'fred';
my @files = <$name/*>; #文件名通配
#可以使用 readline 读取间接文件句柄(用的机会较少)
my $name = 'FRED';
my @lines = readline FRED; # 从 FRED 读取
my @lines = readline $name; # 从 FRED 读取
1.5 目录句柄
- 如果想从目录获得文件列表,可以使用目录句柄 (directory handle);
- 目录句柄看起来像文件句柄,使用起来区别不大;
- 目录句柄和文件句柄类似,会在程序结束或者重启打开其他目录前自动关闭。
- 操作符变化:
操作符类型 | 文件 | 目录 |
---|---|---|
打开 | open | opendir |
读取 | readline | readdir(得到文件名,不是文件内容) |
关闭 | close | closedir |
- 目录句柄也可以使用裸字作为目录句柄名称,但这样存在和文件句柄类似的问题,不能作为参数传递。如果把句柄(文件或目录都可以)指定到标量变量中,就可以传递参数,使用更方便。
- 在标量环境中,readdir函数返回下一个目录记录,如果没有下一个目录记录,返回undef。在列表环境中,它返回在该目录中所有剩下的记录,如果剩下没有记录了,那么这个返回可能是一个空列表。
#!/usr/bin/perl
my $dir_to_process = '/etc';
opendir DIR, $dir_to_process; #用裸字存储目录句柄
opendir my $dh,$dir_to_process; #用标量变量存储目录句柄
foreach ($some = readdir $dh) { #此处不能用DIR替换$dh
next unless $name =~ /\.pm\z/; #这里是正则表达式,不是文件名通配
next if $name eq '.' or $name eq '..'; #'.'表示当前目录,'..'表示上一级目录
...
}
#需要注意的是readdir读取到的文件名不包含路径,只是目录下的文件名而已
#如果/etc目录下有文件test.pl,我们只能看到test.pl,但是看不到/etc/test.pl这样的绝对路径
#此时需要我们手动添加:
my $dir_to_process = '/etc';
opendir my $dh,$dir_to_process or die "Can't open \$dir_to_process";
while (my $name = readdir $dh) {
next if $name =~ /\A\./; #不处理以点号开头的文件
$name = catfile($dir_to_process,$name); #拼接为完整路径
next unless -f $name and -r $name; #只处理可读文件
...
}
2. 文件和目录的操作
2.1 删除文件
- Unix中: $ rm slat fred
- 在shell中,我们可以使用 rm 命令来删除单个或者多个文件;
- Perl中: unlink ‘slat’,‘fred’; unlink qw/slat fred/;
- 在perl中我们可以使用 unlink 操作符指定要删除的文件。
- 返回值为成功删除文件的数目。
- Perl中: link ‘chicken’,‘egg’;
- 为文件新增硬链接,如果文件是一个房间,那么现在文件有两个门,chicken+egg;
- 删除任何chicken 和 egg 任何一个文件,文件都保留在存储空间。
- Perl中: symlink ‘chicken’,‘egg’;
- 为文件新增软链接,如果文件是一个房间,那么现在文件有一个门chicken,egg告诉用户门在chicken那里;
- 删除chicken,文件释放存储空间,egg指向空文件;
- 删除egg,文件依然存储在原来的存储空间,chicken仍然指向该文件;
链接是文件名和磁盘上文件具体存放位置之间的映射关系,但是有些操作系统允许出现多个直接指向相同存储区域的文件链接。直到所有指向链接都销毁后,操作系统用才能回收目标存储区域,unlink 的作用是销毁给定文件名到存储区域的链接,如果这是最后一个链接,那么存储文件的存储区域也就被释放了。
- 由于 unlink 可以对文件列表进行操作,而且 glob 返回的也是文件列表。因此,两者可以搭配使用。
#!/usr/bin/perl
unlink glob '*.o'; #和在shell中执行 rm *.o 效果一致,但是省去了进程开销
#unlink的返回值是成功删除的文件数目:
my $success = unlink qw/slat fred/;
unlink $file or say "\$file unlink failed: $!"; # $!返回失败的错误类型
- 注: 不能使用unlink删除目录,目录删除应该使用rmdir
2.2 重命名文件
- Unix:mv old new
- 在 shell 中可以使用 mv 命令进行文件名的更改;
- Perl:rename ‘old’,‘new’;
- 在 perl 中可以使用 rename 实现和mv一样的功能;
- 成功返回真,否则返回假,也可以使用 $! 查看错误信息;
- 参数能不能是列表???
#!/usr/bin/perl
rename '/etc/som/some_file','som_file'; #rename也可以实现文件移动功能
rename '/etc/som/some_file'=>'som_file'; #rename也可以使用胖箭头替换逗号
#使用rename实现文件批量修改(把文件名为.old的文件全部改为文件名为.new)
foreach my $file (glob "*.old") {
(my $newfile = $file) =~ s/\.old$/.new/; #先将$file的值给$newfile,然后对$newfile进行正则表达式匹配
#s/a/b/,a中是正则表达式,b是字符串。所以a中需要的点号需要转义,但是b中的点号不需要转义
# my $newfile = $file =~ s/\.old$/.new/r; #可以使用修饰符r,省略括号
if (-e $newfile) {
warn "can't rename $file to $newfile, $newfile exist\n";
} else if (rename $file,$newfile){
say "rename $file to $newfile success."
} else {
warn "rename $file to $newfile failed: $!"
}
}
2.3 创建和删除目录
- 创建目录的命令:mkdir ‘directory_name’, 0755;
- ‘’ 内为目录名称,0755位目录的初始权限:-rwxr-xr-x
- mkdir 不要求第二个参数是八进制的,他只是需要某个数字,除非你能迅速算出 0755 的493,否则不如直接让Perl进行计算,并且开头的0不能胜率,否则755会被认为是十进制,对应八进制的1363.
#!/usr/bin/perl
mkdir 'directory_name',0755 or warn "Can't create directory: $!";
#mkdir的第二个参数必须是数字,不能是字符串如下:
my $name = 'fred';
my $permissions = '0755';
mkdir $name,$permissions; #糟糕,0755会被当做十进制进行处理,用1363的权限来建立文件目录
#我们可以使用oct()函数,强行把字符串当做八进制
mkdir $name,oct($permissions);
#使用oct()函数可以方便我们从命令行取得参数:
my ($name,$perm) = @ARGV; #取命令行最先传入的两个参数,作为目录名称和权限
mkdir $name,oct($perm) or die "can't create directory: $!";
- 删除目录的命令:rmdir ‘directory_name’;
- rmdir 不能删除目录列表,每次只能删除一个目录( unlink 的参数可以是列表,但是 rmdir 的参数不能是列表)
- rmdir 删除空目录时会导致失败,这时候需要先删除文件目录中的内容,然后删除已经清空的目录。
#!/usr/bin/perl
#每次只能删除一个空目录
foreach $dir (qw(fred dino betty)) { #rmdir的参数不能是列表
rmdir $dir or die "can't delete $dir directory: $!\n"; #删除非空目录时失败
}
#如果我们需要一个目录存储程序产生的临时文件
my $temp_dir = "/tmp/scratch_$$"; #使用当前进程ID命名变量
mkdir $temp_dir,0755 or warn "Can't create directory: $!";
...
#将$temp_dir当做临时文件的存储目录
...
unlink glob "$temp_dir/* $temp_dir/.*"; #删除临时文件目录中所有文件
rmdir $temp_dir; #现在是空目录,可以删除了 (如果文件目录中有子文件目录,失败)
- 注意:
- 初始的临时目录名将会包含当前的进程标识符,每个当前运行的进程都有一个独一无二的进程数字代号,在perl中,这个数字代号存储在变量$$中。
2.4 修改权限
- Perl中也可以使用chmod来修改文件或目录的权限:
- chmod 0755, ‘fred’, ‘barney’;
- 返回值为操作成功的条目数量;
- 第一个参数表示权限0755,和前面 mkdir 的用法一致,也可以使用oct()函数;
- Unix中的chmod接受用符号表示的权限,如+x,go=u-w,但是 perl 中的 chmod 函数不允许使用符号。
2.4.1 修改文件属主
- 只要操作系统允许,可以使用 chown 改变文件的属主和其用户组,属主和用户组会被同时修改,并且在指定时必须给出数字形式的用户标识符和组标识符。
- 返回操作文件成功数目。
#!/usr/bin/perl
my $user = 1004;
my $group = 100;
chown $user,$group,glob "*.o"; #第一个参数为用户标识符的数字形式,第二个参数为用户所属组的数字形式
#chown的第三个参数是要操作的文件,可以接受列表输入
#如果需要处理的不是数字,需要用 getpwnam 函数将用户名转换为用户编号,用 getgrnam 得到用户组的组编号:
defined(my $user = getpwnam 'fred') or die "bad user\n";
defined(my $group = getgrnam 'grp1') or die "bad group\n";
chown $user,$group,glob "*.o";
2.4.2 修改时间戳
- 某些罕见的情况下,可能需要修改某个文件最近的更改或访问时间来欺骗其他程序,此时我们需要用utime函数来造假。
- utime $now,$ago,glob “*.old”;
- 第一个参数atime:新的访问时间;(使用内部时间戳的格式,和 stat 返回的时间格式一致)
- 第二个参数mtime:新的更改时间;(使用内部时间戳的格式,和 stat 返回的时间格式一致)
- 其余参数:要修改时间戳的文件列表;
#!/usr/bin/perl
my $now = time; #time时可以返回当前时间的内部时间戳的函数
my $ago = $now - 24*60*60; #目前时间减去一天的秒数
utime \$now,\$ago,glob "*.old"; #将最后的访问时间改为现在,最后的更改时间改为一天前的现在
- 当然,我们也可以把时间设置为过去和未来的任何时间,因此可以修改为未来才会产生的文件。
- 当文件有任何变动时,第三个时间戳(ctime)一定会被设为当前值,没有函数可以篡改,就算用utime修改成功,也会被立刻设为当前的值,这是因为它主要用给增量备份的程序使用。(待增补)