Named Unary and File Test Operators (Programming Perl) Book Home Programming PerlSearch this book

3.10. Named Unary and File Test Operators

Some of the "functions" described in Chapter 29, "Functions" are really unary operators. Table 3-2 lists all the named unary operators.

Table 3.2. Named Unary Operators

-X (file tests) gethostbyname localtime return
alarm getnetbyname lock rmdir
caller getpgrp log scalar
chdir getprotobyname lstat sin
chroot glob my sleep
cos gmtime oct sqrt
defined goto ord srand
delete hex quotemeta stat
do int rand uc
eval lc readlink ucfirst
exists lcfirst ref umask
exit length require undef

Unary operators have a higher precedence than some of the binary operators. For example:

sleep 4 | 3;
does not sleep for 7 seconds; it sleeps for 4 seconds and then takes the return value of sleep (typically zero) and bitwise ORs that with 3, as if the expression were parenthesized as:
(sleep 4) | 3;
Compare this with:
print 4 | 3;
which does take the value of 4 ORed with 3 before printing it (7 in this case), as if it were written:
print (4 | 3);
This is because print is a list operator, not a simple unary operator. Once you've learned which operators are list operators, you'll have no trouble telling unary operators and list operators apart. When in doubt, you can always use parentheses to turn a named unary operator into a function. Remember, if it looks like a function, it is a function.

Another funny thing about named unary operators is that many of them default to $_ if you don't supply an argument. However, if you omit the argument but the token following the named unary operator looks like it might be the start of an argument, Perl will get confused because it's expecting a term. Whenever the Perl tokener gets to one of the characters listed in Table 3-3, the tokener returns different token types depending on whether it expects a term or operator.

Table 3.3. Ambiguous Characters

Character Operator Term
+ Addition Unary plus
- Subtraction Unary minus
* Multiplication *typeglob
/ Division /pattern/
< Less than, left shift <HANDLE>, <<END
. Concatenation .3333
? ?: ?pattern?
% Modulo %assoc
& &, && &subroutine

So a typical boo-boo is:

next if length < 80;
in which the < looks to the parser like the beginning of the <> input symbol (a term) instead of the "less than" (an operator) you were thinking of. There's really no way to fix this and still keep Perl pathologically eclectic. If you're so incredibly lazy that you cannot bring yourself to type the two characters $_, then use one of these instead:
next if length() < 80;
next if (length) < 80;
next if 80 > length;
next unless length >= 80;
When a term is expected, a minus sign followed by a single letter will always be interpreted as a file test operator. A file test operator is a unary operator that takes one argument, either a filename or a filehandle, and tests the associated file to see whether something is true about it. If the argument is omitted, it tests $_, except for -t, which tests STDIN. Unless otherwise documented, it returns 1 for true and "" for false, or the undefined value if the file doesn't exist or is otherwise inaccessible. Currently implemented file test operators are listed in Table 3-4.

Table 3.4. File Test Operators

Operator Meaning
-r File is readable by effective UID/GID.
-w File is writable by effective UID/GID.
-x File is executable by effective UID/GID.
-o File is owned by effective UID.
-R File is readable by real UID/GID.
-W File is writable by real UID/GID.
-X File is executable by real UID/GID.
-O File is owned by real UID.
-e File exists.
-z File has zero size.
-s File has nonzero size (returns size).
-f File is a plain file.
-d File is a directory.
-l File is a symbolic link.
-p File is a named pipe (FIFO).
-S File is a socket.
-b File is a block special file.
-c File is a character special file.
-t Filehandle is opened to a tty.
-u File has setuid bit set.
-g File has setgid bit set.
-k File has sticky bit set.
-T File is a text file.
-B File is a binary file (opposite of -T).
-M Age of file (at startup) in days since modification.
-A Age of file (at startup) in days since last access.
-C Age of file (at startup) in days since inode change.

Note that -s/a/b/ does not do a negated substitution. Saying -exp($foo) still works as expected, however--only single letters following a minus are interpreted as file tests.

The interpretation of the file permission operators -r, -R, -w, -W, -x, and -X is based solely on the mode of the file and the user and group IDs of the user. There may be other reasons you can't actually read, write, or execute the file, such as Andrew File System (AFS) access control lists.[3] Also note that for the superuser, -r, -R, -w, and -W always return 1, and -x and -X return 1 if any execute bit is set in the mode. Thus, scripts run by the superuser may need to do a stat in order to determine the actual mode of the file or temporarily set the UID to something else.

[3]You may, however, override the built-in semantics with the use filetest pragma. See Chapter 31, "Pragmatic Modules".

The other file test operators don't care who you are. Anybody can use the test for "regular" files:

while (<>) {
    chomp;
    next unless -f $_;      # ignore "special" files
    ...
}

The -T and -B switches work as follows. The first block or so of the file is examined for strange characters such as control codes or bytes with the high bit set (that don't look like UTF-8). If more than a third of the bytes appear to be strange, it's a binary file; otherwise, it's a text file. Also, any file containing ASCII NUL (\0) in the first block is considered a binary file. If -T or -B is used on a filehandle, the current input (standard I/O or "stdio") buffer is examined rather than the first block of the file. Both -T and -B return true on an empty file, or on a file at EOF (end-of-file) when testing a filehandle. Because Perl has to read a file to do the -T test, you don't want to use -T on special files that might hang or give you other kinds of grief. So on most occasions you'll want to test with a -f first, as in:

next unless -f $file && -T $file;
If any of the file tests (or either the stat or lstat operator) are given the special filehandle consisting of a solitary underline, then the stat structure of the previous file test (or stat operator) is used, thereby saving a system call. (This doesn't work with -t, and you need to remember that lstat and -l will leave values in the stat structure for the symbolic link, not the real file. Likewise, -l _ will always be false after a normal stat.)

Here are a couple of examples:

print "Can do.\n" if -r $a || -w _ || -x _;

stat($filename);
print "Readable\n" if -r _;
print "Writable\n" if -w _;
print "Executable\n" if -x _;
print "Setuid\n" if -u _;
print "Setgid\n" if -g _;

print "Sticky\n" if -k _;
print "Text\n" if -T _;
print "Binary\n" if -B _;

File ages for -M, -A, and -C are returned in days (including fractional days) since the script started running. This time is stored in the special variable $^T ($BASETIME). Thus, if the file changed after the script started, you would get a negative time. Note that most time values (86,399 out of 86,400, on average) are fractional, so testing for equality with an integer without using the int function is usually futile. Examples:

next unless -M $file > .5;      # files are older than 12 hours
&newfile if -M $file < 0;       # file is newer than process
&mailwarning if int(-A) == 90;  # file ($_) was accessed 90 days ago today
To reset the script's start time to the current time, say this:
$^T = time;



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.