Learning Perl 学习笔记 Ch13 文件目录操作

  1. Perl程序运行时以自己的工作目录(working directory)作为起点,Perl提供了一个和shell中cd命令类似的操作符chdir来改变当前的工作目录,但这是一个系统调用,并不等同于shell中的cd,所以shell中类似波浪线~访问用户目录的功能并没有体现在Perl版本的chdir中。同样的,如果chdir执行失败,会将错误信息写入$!
    demo13-1:
#!/usr/bin/perl
print "Where you want to go? ";
chomp(my $path = );
chdir $path or die "Cannot change dir to $path: $!";
./demo13-1
Where you want to go? ../ch12

./demo13-1
Where you want to go? ./test
Cannot change dir to ./test: No such file or directory at ./demo13-1 line 4,  line 1.

  1. Unix中,shell会把形如**.sh的文件名通配符展开成完整的文件名列表,这被称为文件名通配,通常这个工作是由Unix shell完成的,需要命令行参数的程序拿到的参数列表就已经是完整的文件名列表了。在Perl中移植了这个功能,对应的操作符是glob
    glob的参数是字符串,里面是文件通配符的模式,返回的是一个元素由文件名字符串组成的列表,且按字母顺序排序。
my @all_files = glob "*";
my @pm_files = glob "*.pm";
  • 需要注意的是,文件名通配的模式规则不同于正则表达式的模式规则,eg. "*"表示匹配除了文件名以.开头以外的所有文件,".*"则表示文件名以.开头的所有文件,"*.sh"表示后缀是sh的所有文件。这个规则和shell的通配符规则是一致的,而且是跨平台的,即使在不支持shell的操作系统,比如windows上,Perl的glob操作符也遵守同样的规则
  • 文件名通配的模式以空格表示并集关系 eg."*.txt *.sh"表示通配以sh为后缀的文件和以txt为后缀的所有文件
my @all_files_including_dot = glob "* .*";

glob出现以前,Perl使用“尖括号语法”——在尖括号内放上文件名通配模式——来实现同样的功能,例如:my @all_files = <*>;这和my @all_files = glob "*"的效果相同。“尖括号语法”支持变量内插,在一对尖括号的变量名,会被自动替换成变量实际的内容。

