Что делает «xargs grep»?

25

Я знаю grepкоманду и изучаю ее функциональные возможности xargs, поэтому я прочитал эту страницу, где приведены некоторые примеры использования xargsкоманды.

Я запутался в последнем примере, примере 10. Он говорит: «Команда xargs выполняет команду grep, чтобы найти все файлы (среди файлов, предоставляемых командой find), которые содержали строку« stdlib.h »»

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

Тем не менее, в чем разница с простым использованием

$ find . -name '*.c' | grep 'stdlib.h'

?

Очевидно, я все еще борюсь с тем, что именно делает xargs, поэтому любая помощь приветствуется!

Альфа Омега
источник
2
Этот вопрос может быть полезным: когда нужны XArgs?
TheOdd

Ответы:

30
$ find . -name '*.c' | grep 'stdlib.h'

Это передает вывод (stdout) * из findto в (stdin of) * grep 'stdlib.h' как текст (т.е. имена файлов обрабатываются как текст). grepвыполняет свои обычные функции и находит соответствующие строки в этом тексте (любые имена файлов, которые сами содержат шаблон). Содержимое файлов никогда не читается.

$ find . -name '*.c' | xargs grep 'stdlib.h'

Это создает команду, grep 'stdlib.h' для которой каждый результат findявляется аргументом, поэтому она будет искать совпадения внутри каждого найденного файла find( xargsможет рассматриваться как превращение его стандартного аргумента в аргументы для данных команд) *

Используйте -type fв вашей команде поиска, иначе вы получите ошибки grepдля соответствующих каталогов. Кроме того, если в именах файлов есть пробелы, они xargsбудут плохо работать, поэтому используйте нулевой разделитель, добавив -print0и xargs -0для более надежных результатов:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* добавил эти дополнительные пояснительные замечания, как предложено в комментарии @cat

Занна
источник
2
Вы могли бы рассмотреть с учетом (потому что кажется , вы опустили) ключевой момент , что |трубы стандартный вывод в Grep в стандартный ввод , который не является такой же , как аргументы Grep и дает заблуждение результаты.
кот
1
Или используйте GNU find's find -name '*.c' -exec grep stdlib.h {} +. Я почти никогда не использую xargs. Также удивило, что никто не упомянул, что xargs выполняет те же функции, что и подстановка grep $(find)команд, поэтому я написал свой собственный ответ. Объяснение xargs как подстановки команд с меньшими ограничениями и проблемами кажется естественным.
Питер Кордес
Одна из ситуаций, в которой я использую xargs, - это удаление большого количества файлов в результате поиска. Если вы просто выполните -exec rm, он будет запускать rm для каждого файла по одному, что очень неэффективно. Трубопровод к XARGS сделает их все сразу с одним RM. Ограничение, скажем, -n50 (до 50 одновременно) может предотвратить переполнение командной строки (проблема с большим количеством файлов).
LSD
1
@lsd: Почему бы не find -deleteдля этого особого случая? Или для команд, отличных от rm, если у вас есть GNU find, затем -exec some_command {} +группируются в пакеты, такие как xargs, вместо этого \;поведение выполнения команды отдельно для каждой.
Питер Кордес
@lsd findзапускает команду для каждого файла тогда и только тогда, когда он использует -exec command \;Both, xargsи -exec command \+будет вызывать команду с максимальным количеством аргументов, разрешенных системой. Другими словами, они эквивалентны
Сергей Колодяжный
6

xargs берет свой стандартный ввод и превращает его в аргументы командной строки.

find . -name '*.c' | xargs grep 'stdlib.h' очень похоже на

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

И даст те же результаты, пока список имен файлов не слишком длинный для одной командной строки. (Linux поддерживает мегабайты текста в одной командной строке, поэтому обычно вам не нужны xargs.)


Но оба они отстой, потому что они ломаются, если ваши имена файлов содержат пробелы . Вместо этого find -print0 | xargs -0работает, но так же

find . -name '*.c' -exec grep 'stdlib.h' {} +

Это никогда нигде не передает имена файлов: findобъединяет их в большую командную строку и запускает grepнапрямую.

\;вместо +запуска grep отдельно для каждого файла, что намного медленнее. Не делай этого. Но +это расширение GNU, поэтому вам нужно xargsделать это эффективно, если вы не можете предполагать, что GNU find.


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

Так что в этот момент вы могли бы просто сделать find -name stdlib.h. Конечно, с -name '*.c' -name stdlib.h, вы не получите никакого вывода, потому что эти шаблоны не могут как совпадать, так и найти, что поведение по умолчанию - это И правила вместе.

