2017-01-16 17 views
4

У меня есть каталог из 5000 файлов, некоторые из которых ошибочно написаны с синтаксической ошибкой. Я использую следующий код, чтобы определить, какие файлы имеют ошибку:Могут ли логические операторы использоваться с find и xargs?

ls -1 | while read a; do grep -q '^- ' $a || echo $a; done 

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

Мой прецедент не связан с I/O и завершен достаточно быстро. Но мне было любопытно посмотреть, будет ли эта же операция работать, не полагаясь на петлю bash. Несмотря на удобство с Bash, у меня есть тенденция сильно полагаться на трубопроводы в петли, которые часто приводят к mind numbingly slow performance.

+1

Что вы хотите достичь в конце? Отображать имена файлов, содержащие строку? – Inian

+0

Показать файлы, которые НЕ соответствуют этому регулярному выражению. Это логический логический ИЛИ, который является точкой привязки. – Zhro

ответ

3

Вы можете использовать булеву логику с find:

find -maxdepth 1 -type f \(-exec grep -q '^- ' {} \; -o -print \) 

Опция -o является логическим ИЛИ. Если команда, выполненная -exec, вернет ненулевое возвращаемое значение, -print напечатает имя файла.

+0

Спасибо; это работает отлично. Я немного поиграл с ним и обнаружил, что если я добавлю «-mindepth 1», то он будет избегать вывода текущей папки ('.') с остальными результатами. – Zhro

+1

Да, однако '-type f' должен в любом случае исключить текущую папку' (.) '. – hek2mgl

+0

Не могли бы вы объяснить, почему скобки необходимы, чтобы избежать ('.')? – Zhro

2

Вот еще один способ сделать это, используя grep -L:

find -maxdepth 1 -type f -exec grep -L '^- ' {} \; 

Код выше можно было бы перечислить все файлы на директории, которые не содержат строку, начинающуюся с тире + пробел - в их содержание.

Чтобы сделать код выше рекурсивным (то есть, чтобы расширить поиск во всех подкаталогах), просто удалите часть -maxdepth 1.

С man grep о опции -L:

-L, --files-without-match Suppress normal output; instead print the name of each input file from which no output would normally have been printed. The scanning will stop on the first match.

2

Использование только grep достаточно:

grep -d skip -L '^- ' * 

Примечание: В отличие от find, это не будет автоматически включать скрытые файлы.
Чтобы найти рекурсивно, вместо этого используйте grep -L '^- ' -R . (хотя -R не совместим с POSIX, он работает как с GNU, так и с BSD/macOS grep).

-L, как описано в Jamil Said's helpful answer, печатает путь (как указано) каждого входного файла, который делает не содержать слово для поиска.

-d skip пропуски каталогов (в то время как опция -d не совместима с POSIX, поддерживается как GNU, так и BSD/macOS grep).


Оговорка: Как hek2mgl точка в комментариях, командная строка, что приводит после расширения имени файла *может быть слишком длинной, в результате ошибки, такие как /usr/bin/grep: Argument list too long.
(В противоположность этому, если вы сделаете grep поиск рекурсивно -R ., вы не будете сталкиваться с этой проблемой.)

макс. длина конкретной платформы, и может быть запрошена с getconf ARG_MAX, хотя отмечают, что фактический предел ниже чем, в зависимости от размера среды - см this article.

На практике 5000 файлов, вероятно, не будут проблемой, даже на платформах с относительно низким макс. длина, такая как macOS - если у вас нет исключительно длинных имен файлов и/или ваш шаблон подстановки имеет длинный компонент пути [1] .
Последние версии Linux имеют намного более высокий предел.

Если вы превысите предел и должны работать вокруг него, использования xargs следующим образом:

printf '%s\0' * | xargs -0 grep -d skip -L '^- ' 

Обратите внимание, что в то время как -0 читать NUL заканчивающейся вход не POSIX-совместимый, это поддерживаемых как GNU, так и BSD/macOS xargs.

Если входные имена файлов действительно не помещаются в командной строке одного, xargs будет разметить вход таким образом, что приводит к наименьшим количеством grep вызовов, необходимых для обработки всех из них.


[1] Macos 10.12 имеет предел 262,144 байт (256 КБ); если мы консервативно предположим, что после вычитания размера среды и фиксированной части командной строки мы получаем 250,000 байтов для нашего списка имен файлов, это дает нам 250000/5000 == 50 байт на каждое имя файла + пробел (разделитель списка), так что каждое имя файла разрешается до 49 байтов.
Напротив, предел Ubuntu 16.04 в 8 раз больше: 2,097,152 байт (2 МБ).

+1

Отличный ответ, очень круто, делая это с 'grep' в одиночку! –

+0

Проблема в glob. Как говорит OP, в этой папке есть 5000 файлов. Это даст вам слишком длинную ошибку в списке аргументов. Вам нужно использовать '-r' (или' -R') – hek2mgl

+0

@ mklement0 Очень хороший ответ. +2 (что, к сожалению, не разрешено). Я удивлен, что 5000 не проблема. Я знал, что предел настраивается, но я недооценил его (значение по умолчанию). – hek2mgl