Reputation: 1361
I used to think that -f
tested a file to see if it was a regular file, and not anything else. But Perl seems to be behaving differently. I looked up the perldoc entry , and it says:
-f File is a plain file.
Suppose I have a directory having one file called file1
, and 5 symbolic links 1 2 3 4 5
, each of which point to file1
, like so:
-rw-r--r-- file1
lrwxrwxrwx 1 -> file1
lrwxrwxrwx 2 -> file1
lrwxrwxrwx 3 -> file1
lrwxrwxrwx 4 -> file1
lrwxrwxrwx 5 -> file1
drwxr-xr-x ../
drwxr-xr-x ./
If I run find on this directory using -type f
filter, it gives output as expected:
% find . -type f
./file1
But when I run a perl script using -f operator, it gives following output:
% ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ }'
1
2
3
4
5
file1
When I add the test of -l
too, it works as expected:
% ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ and not -l $_}'
file1
Are symbolic links considered plain files too? If so, Why? Is my usage of the file test incorrect?
Upvotes: 19
Views: 5372
Reputation: 139531
$ ls | perl -lne 'print if stat && -f _' 1 2 3 4 5 file1 $ ls | perl -lne 'print if lstat && -f _' file1
By default, GNU find
never dereferences or follows symbolic links, but the find
documentation describes switches that change this policy.
The options controlling the behaviour of find with respect to links are as follows :-
-P
find
does not dereference symbolic links at all. This is the default behaviour. This option must be specified before any of the file names on the command line.
-H
find
does not dereference symbolic links (except in the case of file names on the command line, which are dereferenced). If a symbolic link cannot be dereferenced, the information for the symbolic link itself is used. This option must be specified before any of the file names on the command line.
-L
find
dereferences symbolic links where possible, and where this is not possible it uses the properties of the symbolic link itself. This option must be specified before any of the file names on the command line. Use of this option also implies the same behaviour as the-noleaf
option. If you later use the-H
or-P
options, this does not turn off-noleaf
.
-follow
This option forms part of the “expression” and must be specified after the file names, but it is otherwise equivalent to-L
. The-follow
option affects only those tests which appear after it on the command line. This option is deprecated. Where possible, you should use-L
instead.
The standard distribution comes with a find2perl
utility that is compatible with find
from older Unix systems.
$ find2perl . -type f | perl ./file1
We could instead ask for files that are either plain files themselves or links to plain files.
$ find2perl . -follow -type f | perl ./1 ./2 ./3 ./4 ./5 ./file1
In the code find2perl
generates, the default wanted
sub passed to find
from the File::Find module is
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
-f _
&& print("$name\n");
}
but with -follow
, we get
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
(($dev,$ino,$mode,$nlink,$uid,$gid) = stat($_)) &&
-f _
&& print("$name\n");
}
Notice that the only difference is whether wanted
calls stat
or lstat
, where the latter is documented as
lstat EXPR
lstat
Does the same thing as the
stat
function (including setting the special_
filehandle) but stats a symbolic link instead of the file the symbolic link points to. If symbolic links are unimplemented on your system, a normalstat
is done. For much more detailed information, please see the documentation forstat
.If EXPR is omitted, stats
$_
.
As the sample outputs from find2perl
shows, you can express your intent with the filetest operator but be precise about the semantics of symlinks with your choice of stat
versus lstat
.
_
tokenThe _
at the ends of the quick solutions above is the special filehandle that the lstat
documentation mentions. It holds a copy of the most recent result from stat
or lstat
as a way to avoid having to repeatedly make those expensive system calls. Filetest operators such as -f
, -r
, -e
, and -l
also fill this buffer:
If any of the file tests (or either the
stat
orlstat
operator) is given the special filehandle consisting of a solitary underline, then the stat structure of the previous file test (orstat
operator) is used, saving a system call. (This doesn't work with-t
, and you need to remember thatlstat
and-l
leave values in the stat structure for the symbolic link, not the real file.) (Also, if the stat buffer was filled by anlstat
call,-T
and-B
will reset it with the results ofstat _
). Example: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 _;
Upvotes: 4
Reputation: 8532
By default all the file test operators (apart from -l
) use stat()
to test, which means they are transparent to symlinks. -f
returns true on a regular file, or a symlink to a regular file.
In order to use lstat()
instead, you should first lstat
then use the file tests on the special _
filehandle, which stores the results from the most recent stat
or lstat
operation.
perl -e 'while(<>) { chomp; print "$_\n" if lstat $_ && -f _ }'
Upvotes: 3
Reputation: 13906
When you test a symlink, the test is carried out on the thing that the symlink points to unless you use the -l
symlink test.
This parallels the stat
and lstat
Linux system-calls which behave similarly. That is, if you stat
a symlink, you'll get the result for the target of the symlink, whereas if you lstat
the symlink, you'll get the result for the symlink itself. This behaviour is intentional so that naïve programs don't have to care about symlinks, and symlinks will just work as intended.
You should find that if your symlink refers to a directory, the -f
test is false and the -d
test is true.
Upvotes: 17