1. 程式人生 > >Perl文件測試操作和stat函數

Perl文件測試操作和stat函數

單位 部分 node unix系統 設備id 括號 ada 特殊 文本處理

在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會追蹤到鏈接的目標。如果不想追蹤,則使用lstat函數替代stat。lstat如果測試的目標不是軟鏈接,則返回空列表。

Perl文件測試操作和stat函數