Learning Perl 學(xué)習(xí)筆記 Ch13 文件目錄操作

  1. Perl程序運(yùn)行時(shí)以自己的工作目錄(working directory)作為起點(diǎn),Perl提供了一個(gè)和shell中cd命令類似的操作符chdir來(lái)改變當(dāng)前的工作目錄,但這是一個(gè)系統(tǒng)調(diào)用,并不等同于shell中的cd,所以shell中類似波浪線~訪問用戶目錄的功能并沒有體現(xiàn)在Perl版本的chdir中。同樣的,如果chdir執(zhí)行失敗,會(huì)將錯(cuò)誤信息寫入$!
    demo13-1:
#!/usr/bin/perl
print "Where you want to go? ";
chomp(my $path = <STDIN>);
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, <STDIN> line 1.

  1. Unix中,shell會(huì)把形如**.sh的文件名通配符展開成完整的文件名列表,這被稱為文件名通配,通常這個(gè)工作是由Unix shell完成的,需要命令行參數(shù)的程序拿到的參數(shù)列表就已經(jīng)是完整的文件名列表了。在Perl中移植了這個(gè)功能,對(duì)應(yīng)的操作符是glob。
    glob的參數(shù)是字符串,里面是文件通配符的模式,返回的是一個(gè)元素由文件名字符串組成的列表,且按字母順序排序。
my @all_files = glob "*";
my @pm_files = glob "*.pm";
  • 需要注意的是,文件名通配的模式規(guī)則不同于正則表達(dá)式的模式規(guī)則,eg. "*"表示匹配除了文件名以.開頭以外的所有文件,".*"則表示文件名以.開頭的所有文件,"*.sh"表示后綴是sh的所有文件。這個(gè)規(guī)則和shell的通配符規(guī)則是一致的,而且是跨平臺(tái)的,即使在不支持shell的操作系統(tǒng),比如windows上,Perl的glob操作符也遵守同樣的規(guī)則
  • 文件名通配的模式以空格表示并集關(guān)系 eg."*.txt *.sh"表示通配以sh為后綴的文件和以txt為后綴的所有文件
my @all_files_including_dot = glob "* .*";

glob出現(xiàn)以前,Perl使用“尖括號(hào)語(yǔ)法”——在尖括號(hào)內(nèi)放上文件名通配模式——來(lái)實(shí)現(xiàn)同樣的功能,例如:my @all_files = <*>;這和my @all_files = glob "*"的效果相同?!凹饫ㄌ?hào)語(yǔ)法”支持變量?jī)?nèi)插,在一對(duì)尖括號(hào)的變量名,會(huì)被自動(dòng)替換成變量實(shí)際的內(nèi)容。

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

由于尖括號(hào)也用來(lái)表示從文件句柄中讀取輸入,這有時(shí)會(huì)導(dǎo)致歧義,Perl解析器采用的方法是判斷尖括號(hào)內(nèi)的是否是嚴(yán)格意義的Perl標(biāo)識(shí)符,若是則表示“從文件句柄讀取”;若否,則表示文件名通配。

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

間接文件句柄就是指字符串變量中存儲(chǔ)著實(shí)際文件句柄的名字,除了可以用上面的尖括號(hào)操作符的變量?jī)?nèi)插以外,還可以用readline操作符讀取間接文件句柄

my $name = "FRED";
my @lines = readline $name; ## 等價(jià)于 @lines = readline FRED;

  1. 目錄句柄和文件句柄類似,只不過(guò)它讀取到的是目錄里的內(nèi)容(比如文件名,...,次級(jí)目錄名),Perl提供了一組和文件句柄類似的操作弗來(lái)操作目錄句柄
操作 目錄句柄操作符 文件句柄操作符
打開句柄 opendir open
關(guān)閉句柄 closedir close
讀取內(nèi)容 readdir readline

demo13-2

