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?
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.
$ 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 :-
-Pfinddoes 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.
-Hfinddoes 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.
-Lfinddereferences 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-noleafoption. If you later use the-Hor-Poptions, 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-followoption affects only those tests which appear after it on the command line. This option is deprecated. Where possible, you should use-Linstead.
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 EXPRlstatDoes the same thing as the
statfunction (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 normalstatis 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
statorlstatoperator) is given the special filehandle consisting of a solitary underline, then the stat structure of the previous file test (orstatoperator) is used, saving a system call. (This doesn't work with-t, and you need to remember thatlstatand-lleave values in the stat structure for the symbolic link, not the real file.) (Also, if the stat buffer was filled by anlstatcall,-Tand-Bwill 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 _;
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 _ }'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With