my $dir = "/etc";
my @dir_files = <$dir/* $dir/.*>;

由于尖括号也用来表示从文件句柄中读取输入,这有时会导致歧义,Perl解析器采用的方法是判断尖括号内的是否是严格意义的Perl标识符,若是则表示“从文件句柄读取”;若否,则表示文件名通配。

my @files = ;  ## glob
my @lines = ;  ## 从文件句柄读取
my $name = "FRED";
my @files = <$name/*>;  ## glob
my @lines = <$name>;  ## 间接句柄读取(indirect filehandler read)

间接文件句柄就是指字符串变量中存储着实际文件句柄的名字,除了可以用上面的尖括号操作符的变量内插以外,还可以用readline操作符读取间接文件句柄

my $name = "FRED";
my @lines = readline $name; ## 等价于 @lines = readline FRED;

  1. 目录句柄和文件句柄类似,只不过它读取到的是目录里的内容(比如文件名,...,次级目录名),Perl提供了一组和文件句柄类似的操作弗来操作目录句柄
操作 目录句柄操作符 文件句柄操作符
打开句柄 opendir open
关闭句柄 closedir close
读取内容 readdir readline

demo13-2

print "Please type in directory: ";
chomp (my $directory = );
opendir DIR, $directory or die "Failed to open dir '$directory': $!\n";
foreach $filename (readdir DIR){
  print "$filename\n";
}
close DIR;
  • 目录句柄会在程序结束时自动关闭,也会在用这个句柄打开一个新目录前自动关闭
  • 目录句柄返回的列表只有文件名,不包含路径
./demo13-2
Please type in directory: ./
.
..
demo13-1
demo13-2
  • 目录句柄和glob操作符有以下区别:
    • 目录句柄读到的名称列表没有经过排序,glob返回的列表是按字母顺序排序的
    • 目录句柄不支持模式匹配,会返回所有的文件
    • 目录句柄返回的列表也包含...
      由于这些区别,所以如果要用目录句柄实现类似glob的文件名通配功能,只能自己编码实现
while $file (readdir DIR){
  next if $file eq "." or $file eq ".."; ## 在结果中排除 .(当前目录)和 ..(上级目录)
  next if $file =~ /^\./; ## 在结果中排除文件名以.开头的文件
  next unless $file =~ /\.pm$/; ## 在结果中过滤后缀为pm的文件
}

这里用正则表达式的模式取代了文件名通配的模式


  1. Perl提供一组和Unix系统兼容的文件操作。
    删除文件操作符unlink和unix shell的rm命令类似,可以直接删除文件。unlink的参数是列表,会将列表中的所有文件都删除,所以可以和glob连用,实现rm加文件通配符的效果
unlink glob "*.out" ## 效果和rm *.out相同
  • unlink的返回值是成功删除的文件数,如果删除失败,Perl会把系统返回的错误信息放入变量$!,但是每次删除失败都会更新$!的内容,如果有多个文件删除失败,只会记录最后一次的失败信息。如果想要精确的控制删除结果,只能用循环结构来控制每次删除一个文件
foreach my $file (@file_name_list){
  unlink $file or warn "delete file '$file' failed:$!\n"; #每次删除文件失败都会打印失败信息,且不会中断程序
}
  • rm一样,unlink也不能删除目录

  1. Perl的rename函数可以和Unix shell的mv命令一样重命名文件,而且和mv一样,可以用来移动文件 (当然,执行程序的用户必须具有对应目录的权限)
rename "/home/user/oldfile", "/etc/user/arch" or die "move file to new directory failed: $!\n";

rename返回1或0表示操作结果成功或失败,如果失败,Perl把系统调用返回的错误信息放入变量$!
因为rename每次只能操作一个文件,所以如果要实现批量重命名或者移动文件的需求,就需要借助循环来实现

foreach my $old_file (glob "*.old"){
  (my $new_file = $old_file) =~ s/.old$/.new/; ## 借助正则表达式将原来的文件名后缀改为.new
  if(-e $new_file) {
    warn "Cannot rename file '$old_file' because file '$new_file' already exists.\n";
  } else{
    rename $old_file, $new_file or warn "Rename file '$old_file' to '$new_file' failed: $!\n";  
  }
}
  • rename函数有一个限制,如果要移动文件,必须在同一个文件系统(挂载卷)内移动,这是来自Unix系统的限制。

  1. Perl支持Unix系统的硬链接和软链接文件,首先需要了解Unix文件系统是如何工作的。Unix系统中,通过文件索引号(inode)标识文件在磁盘上的位置,目录就相当于是一张由文件名和inode组成的对照关系表。不同的文件名可能指向同一个inode,每一个inode都有一个链接计数,代表目录中,直接指向inode的记录,这就是硬链接,Unix中的普通文件,以及对文件的删除,改名等操作,实际上操作的都是硬链接。如果用系统命令ln file1, link_file1创建一个硬链接link_file1,那么它和file1是完全等价的,即使删除了file1,但是link_file1仍然记录了inode信息,仍然可以读取和修改文件内容。但如果所有硬链接都被删除,即使文件内容还存在硬盘上,但是操作系统再也无法访问和修改这部分内容了,所以会把这个inode节点标记为空闲,可以被新的文件内容覆盖。
  • 这也是为什么rename函数和mv命令必须在同一个文件系统(挂载卷)内移动文件,因为改变的只是目录信息,文件实际存储的inode没有发生变化,但如果要跨卷移动文件,则涉及寻找空闲inode,分配inode,复制文件内容等一系列复杂操作。
  • 硬链接只能针对实际的文件创建,而不能创建目录的硬链接
ln ../ch12 ./ch1111
ln: ../ch12: hard link not allowed for directory
  • Perl的link "file_name" "link_name"函数提供和系统命令ln相同的功能:创建硬链接,并且返回布尔值0或1表示执行结果。如果执行失败(比如针对目录创建硬链接),Perl解析器会把系统调用返回的错误信息放在$!变量里

软链接(符号链接)则和inode解耦,它只局限在目录系统中,指向目录系统中的一个位置,甚至可以指向一个实际不存在路径,如果软链接指向的位置确实有文件存在,那么对软链接的操作都会被跳转到实际的文件进行(删除除外,删除只会删除软链接文件自己)。Unix使用命令ln -s file1, soft_link_file1来创建软链接,Perl则提供symlink "file1", "soft_link_file1"来创建软链接,同样有布尔值返回和$!保存的错误信息。同时,Perl的readlink $soft_link函数可以读取符号链接实际指向的位置并返回,如果参数不是符号链接,则返回undef

  • 要分清硬链接和软连接需要设想一下Unix访问文件的过程,如果是一个硬链接文件(也就是普通的Unix文件),OS从目录中读取到inode信息,然后调用I/O,从磁盘中读取对应位置的内容。如果访问的是一个符号链接,则OS先去符号链接指向的位置去找是否存在对应的文件,如果不存在则返回"file not found",如果恰好存在文件(硬链接),则读取它的inode,然后调用I/O去访问磁盘内容。

所以Perl中删除文件的函数名字是unlink ,它可以删除硬链接和软链接,如果删除的是硬链接(也就是普通文件)还会修改inode链接计数,如果inode计数减至0,则释放inode。


  1. 建立目录可以用Perl提供的和Unix shell同名的函数mkdir
  • mkdir接受两个参数,第一个目录名,如果没有包含路径则在当前工作目录下创建,第二个参数是赋予新目录的权限,返回0或1表示操作结果,如果创建目录失败会把错误信息放在$!变量里
mkdir "new_dir", 0755 or warn "create new directory failed: $!\n";

权限位采用unix的三个八进制的形式,即使在非Unix系统上也是如此,如果第二个参数不是八进制数(第一个数位0表示八进制数)则会转换成八进制,特别的是,如果是字符串转换成数字只会转换成十进制数,即使第一位是0也不会转换为八进制数。必须使用函数oct显式进行转换

#!/usr/bin/perl
print "Please type in new directory: ";
chomp(my $new_dir = );
print "Please type in permission(as octal):";
chomp(my $perm = );

mkdir $new_dir, oct($perm) or die "Create new directory '$new_dir' with permission ACL '$perm' failed: $!\n";
print "Success.\n";
./demo13-3
Please type in new directory: test
Please type in permission(as octal):755
Success.

删除目录可以使用Perl提供的rmdir函数,它接受一个目录名做参数,这意味着它一次只能删除一个目录,而不像unlink可以把参数列表中的文件都删除。rmdir只能删除空目录,如果目录非空,包含了文件或者子目录,就需要先逐级删除子目录和文件,或者使用File::Path::rmtree来递归删除


  1. 修改权限和属组使用Perl的chmodchown函数,在Unix shell中也有同名的命令,他们的功能完全一致。
  • chmod函数可以接受一个参数列表,其中第一个参数必须是表示权限控制表ACL的八进制数,其余的参数都是需要修改的文件或目录列表
chmod 0755, "demo_file", "demo_file2", "demo_dir";

执行结果返回成功修改的条目数量,如果失败,会将错误信息放在$!变量
Unix shell中的chmod可以接受诸如u+x这样符号表示的权限,但是Perl版本的chmod函数不支持

  • chown函数可以同时修改属主和属组,但是只能接受数字形式的用户和组标识符,它同样接受一个参数列表,其中第一个参数是用户标识符,第二个参数是组标识符,其余的参数是需要修改的文件或目录列表
    要获取用户的标识符可以使用getpwnam函数,要获取组标识符可以使用getgrnam函数,如果不存在则会返回undef
defined(my $user = getpwnam "test_user") or die "bad users";
defined(my $group = getgrnam "test_user_group") or die "bad group";
chown $user, $group, glob "* .*";

执行结果返回成功修改的条目数量,如果失败,会将错误信息放在$!变量


  1. Perl可以用utime函数修改文件的atime最近访问时间和mtime最近修改时间,至于ctime则没有函数可以修改。utime接收一个参数列表,第一个参数是新的访问时间,第二个参数是新的修改时间,其余参数是文件列表,新的时间必须以时间戳(单位为秒)的形式传入
    demo13-4
#!/usr/bin/perl
my $access_now = time; ## time函数返回当前时间戳
my $modified_yesterday = $access_now - 24 * 60 * 60;
$result = utime $access_now, $modified_yesterday, glob "demo13-*";
print "success modified $result file(s)\n";

你可能感兴趣的:(Learning Perl 学习笔记 Ch13 文件目录操作)