Perl文件測試操作和stat函數
在shell中通過test命令或者中括號[]
可以進行文件測試以及其它類型的測試,例如判斷文件是否存在,比較操作是否為真等等。perl作為更強大的文本處理語言,它也有文件測試類表達式,而且和shell的文件測試用的字母符號都類似。
perl中測試文件的屬性來源是perl的內置函數stat,它可以獲得文件的13項屬性。後文會介紹該函數。
測試符
測試符號都是短橫線開頭,加一個字母。例如,測試文件是否存在-e "a.log"
。在可能產生歧義的情況下,這些測試符可以用括號包圍,例如:(-e "a.log")
。
註意,perl主要應用於Unix類系統,而Unix中一切皆文件,所以下表中出現的"文件"如非特地指明,否則它既可表示普通文件,也表示目錄,還表示其它類型的文件。
以下是文件大小測試符:
符號 意義
----------------------------------------------
-e 文件是否存在
-z 文件是否存在且為空(對目錄而言,永遠為假)
-s 文件是否存在且不為空,返回值是文件大小,單位為字節
其中-s
會返回文件的字節數大小。對於真假的判斷,通過這個返回值也可以直接判斷,大於0表示非空,等於0表示空。
例如:
print "not empty" if "-s /tmp/a.log"; print "empty" unless "! -s /tmp/a.log"; print ((-s "/usr/bin/passwd") + 10); # 返回文件字節數+10
以下是文件類型測試符:
符號 意義(同時檢測文件的存在性)
----------------------------------------------
-f 文件是否為普通文件
-d 文件是否為目錄文件
-l 文件是否為軟鏈接(字符鏈接)
-b 文件是否為塊設備
-c 文件是否是字符設備文件
-p 文件是否為命名管道
-S 文件是否為socket文件
以下是權限類測試符:
首先區分一下Effective uid和real uid:
- real uid:文件調用者的uid
- effective uid:文件調用最後生效的uid
如未對文件進行特殊設置,real uid和effective uid是一致的。但是如果設置了setuid屬性,那麽文件在執行的時候會提升為某用戶的權限(一般是提升為root),這時候effective uid就是root,而real uid則是文件調用者的uid。
比如longshuai用戶讀取一個文件,則這個文件的real uid就是longshuai。比如/usr/bin/passwd這個文件設置了setuid,調用這個程序的用戶是real uid,最後執行的時候提權為root用戶,那麽這個程序的effective uid就是root。
一般來說,只會去檢測real uid的權限屬性。只有極少數文件會設置setuid/setgid/sticky,去檢測這類文件的權限的機會就更小了。
符號 意義(同時檢測文件的存在性)
----------------------------------------------
-r 文件(對effective uid)是否可讀
-w 文件(對effective uid)是否可寫
-x 文件(對effective uid)是否可執行
-o 文件(對effective uid)的所有者
-R 文件(對real uid)是否可讀
-W 文件(對real uid)是否可寫
-X 文件(對real uid)是否可執行
-O 文件(對real uid)的所有者
-u 文件是否設置了setuid (setuid只對可執行普通文件有效)
-g 文件是否設置了setgid (setgid只對普通文件或目錄有效)
-k 文件是否設置了sticky (sticky屬性只對目錄有效)
例如:
print "readable" if -r "/tmp/a.log";
以下是其它測試符:
符號 意義
----------------------------------------------
-e 文件是否存在
-z 文件是否存在且為空(對目錄而言,永遠為假)
-s 文件是否存在且不為空,返回值是文件大小,單位為字節
-M 最後一次修改(mtime)距離目前的天數
-A 最後一次訪問(atime)距離目前的天數
-C 最後一次inode修改(ctime)距離目前的天數
-T 文件看起來像文本文件
-B 文件看起來像二進制文件
-t 文件句柄是否為TTY設備(該測試只對文件句柄有效)
上面-M/-A/-C
會計算天數,它是(小時數/24)來計算的。例如,6小時前修改的文件,它的天數就是0.25天。
上面的-T/-B
是mime類型猜測,perl會根據文件的前幾個字節來猜測這個文件,當然,它不一定能猜對,但大多數時候是沒什麽問題的。
例如:
# 修改文件mtime時間為6小時前
touch -m -t "6 hours ago" /tmp/b.log
如果perl程序內容為:
print (-M "/tmp/b.log");
它的執行結果將輸出0.25:
0.250162037037037
以下是文件mime類型猜測。腳本內容如下:
print "text file\n" if -T "$ARGV[0]";
print "Binary file\n" if -B "$ARGV[0]";
執行結果如下:
$ ./19.pl 1.plx
text file
$ ./19.pl /etc
Binary file
$ ./19.pl initramfs-3.10.0-327.el7.x86_64.img
Binary file
$ ./19.pl /bin/ls
Binary file
測試符的陷阱
測試符操作可以省略參數,這時它的操作對象是默認變量$_
。但是對於-t
測試符來說例外,它的默認操作對象是<STDIN>
,因為它的對象是文件句柄,而非文件名。
例如:
#!/usrb/bin/perl
foreach (`ls`){
chomp;
print "$_ is executable\n" if -x;
}
但是省略參數的時候,很容易出錯。操作符會把後面任何一個非空格字符(串)當作它的參數。例如,-s
返回的是字節大小,你想讓它按kb顯示。
print (-s / 1024);
但這時的"-s"會把/
當作它的測試對象參數,而不是$_
。所有,省略參數的時候,建議將測試符用括號包圍起來:
print ((-s) / 1024);
測試文件多個屬性符(1):緩存文件測試信息
如果想要同時測試文件的可讀性、可寫性:
print "writable and readable\n" if -w "/tmp/a.log" and -r "/tmp/a.log";
但是這不是最佳方式,因為perl每執行一次測試符表達式,都需要對文件執行一次stat函數,但實際上第二次測試執行的stat是多余的,因為一次測試就可以獲取到文件的所有屬性。
perl中有一個特殊的緩存文件句柄_
(就是一個下劃線),它可以最近的緩存文件屬性信息。
下面是等價的操作:
print "writable and readable\n" if -w "/tmp/a.log" and -r _;
_
的緩存周期可以延續,直到測試下一個文件,所以將兩次測試分開寫也可以。但需要註意的是,在使用_
的時候要確保它緩存的對象正是所需要的文件屬性。
print "a.log writable\n" if -w "/tmp/a.log";
print "b.log writable\n" if -w "/tmp/b.log";
print "b.log writable\n" if -r _;
上面第三個語句中_
緩存的是/tmp/b.log文件的屬性。
測試文件多個屬性符(2):棧式文件測試
可以將多個測試操作符連在一起寫。
- 連寫的時候,從右向左依次執行,並按照and邏輯運算符判斷真假。也就是說,先測試靠近文件名的操作符
- 對於返回真/假值的測試符,連寫的測試符前後順序不會影響結果
- 對於返回非真/假值的測試符(即-s/-M/-A/-C),連寫測試符時應盡量謹慎,最保險的方式是不要連寫
例如下面兩個語句,它們在最終測試結果上是等價的。第一個語句先測試可寫性,再測試可讀性,只有兩者均為真時if條件才為真。
print "writable and readable\n" if -r -w "/tmp/a.log";
print "writable and readable\n" if -w -r "/tmp/a.log";
但是,返回非真/假值的測試符,需要小心小心再小心。例如,-s
返回的是文件字節數:
@arr=`ls`;
foreach (@arr){
chomp;
if (-s -f $_ < 512){ # 這裏的結果會出乎意料
print "${_}'s size < 512 bytes\n";
}
}
上面的if條件子句等價於(-f $_ and -s _) < 512
,它會輸出小於512字節的普通文件,以及所有非普通文件。因為and是短路的,如果測試的目標$_
不是一個普通文件,而是一個目錄,-f $_
就會返回假,並結束測試,然後這部分表達式和512做數值比較,假對應的數值是0,它永遠會返回真。
所有,對於返回非真/假值的測試符,應該避免測試符連寫:
@arr=`ls`;
foreach (@arr){
chomp;
if (-f $_ and -s _ < 512){
print "${_}'s size < 512 bytes\n";
}
}
stat函數和lstat函數
雖然文件測試符有很多,且測試的屬性來源都是stat函數,但stat函數返回的信息(共13項屬性)比支持的文件測試符還要多。
註:Unix操作系統裏有一個stat命令,它也是返回文件的屬性信息,和perl的內置stat函數基本類似。
它返回13項屬性先後順序分別是:
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
= stat($filename);
屬性 意義
---------------------------------------------------
dev :文件所屬文件系統的設備ID
inode :文件inode號碼
mode :文件類型和文件權限(兩者都是數值表示)
nlink :文件硬鏈接數
uid :文件所有者的uid
gid :文件所屬組的gid
rdev :文件的設備ID(只對特殊文件有效,即設備文件)
size :文件大小,單位字節
atime :文件atime的時間戳(從1970-01-01開始計算的秒數)
mtime :文件mtime的時間戳(從1970-01-01開始計算的秒數)
ctime :文件ctime的時間戳(從1970-01-01開始計算的秒數)
blksize :文件所屬文件系統的block大小
blocks :文件占用block數量(一般是512字節的塊大小,可通過unix的stat -c "%B"獲取塊的字節)
需要註意的是,$mode返回的是文件類型和文件權限的結合體,且文件權限並非直接的8進制權限值,要計算出Unix系統中直觀的權限數值(如0755、0644),需要和0777做位運算。
use 5.010;
$filename=$ARGV[0];
my @arr = ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
= stat($filename);
say '$dev :',$arr[0];
say '$inode :',$arr[1];
say '$mode :',$arr[2];
say '$nlink :',$arr[3];
say '$uid :',$arr[4];
say '$gid :',$arr[5];
say '$rdev :',$arr[6];
say '$size :',$arr[7];
say '$atime :',$arr[8];
say '$mtime :',$arr[9];
say '$ctime :',$arr[10];
say '$blksize :',$arr[11];
say '$blocks :',$arr[12];
返回結果:
$dev :2050
$inode :67326520
$mode :33188
$nlink :1
$uid :0
$gid :0
$rdev :0
$size :12
$atime :1533544992
$mtime :1533426824
$ctime :1533426824
$blksize :4096
$blocks :8
如果要計算文件權限,則:
printf "perm :%04o\n",$mode & 0777; # 將返回0644、0755類型的權限值
也可以直接一點取出某項屬性的值:
my $mode = (stat($filename))[2];
以下是stat函數的其它一些註意事項:
- stat函數返回布爾值表示是否成功stat:
- 如果stat成功,則立即設置這些屬性變量,並緩存到特殊文件句柄
_
- 如果stat失敗,則返回空列表
- 如果stat成功,則立即設置這些屬性變量,並緩存到特殊文件句柄
- 如果stat函數測試的是特殊文件句柄
_
,它將不會重新測試緩存文件,而是直接返回緩存的屬性信息 - 如果省略stat函數的參數,則默認測試
$_
對於軟鏈接文件,stat會追蹤到鏈接的目標。如果不想追蹤,則使用lstat函數替代stat。lstat如果測試的目標不是軟鏈接,則返回空列表。
Perl文件測試操作和stat函數