xargs: команда, которая делает всё остальное компонуемым
Это перевод оригинальной статьи xargs: The command that makes everything else composable.
Подписывайтесь на телеграм-канал usr_bin, где я публикую много полезного по Linux, в том числе ссылки на статьи в этом блоге.
Вы видели её в ответах на Stack Overflow. Вы копировали и вставляли её один или два раза. Вы примерно знаете, что она как-то связана с конвейерами.
Но если бы вас попросили написать команду xargs с нуля, вы, скорее всего, задумались бы.
В этом и заключается пробел в понимании xargs. Большинство разработчиков знают, что она существует. Почти никто не использует её уверенно. Эта статья исправит это.
Проблема, которую она решает
Конвейер Unix | — мощный инструмент. Он берет вывод одной команды и передает его в качестве входных данных следующей команде.
cat files.txt | grep "error"
Это работает, потому что grep предназначена для чтения из stdin. Но многие команды устроены иначе. Они ожидают аргументы, а не stdin.
echo "file.txt" | rm
Это не сработает. rm не читает из stdin. Она ожидает аргументы: rm file.txt.
Именно этот пробел и заполняет xargs. Она читает данные из stdin и преобразует этот ввод в аргументы для другой команды
echo "file.txt" | xargs rm
Теперь это работает. xargs получил file.txt из stdin и передала его в rm как аргумент.
В этом и заключается вся модель понимания. xargs преобразует stdin в аргументы.
Базовый синтаксис
command1 | xargs command2
command1 формирует вывод. xargs берет этот вывод и передает его в качестве аргументов для command2.
По умолчанию xargs разбивает ввод по пробельным символам и символам новой строки, а затем за один вызов передает все элементы как аргументы.
echo "a b c" | xargs echo # Output: a b c
Всё просто. Теперь давайте сделаем это полезным.
Реальное использование, шаг за шагом
Удаление списка файлов
У вас есть файл, содержащий пути к файлам, которые нужно удалить:
cat to_delete.txt | xargs rm
find . -name "*.log" | xargs rm "*.log" | xargs rm
Это наиболее распространенный шаблон использования xargs. find формирует список путей, а xargs передает их все в rm.
Без xargs вам пришлось бы использовать цикл:
# Without xargs — more verbose for f in $(find . -name "*.log"); do rm "$f" done
xargs позволяет записать это в одну строку.
Флаг -I: размещение аргумента именно там, где нужно
По умолчанию xargs добавляет аргументы в конец команды. Но иногда аргумент нужно разместить в определенной позиции.
cat branches.txt | xargs -I {} git checkout {}-I {} определяет заполнитель. Везде, где вы помещаете {} в команде, xargs подставляет текущий элемент. В качестве заполнителя можно использовать любую строку. {} — это просто соглашение.
Реальный пример: перемещение файлов в каталог резервных копий.
find . -name "*.bak" | xargs -I {} mv {} ./backup/{}"*.bak" | xargs -I {} mv {} ./backup/{}Это перемещает каждый файл .bak в каталог ./backup/, сохраняя имя файла. Без -I выразить это было бы неудобно.
-n: обрабатывать по одному элементу за раз
По умолчанию xargs объединяет все аргументы в один вызов команды. Иногда требуется один вызов на каждый элемент.
cat servers.txt | xargs -n 1 ping -c 1
-n 1 указывает xargs передавать один аргумент на каждый вызов команды. Поэтому, если в servers.txt содержится пять серверов, команда ping -c 1 будет выполнена пять раз — по одному разу для каждого сервера.
-n 2 будет передавать по два аргумента за один вызов и так далее.
-P: Выполнять параллельно
Именно здесь xargs становится по-настоящему мощной.
cat urls.txt | xargs -n 1 -P 4 curl -O
-P 4 запускает до 4 процессов параллельно. В сочетании с -n 1 (один URL на вызов) это позволяет загружать все URL по четыре одновременно, а не последовательно.
Тот же шаблон подходит для любой медленной задачи, которую можно распараллелить: обработка файлов, запуск тестов, обращения к API:
find . -name "*.csv" | xargs -n 1 -P 8 python process.py
Восемь файлов обрабатываются одновременно. Без очередей задач, без дополнительных инструментов. Только xargs.
Шаблон -print0 и -0
В большинстве примеров с xargs есть одна неочевидная проблема. Имена файлов могут содержать пробелы. А поскольку по умолчанию xargs разбивает ввод по пробельным символам, файл с именем my report.csv будет воспринят как два аргумента: my и report.csv.
find . -name "*.csv" -print0 | xargs -0 rm"*.csv" -print0 | xargs -0 rm
-print0 указывает find разделять результаты нулевым символом вместо символа новой строки. -0 указывает xargs разбивать ввод по нулевым символам вместо пробельных. Теперь имена файлов с пробелами обрабатываются корректно.
Возьмите это за привычку всякий раз, когда объединяете find и xargs. Это защитный подход, который ничего не стоит.
# Fragile -> breaks on filenames with spaces find . -name "*.log" | xargs rm # Robust -> handles all filenames correctly find . -name "*.log" -print0 | xargs -0 rm
args как связующее звено между командами
Как только вы поймете xargs, вы начнете замечать её повсюду — не как команду, которую нужно искать в справочнике, а как связующее звено, которое делает остальные команды комбинируемыми.
Несколько шаблонов, которые стоит запомнить:
Поиск с помощью grep среди отфильтрованного набора файлов:
find . -name "*.java" -print0 | xargs -0 grep -l "NullPointerException""*.java" -print0 | xargs -0 grep -l "NullPointerException"
Найти все Java-файлы, содержащие NullPointerException. -l указывает grep выводить только имена файлов, а не совпавшие строки.
echo -e "nginx\npostgres\nredis" | xargs -n 1 systemctl restart-e "nginx\npostgres\nredis" | xargs -n 1 systemctl restart
Массовое переименование файлов:
find . -name "*.jpeg" -print0 | xargs -0 -I {} bash -c 'mv "$1" "${1%.jpeg}.jpg"' _ {}"*.jpeg" -print0 | xargs -0 -I {} bash -c 'mv "$1" "${1%.jpeg}.jpg"' _ {}Немного сложнее → используется -I {} и вложенная оболочка для выполнения преобразования строки, но при этом массовое переименование выражается одной строкой.
Подсчет количества строк в нескольких файлах:
find . -name "*.log" -print0 | xargs -0 wc -l"*.log" -print0 | xargs -0 wc -l
xargs или цикл for
Частый вопрос: когда следует использовать xargs, а когда цикл for?
Я бы использовал xargs, когда:
- у вас есть список элементов из
stdin, и вы хотите передать их команде; - вам нужно параллельное выполнение с помощью
-P; - вам нужны лаконичные, легко комбинируемые однострочники.
И использовал бы цикл for, когда:
- для каждого элемента нужна условная логика;
- важна обработка ошибок для каждого элемента;
- операция достаточно сложная, и читаемость важнее краткости.
xargs оптимизирована для производительности и комбинируемости. Циклы for оптимизированы для управления.
xargs делает только одну вещь: она преобразует stdin в аргументы.
Всё остальное — -I для размещения, -n для пакетной обработки, -P для параллелизма, -0 для безопасности — лишь управляет тем, как происходит это преобразование.
Как только это станет понятно, вы перестанете копировать xargs из Stack Overflow и начнете использовать её естественным образом. Она станет тем самым связующим звеном, которого вам не хватало между командами, формирующими вывод, и командами, ожидающими аргументы.
Именно тогда терминал начинает ощущаться по-настоящему управляемым.
На этом все! Спасибо за внимание! Если статья была интересна, подпишитесь на телеграм-канал usr_bin, где будет еще больше полезной информации.