Замените lessв любой точке процесса, чтобы увидеть, какую продукцию производит любая часть конвейера.


Дальнейшее чтение: http://mywiki.wooledge.org/BashFAQ имеет несколько замечательных вещей.

Питер Кордес
источник
1
GNU xargs также должен -dустановить разделитель, так что вы можете использовать его -d'\n'для обработки списка, разделенного новой строкой , что может быть полезно, если вы обрабатываете список имен файлов в файле и т. Д. (Если имена файлов не имеют в них
новые
@ilkkachu: да, переводы строк в именах файлов встречаются гораздо реже, чем пробелы, поскольку они нарушают большинство сценариев. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }также работает с тем же эффектом. Или, как однострочник, (IFS=...; cmd...)подоболочка также работает, чтобы содержать изменение IFS без необходимости сохранять / восстанавливать его.
Питер Кордес
@PeterCordes Пожалуйста, не делайте такие command $( find )вещи. Проблемные имена файлов с пробелами и специальными символами могут сломать этот тип вещей. По крайней мере, двойная кавычка команда подстановки.
Сергей Колодяжный
@SergiyKolodyazhnyy: Спасибо, что указали, что, похоже, я действительно рекомендую это сделать. Люди, занимающиеся скиммингом, могли бы скопировать / вставить это вместо чтения следующего раздела Обновлено для решения этой проблемы.
Питер Кордес
@SergiyKolodyazhnyy: Или вы отвечали на мой комментарий? Обратите внимание, что я установил, IFSтак что это эквивалентно использованию xargs '-d\n'. Раскрытие глобуса и обработка метасимволов оболочки происходят до эффектов подстановки команд, поэтому я думаю, что это безопасно даже с именами файлов, которые содержат $()или >. Согласился, что использование разделения слов при подстановке команд не является хорошей практикой, за исключением одноразового интерактивного использования, когда вы что-то знаете о именах файлов. Но command "$(find)"полезно, только если вы ожидаете, что оно
Питер Кордес
5

Обычно xargsиспользуется для случаев, когда вы передадите (с символом |) что-то из одной команды в другую ( Command1 | Command2), но выходные данные из первой команды не будут правильно получены в качестве входных данных для второй команды.

Обычно это происходит, когда вторая команда не обрабатывает ввод данных через стандартный ввод (stdin) правильно (например: несколько строк в качестве ввода, способ установки строк, символы, используемые в качестве ввода, несколько параметров в качестве ввода, тип данных, полученный как вход и т. д.). Чтобы дать вам быстрый пример, протестируйте следующее:

Пример 1:

ls | echo- Это ничего не сделает, так echoкак не знает, как обрабатывать ввод, который он получает. Теперь в этом случае, если мы используем xargsего, он будет обрабатывать ввод таким способом, который может быть корректно обработан echo(например: как одна строка информации)

ls | xargs echo- Это выведет всю информацию из lsодной строки

Пример 2:

Допустим, у меня есть несколько файлов goLang в папке с именем go. Я бы посмотрел на них примерно так:

find go -name *.go -type f | echo- Но если символ трубы там и echoв конце, он не будет работать.

find go -name *.go -type f | xargs echo- Здесь это будет работать благодаря, xargsно если бы я хотел, чтобы каждый ответ от findкоманды в одной строке, я сделал бы следующее:

find go -name *.go -type f | xargs -0 echo- В этом случае тот же вывод из findбудет показан как echo.

Команды, такие как cp, echo, rm, lessи другие, которым нужен лучший способ обработки ввода, получают преимущество при использовании с xargs.

Луис Альварадо
источник
4

xargs используется для автоматической генерации аргументов командной строки на основе (обычно) списка файлов.

Итак, рассмотрим некоторые альтернативы использованию следующей xargsкоманды:

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Есть несколько причин использовать его вместо других опций, которые изначально не упоминались в других ответах:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;будет генерировать по одному grepпроцессу для каждого файла - это обычно считается плохой практикой и может создать большую нагрузку на систему, если будет найдено много файлов.
  2. Если файлов много, grep 'stdlib.h' $(find . -name '*.c')команда, скорее всего, потерпит неудачу, поскольку результат $(...)операции превысит максимальную длину командной строки оболочки

Как упоминалось в других ответах, причина использования -print0аргумента to findв этом сценарии и -0аргумента xargs заключается в том, что имена файлов с определенными символами (например, кавычки, пробелы или даже символы новой строки) по-прежнему обрабатываются правильно.

Майкл Ферт
источник