print "Please type in directory: ";
chomp (my $directory = <STDIN>);
opendir DIR, $directory or die "Failed to open dir '$directory': $!\n";
foreach $filename (readdir DIR){
  print "$filename\n";
}
close DIR;
  • 目錄句柄會(huì)在程序結(jié)束時(shí)自動(dòng)關(guān)閉,也會(huì)在用這個(gè)句柄打開一個(gè)新目錄前自動(dòng)關(guān)閉
  • 目錄句柄返回的列表只有文件名,不包含路徑
./demo13-2
Please type in directory: ./
.
..
demo13-1
demo13-2
  • 目錄句柄和glob操作符有以下區(qū)別:
    • 目錄句柄讀到的名稱列表沒有經(jīng)過(guò)排序,glob返回的列表是按字母順序排序的
    • 目錄句柄不支持模式匹配,會(huì)返回所有的文件
    • 目錄句柄返回的列表也包含...
      由于這些區(qū)別,所以如果要用目錄句柄實(shí)現(xiàn)類似glob的文件名通配功能,只能自己編碼實(shí)現(xiàn)
while $file (readdir DIR){
  next if $file eq "." or $file eq ".."; ## 在結(jié)果中排除 .(當(dāng)前目錄)和 ..(上級(jí)目錄)
  next if $file =~ /^\./; ## 在結(jié)果中排除文件名以.開頭的文件
  next unless $file =~ /\.pm$/; ## 在結(jié)果中過(guò)濾后綴為pm的文件
}

這里用正則表達(dá)式的模式取代了文件名通配的模式


  1. Perl提供一組和Unix系統(tǒng)兼容的文件操作。
    刪除文件操作符unlink和unix shell的rm命令類似,可以直接刪除文件。unlink的參數(shù)是列表,會(huì)將列表中的所有文件都刪除,所以可以和glob連用,實(shí)現(xiàn)rm加文件通配符的效果
unlink glob "*.out" ## 效果和rm *.out相同
  • unlink的返回值是成功刪除的文件數(shù),如果刪除失敗,Perl會(huì)把系統(tǒng)返回的錯(cuò)誤信息放入變量$!,但是每次刪除失敗都會(huì)更新$!的內(nèi)容,如果有多個(gè)文件刪除失敗,只會(huì)記錄最后一次的失敗信息。如果想要精確的控制刪除結(jié)果,只能用循環(huán)結(jié)構(gòu)來(lái)控制每次刪除一個(gè)文件
foreach my $file (@file_name_list){
  unlink $file or warn "delete file '$file' failed:$!\n"; #每次刪除文件失敗都會(huì)打印失敗信息,且不會(huì)中斷程序
}
  • rm一樣,unlink也不能刪除目錄

  1. Perl的rename函數(shù)可以和Unix shell的mv命令一樣重命名文件,而且和mv一樣,可以用來(lái)移動(dòng)文件 (當(dāng)然,執(zhí)行程序的用戶必須具有對(duì)應(yīng)目錄的權(quán)限)
rename "/home/user/oldfile", "/etc/user/arch" or die "move file to new directory failed: $!\n";

rename返回1或0表示操作結(jié)果成功或失敗,如果失敗,Perl把系統(tǒng)調(diào)用返回的錯(cuò)誤信息放入變量$!
因?yàn)?code>rename每次只能操作一個(gè)文件,所以如果要實(shí)現(xiàn)批量重命名或者移動(dòng)文件的需求,就需要借助循環(huán)來(lái)實(shí)現(xiàn)

