Почему find иногда совпадает с аргументом пути командной строки?

9

В Linux

cd /tmp
mkdir foo; cd foo

Сейчас работает

find . -name 'foo'

не дает вывода. В то время как работает

find /tmp/foo -name 'foo'

Дает вывод, /tmp/fooкоторый не имеет смысла для меня. Может кто-нибудь объяснить, почему?

Йорк
источник
2
найти поиск внутри заданного пути, как указано. Так что, если вы введете ./это не совпадаетfoo
Костас
@ Костас: я это понимаю. То, что я не понимаю, это то, почему это имело значение, если я даю абсолютный путь find.
Йорк,
@York В определенных ситуациях нет поведения, которое всегда правильно. Представьте себе символическую ссылку с именем, barкоторое указывает на файл, fooкоторый находится за пределами пути поиска. Это совпадение или нет?
Хауке Лагинг
1
Они .и /tmp/fooне одинаковы - это две разные жесткие ссылки на один и тот же каталог; find /tmp/foo/. -name 'foo'тоже ничего не находит.
Джимми
@jimmij: когда я запускался find /tmp/foo -name 'foo', я просил bash найти в каталоге /tmp/fooфайл с именем "foo". Поскольку каталог /tmp/fooпуст, он не должен был ничего возвращать. Я не понимаю, почему это возвращается /tmp/foo. С другой стороны, когда я запускался find . -name 'foo', я спрашивал bash то же самое, то есть, находил файл в текущем каталоге (который оказался /tmp/foo) с именем «foo», и он не возвращает ничего, что имеет смысл.
Йорк,

Ответы:

15

findОбходит указанное дерево каталогов и оценивает данное выражение для каждого найденного файла. Обход начинается с заданного пути. Вот краткое изложение того, как find . -name fooработает:

  • Первый путь в командной строке: .
    • Соответствует ли базовое имя ( .) шаблону foo? Нет, так ничего не делай.
      Так получилось, что /tmp/fooэто другое имя для того же каталога. Но findне знает этого (и это не должно пытаться выяснить).
    • Является ли путь каталогом? Да, так пройти через это. Перечислите записи ., и для каждой записи выполните процесс обхода.
      • Каталог пуст: он не содержит никакой записи, кроме .и .., который findне проходит рекурсивно. Итак, работа закончена.

И find /tmp/foo:

  • Первый путь в командной строке: /tmp/foo
    • Соответствует ли базовое имя ( foo) шаблону foo? Да, так что условие соответствует.
      • С этим условием не связано никаких действий, поэтому выполните действие по умолчанию, которое заключается в печати пути.
    • Является ли путь каталогом? Да, так пройти через это. Перечислите записи /tmp/foo, и для каждой записи выполните процесс обхода.
      • Каталог пуст: он не содержит никакой записи, кроме .и .., который findне проходит рекурсивно. Итак, работа закончена.

Бывает так, что .и один /tmp/fooи тот же каталог, но этого недостаточно, чтобы гарантировать findодинаковое поведение на обоих. У findкоманды есть способы различать пути к одному и тому же файлу; -nameпредикат один из них. find /tmp/foo -name fooсоответствует начальному каталогу, а также любому файлу под ним, который называется foo. find . -name .соответствует только начальному каталогу ( .никогда не может быть найден во время рекурсивного обхода).

Жиль "ТАК - прекрати быть злым"
источник
msgstr "он не содержит никаких записей, кроме. и .., которые находят не рекурсивно." Я предполагаю, что "не поперечно рекурсивно" относится к "..". Если это правильно, то что будет означать «рекурсивный поперечный» в контексте «..»?
Фахим Митха
@FaheemMitha «не проходит рекурсивно» относится как к, так .и к ... (При рекурсивном findобходе .он в конечном итоге будет проходить через каталог снова и снова, снова и снова и…) .и ..будет не просто специальным обозначением, а появлением в виде записей каталога.
Жиль "ТАК - перестань быть злым"
5

Перед применением тестов нормализация аргументов командной строки не производится. Таким образом, результаты различаются в зависимости от используемого пути (если задействованы символические ссылки):

