Perl目錄操作
阿新 • • 發佈:2018-12-30
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修改成功,也會被立刻設為當前的值,這是因為它主要用給增量備份的程式使用。(待增補)