foreach my $old_file (glob "*.old"){
  (my $new_file = $old_file) =~ s/.old$/.new/; ## 借助正則表達(dá)式將原來(lái)的文件名后綴改為.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函數(shù)有一個(gè)限制,如果要移動(dòng)文件,必須在同一個(gè)文件系統(tǒng)(掛載卷)內(nèi)移動(dòng),這是來(lái)自Unix系統(tǒng)的限制。

  1. Perl支持Unix系統(tǒng)的硬鏈接和軟鏈接文件,首先需要了解Unix文件系統(tǒng)是如何工作的。Unix系統(tǒng)中,通過(guò)文件索引號(hào)(inode)標(biāo)識(shí)文件在磁盤上的位置,目錄就相當(dāng)于是一張由文件名和inode組成的對(duì)照關(guān)系表。不同的文件名可能指向同一個(gè)inode,每一個(gè)inode都有一個(gè)鏈接計(jì)數(shù),代表目錄中,直接指向inode的記錄,這就是硬鏈接,Unix中的普通文件,以及對(duì)文件的刪除,改名等操作,實(shí)際上操作的都是硬鏈接。如果用系統(tǒng)命令ln file1, link_file1創(chuàng)建一個(gè)硬鏈接link_file1,那么它和file1是完全等價(jià)的,即使刪除了file1,但是link_file1仍然記錄了inode信息,仍然可以讀取和修改文件內(nèi)容。但如果所有硬鏈接都被刪除,即使文件內(nèi)容還存在硬盤上,但是操作系統(tǒng)再也無(wú)法訪問和修改這部分內(nèi)容了,所以會(huì)把這個(gè)inode節(jié)點(diǎn)標(biāo)記為空閑,可以被新的文件內(nèi)容覆蓋。
  • 這也是為什么rename函數(shù)和mv命令必須在同一個(gè)文件系統(tǒng)(掛載卷)內(nèi)移動(dòng)文件,因?yàn)楦淖兊闹皇悄夸浶畔?,文件?shí)際存儲(chǔ)的inode沒有發(fā)生變化,但如果要跨卷移動(dòng)文件,則涉及尋找空閑inode,分配inode,復(fù)制文件內(nèi)容等一系列復(fù)雜操作。
  • 硬鏈接只能針對(duì)實(shí)際的文件創(chuàng)建,而不能創(chuàng)建目錄的硬鏈接
ln ../ch12 ./ch1111
ln: ../ch12: hard link not allowed for directory
  • Perl的link "file_name" "link_name"函數(shù)提供和系統(tǒng)命令ln相同的功能:創(chuàng)建硬鏈接,并且返回布爾值0或1表示執(zhí)行結(jié)果。如果執(zhí)行失?。ū热玑槍?duì)目錄創(chuàng)建硬鏈接),Perl解析器會(huì)把系統(tǒng)調(diào)用返回的錯(cuò)誤信息放在$!變量里

軟鏈接(符號(hào)鏈接)則和inode解耦,它只局限在目錄系統(tǒng)中,指向目錄系統(tǒng)中的一個(gè)位置,甚至可以指向一個(gè)實(shí)際不存在路徑,如果軟鏈接指向的位置確實(shí)有文件存在,那么對(duì)軟鏈接的操作都會(huì)被跳轉(zhuǎn)到實(shí)際的文件進(jìn)行(刪除除外,刪除只會(huì)刪除軟鏈接文件自己)。Unix使用命令ln -s file1, soft_link_file1來(lái)創(chuàng)建軟鏈接,Perl則提供symlink "file1", "soft_link_file1"來(lái)創(chuàng)建軟鏈接,同樣有布爾值返回和$!保存的錯(cuò)誤信息。同時(shí),Perl的readlink $soft_link函數(shù)可以讀取符號(hào)鏈接實(shí)際指向的位置并返回,如果參數(shù)不是符號(hào)鏈接,則返回undef

  • 要分清硬鏈接和軟連接需要設(shè)想一下Unix訪問文件的過(guò)程,如果是一個(gè)硬鏈接文件(也就是普通的Unix文件),OS從目錄中讀取到inode信息,然后調(diào)用I/O,從磁盤中讀取對(duì)應(yīng)位置的內(nèi)容。如果訪問的是一個(gè)符號(hào)鏈接,則OS先去符號(hào)鏈接指向的位置去找是否存在對(duì)應(yīng)的文件,如果不存在則返回"file not found",如果恰好存在文件(硬鏈接),則讀取它的inode,然后調(diào)用I/O去訪問磁盤內(nèi)容。

