1. 程式人生 > >Perl目錄操作

Perl目錄操作

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修改成功,也會被立刻設為當前的值,這是因為它主要用給增量備份的程式使用。(待增補)