Найти папки, содержащие только один конкретный файл (.DS_Store) и никаких других?

Есть ли какой-нибудь способ использоватьКоманда в терминале macOS, чтобы рекурсивно найти все папки по заданному пути, которые практически пусты или пусты, за исключением одного файла .DS_Store?

Я пробовал в терминале следующее, но безрезультатно:

      find TARGET_PATH -empty -type d

Судя по всему, эти сверхневидимые файлы .DS_Store по-прежнему считаются содержимым папки даже при отсутствии каких-либо других файлов. :/

Этот вопрос касается меньшей части более крупной задачи, которую я пытаюсь выполнить. Теперь у меня почти все работает, за исключением части сценария, которая, кажется, работает только тогда, когда я запускаю его напрямую из терминала. Вот ссылка на основную проблему на случай, если кто-нибудь, читающий этот вопрос, сможет помочь: Сервис для выполнения серии команд в выбранных папках для устранения проблем с файлами .DS_Store в Finder.

1 ответ

Решение

Следующая команда делает то, что, я думаю, вы хотите:

      braces={} find TARGET_PATH -type d \( -empty -o -exec sh -c '
   echo | find "$1" ! \( -type f -name .DS_Store \) -exec sh -c "
      for f do read dummy || { kill -s INT \"\$PPID\"; exit 1; } ; done
   " inner-sh "$braces" +
' outer-sh {} \; \) -print

Объяснение

Команда печатает каталоги, для которых значение true или пользовательское значение.-execтест оценивается как истинный. Пользовательский тест предназначен для того, чтобы игнорировать обычные файлы с именами и определять, считает ли он данный каталог пустым.

Пользовательский тест — это процесс с именем outer-sh. Из основного файла он получает ровно один путь к каталогу. Он запускает другой, используя каталог в качестве единственной отправной точки. Этот внутренний предназначен для игнорирования обычных файлов с именем . Он находит по крайней мере свою отправную точку, возможно, больше файлов . Он передает их другому пользовательскому «тесту», процессу с именемinner-sh. На этот раз это , поэтому, возможно, для . Я называю это «тестом» (в кавычках), потому что на самом деле это не тест в контексте ; его задача — не тестировать что-то для того, кто это запускает; его задача — убить это в некоторых обстоятельствах.

Внутренний пользовательский «тест» предназначен для определения наличия более одного позиционного аргумента. Он мог бы просто проверить его$#, но внутреннему процессу разрешено решить, что даже два пути генерируют слишком длинную командную строку, его можно вызывать с одним аргументом, оставляя остальные пути (если таковые имеются) для отдельных процессов. Здесь трюк с ударами.

Хитрость в том, что эта подошва соединена с внутренней частью. Sole печатает ровно одну пустую строку (т.е. один символ новой строки). Внутренний не читает из своего стандартного ввода, но наши внутренние «тесты» могут; и они делают. Для каждого пути выполняется. Первое имя пути — это начальная точка внутреннего файла, он будет использовать строку из, и все будет успешно. Второе имя пути может быть предоставлено или не предоставлено (в зависимости от того, является ли каталог непустым или пустым после игнорирования возможного); если он предусмотрен, то будет другой, и он потерпит неудачу (независимо от того, выполняется ли он тем же, что и первый, или нет; в этом вся хитрость).

В случае неудачи оболочка, выполняющая внутренний «тест», убьет своего родителя и завершит работу. Родитель является внутренним. Уничтожение его приводит к прекращению обработки файлов и созданию большего количества файлов, даже если в противном случае это должно было произойти.

Другими словами, если в каталоге есть файл, отличный от этого, произойдет сбой, и это приведет к уничтожению внутреннего файла, поэтому статус выхода из внутреннего файла, видимый внешним, будет ненулевым.

В случае, если внутренний заметит кончину внутреннего до того, как заметит сигнал, и если ему больше нечего делать, он может (?) выйти сам. Внутренний завершается с ошибкой () после , поэтому даже тогда внутренний завершается с ненулевым статусом выхода ( -exec utility … +всегда верно в качестве теста, но если утилита дает сбой, она возвращает ненулевой статус выхода). Я сомневаюсь, что этот сценарий выхода до того, как он будет убит (когда его собираются убить), возможен, я полагаю, он должен сначала отреагировать на сигнал. exit 1это на тот случай, если я ошибаюсь.

Таким образом, если в каталоге есть файл, отличный от , внешний увидит сбой внутреннего и сам завершит работу с ненулевым статусом выхода. Это будет интерпретировано внешним устройством как пользовательский тест, оцененный как ложный, поэтому не будет выполнено.

Если в каталоге нет другого файла, то будет ровно один внутренний и ровно одинread dummy. Здесь не будетkill. Несколько вещей будут успешными последовательно:read, внутренний , внутренний , внешний ; и тогда внешний будет считать, что пользовательский тест оценен как истинный, и будет выполнен.


Примечания

  • Я намеренно сохранил все внутри основного (в отличие, например, отfind … | awk …), так что теперь вы можете что-то добавить (-exec …,-delete, …) после, до или вместо-print.

  • Оbraces={}. Если бы я буквально использовал где внутреннийfind … -exec …ожидает этого, он может быть расширен внешним ( в зависимости от реализации) и полностью нарушить код. Сохранив переменную в среде и позволив правой оболочке расширить ее, я скрыл это{}из внешнего.

  • Пользовательский тест запускает несколько процессов для каждого тестируемого каталога. Не ждите, что вся команда будет работать так же хорошо, какfind TARGET_PATH -type d -empty.

  • Вы использовали , поэтому я знаю, что вы это поддерживаете. В общем, это не переносимый операнд. Наш специальный тест позволяет удалить из кода (а затем и самые внешние\( \)пара не нужна, хотя может остаться). Пользовательский тест сам по себе обнаруживает действительно пустые каталоги. Это означает, что вы можете использовать решение с реализациями, которые не поддерживают . Тем не менее, благодаря тому, как-oработает (-test1 -o -test2пропускает-test2если-test1возвращает true), если вы можете использовать, то используйте его, потому что для действительно пустых каталогов это ускоряет работу.

  • Во внутреннем я мог бы использовать вместо . Дилемма: лучше ли использовать и позволить заранее запросить файловую систему, чтобы построить возможно большой список для предоставления внутреннему файлу, когда достаточно не более двух путей? или лучше использовать и чаще спавнить два внутреннихshпроцессы, но без необходимости чтения из файловой системы вперед (и безforпетля)? Трюк сechoпозволяет нам использовать\;и вы сможете проверить, какой вариант вам больше подходит. В моих тестах на достаточно большом дереве каталогов+в целом было немного быстрее, но ваш пробег может отличаться.

    Если вам когда-нибудь понадобится использовать решение с каким-нибудь старымfindэто не поддерживает-exec … +, Перевести в-exec … \;свободно.

  • Чтобы определить, пуст ли каталог, необходимо разрешение на чтение этого каталога. Если чтение не разрешено и, следовательно, невозможно узнать, пуст ли каталог, тогда он будет считаться непустым и оцениваться как ложь. Насколько я тестировал, наш собственный тест ведет себя точно так же; поэтому независимо от того, используете ли вы его с или без , вы должны получить результаты, которые получили бы, если бы могли указать ему игнорировать файлы.

    Разница в том, что наш код печатает (в stderr) больше «копий»permission deniedдля каждого каталога без разрешения на чтение. Я не ожидаю, что это когда-нибудь станет проблемой.

  • Чтобы узнать, пуст ли каталог,-emptyне требуется разрешение на выполнение в каталоге. Я намеренно разработал решение безcdи без-execdir, поэтому ни одному нашему пользовательскому тесту не требуется разрешение на выполнение.

  • Изменять-empty -oк! -emptyи вы найдете каталоги с одинокими.DS_Storeфайлы, а не действительно пустые каталоги.

Другие вопросы по тегам