Подтвердить что ты не робот

Оператор проверки файлов Perl -f возвращает true для символических ссылок

Раньше я думал, что -f проверил файл, чтобы узнать, был ли он обычным файлом, а не что-то еще. Но Perl, похоже, ведет себя по-другому. Я просмотрел запись perldoc, и она говорит:

-f  File is a plain file.

Предположим, что у меня есть каталог, имеющий один файл с именем file1 и 5 символических ссылок 1 2 3 4 5, каждый из которых указывает на file1, например:

-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 ./

Если я запустил find в этом каталоге, используя фильтр -type f, он выдаст результат, как ожидалось:

%  find . -type f
./file1

Но когда я запускаю perl script с помощью оператора -f, он дает следующий вывод:

%  ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ }'
1
2
3
4
5
file1

Когда я добавляю тест -l тоже, он работает как ожидалось:

%  ls | perl -e 'while(<>) { chomp; print "$_\n" if -f $_ and not -l $_}'
file1

Являются ли символические ссылки одинаковыми? Если да, то почему? Является ли неправильным использование теста файла?

4b9b3361

Ответ 1

Быстрые решения

$ ls | perl -lne 'print if stat && -f _'
1
2
3
4
5
file1

$ ls | perl -lne 'print if lstat && -f _'
file1

Символьные ссылки и поиск

По умолчанию GNU find никогда не вызывает различий или не следует за символическими ссылками, но в документации find описаны переключатели, которые меняют эту политику.

Параметры, управляющие поведением ссылок по ссылкам, следующие: -

-P
find не разыгрывает символические ссылки вообще. Это поведение по умолчанию. Эта опция должна быть указана перед любым из имен файлов в командной строке.

-H
find не вызывает разыгрывание символических ссылок (за исключением случаев, когда имена файлов в командной строке разыменовываются). Если символическая ссылка не может быть разыменована, используется информация для самой символической ссылки. Эта опция должна быть указана перед любым из имен файлов в командной строке.

-L
find где возможно, разыменовывают символические ссылки, и там, где это невозможно, они используют свойства самой символической ссылки. Эта опция должна быть указана перед любым из имен файлов в командной строке. Использование этой опции также подразумевает то же поведение, что и параметр -noleaf. Если вы позже используете опции -H или -P, это не отключает -noleaf.

-follow
Этот параметр является частью "выражения" и должен указываться после имен файлов, но в остальном он эквивалентен -L. Опция -follow влияет только на те тесты, которые появляются после нее в командной строке. Этот параметр устарел. По возможности вам следует использовать -L.

Преобразование команд find в Perl

Стандартное распространение поставляется с утилитой find2perl, которая совместима с find от старых систем Unix.

$ find2perl . -type f | perl
./file1

Вместо этого мы можем запросить файлы, которые являются либо обычными файлами, либо ссылками на простые файлы.

$ find2perl . -follow -type f | perl
./1
./2
./3
./4
./5
./file1

В коде find2perl создается, по умолчанию wanted sub, переданное в find из модуля File:: Find

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -f _
    && print("$name\n");
}

но с -follow, получим

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);

    (($dev,$ino,$mode,$nlink,$uid,$gid) = stat($_)) &&
    -f _
    && print("$name\n");
}

Обратите внимание, что единственное отличие заключается в том, что wanted вызывает stat или lstat, где последний задокументирован как

lstat EXPR
lstat

То же самое, что и функция stat (включая установку специального дескриптора файла _), но статирует символическую ссылку вместо укажите символические ссылки. Если символические ссылки не реализованы в вашей системе, выполняется нормальное stat. Более подробную информацию см. В документации для stat.

Если EXPR опущен, укажите $_.

Как показывает результат выборки из find2perl, вы можете выразить свое намерение с помощью оператора filetest, но уточните семантику символических ссылок с вашим выбором stat по сравнению с lstat.

Этот забавный _ токен

_ на концах приведенных выше быстрых решений - это специальный дескриптор файла, который упоминается в lstat документации. Он содержит копию последнего результата от stat или lstat, чтобы избежать необходимости многократно выполнять эти дорогостоящие системные вызовы. Операторы Filetest, такие как -f, -r, -e и -L также заполняют этот буфер:

Если какой-либо из тестов файла (или оператора stat или lstat) задан специальным дескриптором файла, состоящим из одиночной подчеркивания, то структура stat предыдущего теста файла (или оператора stat) является используется, сохраняя системный вызов. (Это не работает с -t, и вам нужно помнить, что lstat и -L оставляют значения в структуре stat для символической ссылки, а не реального файла.) (Также, если буфер статистики был заполнен по вызову lstat -t и -B будет reset с результатами stat _). Пример:

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 _;

Ответ 2

Когда вы проверяете символическую ссылку, тест выполняется по тому, на что указывает символическая ссылка, если вы не используете тест -l symlink.

Параллельно с системными вызовами stat и lstat Linux, которые ведут себя аналогично. То есть, если вы stat символическая ссылка, вы получите результат для цели символической ссылки, тогда как если вы lstat символическая ссылка, вы получите результат для самой символической ссылки. Это поведение преднамеренно, поэтому наивные программы не должны заботиться о символических ссылках, а символические ссылки будут работать по назначению.

Вы должны обнаружить, что если ваша символическая ссылка относится к каталогу, тест -f является ложным, а тест -d равен true.

Ответ 3

По умолчанию все операторы проверки файлов (кроме -l) используют stat() для проверки, что означает, что они прозрачны для символических ссылок. -f возвращает true в обычный файл или символическую ссылку на обычный файл.

Чтобы вместо этого использовать lstat(), вы должны сначала lstat использовать тесты файлов на специальном дескрипторе файла _, в котором хранятся результаты из последней операции stat или lstat.

perl -e 'while(<>) { chomp; print "$_\n" if lstat $_ && -f _ }'