所以Perl中刪除文件的函數(shù)名字是unlink ,它可以刪除硬鏈接和軟鏈接,如果刪除的是硬鏈接(也就是普通文件)還會(huì)修改inode鏈接計(jì)數(shù),如果inode計(jì)數(shù)減至0,則釋放inode。


  1. 建立目錄可以用Perl提供的和Unix shell同名的函數(shù)mkdir
  • mkdir接受兩個(gè)參數(shù),第一個(gè)目錄名,如果沒有包含路徑則在當(dāng)前工作目錄下創(chuàng)建,第二個(gè)參數(shù)是賦予新目錄的權(quán)限,返回0或1表示操作結(jié)果,如果創(chuàng)建目錄失敗會(huì)把錯(cuò)誤信息放在$!變量里
mkdir "new_dir", 0755 or warn "create new directory failed: $!\n";

權(quán)限位采用unix的三個(gè)八進(jìn)制的形式,即使在非Unix系統(tǒng)上也是如此,如果第二個(gè)參數(shù)不是八進(jìn)制數(shù)(第一個(gè)數(shù)位0表示八進(jìn)制數(shù))則會(huì)轉(zhuǎn)換成八進(jìn)制,特別的是,如果是字符串轉(zhuǎn)換成數(shù)字只會(huì)轉(zhuǎn)換成十進(jìn)制數(shù),即使第一位是0也不會(huì)轉(zhuǎn)換為八進(jìn)制數(shù)。必須使用函數(shù)oct顯式進(jìn)行轉(zhuǎn)換

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

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函數(shù),它接受一個(gè)目錄名做參數(shù),這意味著它一次只能刪除一個(gè)目錄,而不像unlink可以把參數(shù)列表中的文件都刪除。rmdir只能刪除空目錄,如果目錄非空,包含了文件或者子目錄,就需要先逐級(jí)刪除子目錄和文件,或者使用File::Path::rmtree來(lái)遞歸刪除


  1. 修改權(quán)限和屬組使用Perl的chmodchown函數(shù),在Unix shell中也有同名的命令,他們的功能完全一致。
  • chmod函數(shù)可以接受一個(gè)參數(shù)列表,其中第一個(gè)參數(shù)必須是表示權(quán)限控制表ACL的八進(jìn)制數(shù),其余的參數(shù)都是需要修改的文件或目錄列表
chmod 0755, "demo_file", "demo_file2", "demo_dir";

執(zhí)行結(jié)果返回成功修改的條目數(shù)量,如果失敗,會(huì)將錯(cuò)誤信息放在$!變量
Unix shell中的chmod可以接受諸如u+x這樣符號(hào)表示的權(quán)限,但是Perl版本的chmod函數(shù)不支持

  • chown函數(shù)可以同時(shí)修改屬主和屬組,但是只能接受數(shù)字形式的用戶和組標(biāo)識(shí)符,它同樣接受一個(gè)參數(shù)列表,其中第一個(gè)參數(shù)是用戶標(biāo)識(shí)符,第二個(gè)參數(shù)是組標(biāo)識(shí)符,其余的參數(shù)是需要修改的文件或目錄列表
    要獲取用戶的標(biāo)識(shí)符可以使用getpwnam函數(shù),要獲取組標(biāo)識(shí)符可以使用getgrnam函數(shù),如果不存在則會(huì)返回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 "* .*";

執(zhí)行結(jié)果返回成功修改的條目數(shù)量,如果失敗,會(huì)將錯(cuò)誤信息放在$!變量


  1. Perl可以用utime函數(shù)修改文件的atime最近訪問時(shí)間和mtime最近修改時(shí)間,至于ctime則沒有函數(shù)可以修改。utime接收一個(gè)參數(shù)列表,第一個(gè)參數(shù)是新的訪問時(shí)間,第二個(gè)參數(shù)是新的修改時(shí)間,其余參數(shù)是文件列表,新的時(shí)間必須以時(shí)間戳(單位為秒)的形式傳入
    demo13-4
#!/usr/bin/perl
my $access_now = time; ## time函數(shù)返回當(dāng)前時(shí)間戳
my $modified_yesterday = $access_now - 24 * 60 * 60;
$result = utime $access_now, $modified_yesterday, glob "demo13-*";
print "success modified $result file(s)\n";
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容