cd /tmp
mkdir foo
ln -s foo bar
find /tmp/foo -name foo
find /tmp/bar -name foo

В «вашем случае» оба вызова дали бы один и тот же результат, который может быть (более) запутанным. Вы можете использовать, -mindepth 1если хотите, чтобы начальные точки игнорировались (может быть не POSIX).

Хауке Лагинг
источник
-mindepth 1работает. Однако я до сих пор не понимаю, почему find . -name 'foo'и веду find /tmp/foo -name 'foo'себя по-другому.
Йорк,
2

(gnu) find показывает любые совпадения, найденные в пути, указанном в команде, потому что он начинает свое сравнение с аргументами командной строки, оттуда спускаясь глубже в структуру каталогов (таким образом, -maxdepth 0тесты ограничиваются базовым уровнем или только аргументами командной строки, тогда как -mindepth 1пропускает аргументы командной строки, как man findобъясняет). По этой причине find /tmp/foo -name 'foo'будет получено одно совпадение, даже если сам каталог пуст.

find . -name 'foo'с другой стороны, не даст никакого результата, потому что .(точка) - это специальный файл, который действует как жесткая ссылка на тот же индекс, что и /tmp/foo- он похож на отдельное (хотя и специальное) имя файла, а не на символическую ссылку или выражение, которое подвергается расширение пути с помощью оболочки. Следовательно, первый тест, примененный командой find к аргументам командной строки в данном примере, не будет показывать никаких совпадений, поскольку в .действительности не соответствует шаблону имени, определенному в -name 'foo'. Это не /tmp/foo/.так, поскольку проверка -nameшаблона выполняется только для базового имени пути (см. man find), Которое также здесь ..

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

Shevek
источник
0

Я пытался комментировать ответ Жиля, но это было слишком долго, чтобы уместиться в комментарии. По этой причине я ставлю это как ответ на свой вопрос.

Объяснения Жиля (и Шевека тоже) ясны и имеют смысл. Ключевым моментом здесь является то, что он не только findпытается сопоставить имена файлов для файлов внутри заданных путей (рекурсивно), но также пытается сопоставить базовое имя самих заданных путей.

С другой стороны, есть ли доказательства того, что именно так findи должно работать, а не быть ошибкой? На мой взгляд, было бы лучше, если бы это не делало результаты непоследовательными, find .и find ABSOLUTE-PATHпотому что непоследовательность всегда сбивает с толку и может потратить много времени разработчика, пытаясь выяснить, «что было не так». В моем случае я писал скрипт, а путь был взят из переменной. Поэтому, чтобы мой сценарий работал должным образом, мне нужно написать find $path/. -name 'pattern'.

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

Йорк
источник
-1

В fooкаталоге, в котором вы начали относительный поиск, нет названного объекта .

Вы правы, предполагая, что gfindэто ошибка, когда он сообщает об /tmp/fooиспользовании абсолютного имени в качестве начального каталога.

Gfindимеет различные отклонения от стандарта, кажется, вы нашли другое. Если вам нравится более стандартное совместимое решение, я рекомендую sfindего использовать schilytools.

-nameотносится к результатам поиска в каталоге. Ни один из двух упомянутых вами случаев не вернет запись каталога fooиз readdir()операции.

Шили
источник
Я подозреваю, что это тоже ошибка. Вот вывод из find --version: find (GNU findutils) 4.4.2 Copyright (C) 2007 Free Software Foundation, Inc. Лицензия GPLv3 +: GNU GPL версии 3 или более поздней < gnu.org/licenses/gpl.html >
Йорк,
Ну, я проверил ваши примеры команд с find(сертифицирован POSIX) gfindи sfind; проблема существует только при использовании gfind. В Linux gfindне устанавливается под собственным именем, а под именем find.
Щили
3
Это правильно для find /tmp/foo -name fooвывода /tmp/foo. (Cc @York) Это поведение OpenBSD find, GNU find, BusyBox find, Solaris findи любого другого POSIX-совместимого find(«Первичный должен оцениваться как true, если базовое имя проверяемого имени файла соответствует шаблону (…)» - когда имя файла это тот, который передается как операнд пути, readdirвызов не задействован).
Жиль "ТАК - перестань быть злым"