Xargs: многообразие приёмов использования. Теперь зададим разделитель аргументов. Знайте, когда использовать grep для подсчета - а когда от него лучше отказаться


Автор: Joshua Reed
Дата публикации: 25 сентября 2014 года
Перевод: А. Кривошей
Дата перевода: апрель 2015 г.

Попадали ли вы когда-нибудь в ситуацию, когда вам нужно запускать одну и ту же команду много раз подряд? Если да, то вы понимаете, насколько это скучно и неэффективно. А хорошая новость в том, что в операционных системах на базе Unix есть замечательная команда xargs, которая позволяет решить эту проблему.С помощью этой команды вы можете очень эффективно работать с большим количеством файлом, экономя свое время и нервы. В этой статье вы увидите, как это делается.
У команды xargs есть два компонента. Во-первых, вы должны указать файлы, которые вас интересуют. Во-вторых, вы должны задать команду или скрипт, который вы хотите применить к каждому из этих файлов.
В этом руководстве мы рассмотрим три сценария использования команды xargs для обработки файлов, расположенных в нескольких разных директориях:

1. Подсчет количества строк во всех файлах.
2. Вывод первой строки заданных файлов.
3. Обработка каждого файла с помощью заданного скрипта.

Рассмотрим директорию с именем xargstest (дерево директорий можно вывести с помощью команды tree с опциями -i и -f, которые позволяют вывести результаты без отступов и с полным префиксом пути для каждого файла):

$ tree -if xargstest/

Директория xargstest, ее поддиректории и файлы будут далее использоваться в наших примерах.

Сценарий 1: подсчет количества строк во всех файлах

Как говорилось ранее, первым компонентом команды xargs является список файлов, с которыми работает команда. Для того, чтобы индентифицировать эти файлы и вывести их спиоск, можно воспользоваться командой find. Опция -name "file??" показывает, что нас интересуют только файлы, чье название начинается с "file", после чего в названии есть еще два любых любых символа. Поиск производится в директории xargstest, причем поиск осуществляется рекурсивно, то есть и во всех поддиректориях xargstest (если они имеются).

$ find xargstest/ -name "file??" xargstest/dir3/file3B xargstest/dir3/file3A xargstest/dir1/file1A xargstest/dir1/file1B xargstest/dir2/file2B xargstest/dir2/file2A

Мы можем перенаправить результаты поиска в команду sort, чтобы упорядочить их по имени:

$ find xargstest/ -name "file??" | sort xargstest/dir1/file1A xargstest/dir1/file1B xargstest/dir2/file2A xargstest/dir2/file2B xargstest/dir3/file3A xargstest/dir3/file3B

Теперь нам нужен второй компонент, представляющий собой команду, которую мы хотим выполнить. Для подсчета количества строк в каждом файле мы будем использовать команду wc с опцией -l (количество строк отображается в начале каждой строки вывода):

$ find xargstest/ -name "file??" | sort | xargs wc -l 1 xargstest/dir1/file1A 2 xargstest/dir1/file1B 3 xargstest/dir2/file2A 4 xargstest/dir2/file2B 5 xargstest/dir3/file3A 6 xargstest/dir3/file3B 21 total

Вы видите, что вместо того, чтобы вручную запускать команду wc -l для каждого из этих файлов, xargs позволяет выполнить эту операцию одной командой. Вы можете легко осуществлять различные операции с сотнями файлов.

Сценарий 2: вывод первой строки каждого из заданных файлов

Теперь, когда вы в общем понимаете, как использовать команду xargs, вы имеете полную свободу в выборе команды для выполнения. Иногда вам может понадобиться обработать только некоторую выборку файлов, но не все, расположенные в данной директории. В таком случае необходимо воспользоваться командой find с опцией -name для того, чтобы выбрать требуемые файлы и перенаправить их в команду xargs. Например, если вам нужно вывести первую строку всех файлов, имена которых заканчиваются на "B", используется следующая комбинация команд find, xargs и head (head -n1 выводит первую строку файла):

$ find xargstest/ -name "file?B" | sort | xargs head -n1 ==> xargstest/dir1/file1B <== one ==> xargstest/dir2/file2B <== one ==> xargstest/dir3/file3B <== one

Вы видите, что обрабатываются только файлы, чье имя заканчивается на "B", остальные игнорируются.

Сценарий 3: Обработка каждого файла с помощью скрипта

Наконец, у вас может быть свой скрипт (написанный на Bash, Python, или, например, на Perl) для обработки файлов. Просто подставьте его имя вместо команд wc и head в предыдущем примере.

$ find xargstest/ -name "file??" | xargs myscript.sh

Необходимо, чтобы скрипт myscript.sh был написан таким образом, чтобы он принимал имя файла в качестве аргумента и обрабатывал этот файл. Приведенная выше команда будет применять этот скрипт к каждому файлу, найденному командой find.

Обратите внимание, что в вышеприведенных примерах имена файлов не содержат пробелов. В целом можно сказать, что жизнь в Linux значительно приятнее в том случае, если вы не используете пробелы в именах файлов. Если же вам необходимо обработать файлы, чьи названия включают пробелы, приведенная выше команда не будет работать. Ее необходимо немного модифицировать, добавив опцию "-print0" к команде find (которая выводит полное имя с символом "null" после него в stdout) и опцию "-0" к команде xargs (которая интерпретирует символ "null" в конце строки), как показано ниже:

$ find xargstest/ -name "file*" -print0 | xargs -0 myscript.sh

Обратите внимание, что аргумент для опции -name изменился на "file*", что подразумевает любые файлы с именами, начинающимися с "file" и заканчивающимися любыми символами.

Заключение

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

On Сб, 19/09/2015 - 16:07

Задача

Есть консольная команда вида:

./do-something.sh -x 1

Значение аргумента x может меняться в диапазоне от 1 до 30 000. Выполнение команды для одного аргумента занимает от 30 секунд до 15 минут. Нужно максимально быстро выполнить эту команду для заданного диапазона аргументов на N-ядерном сервере максимально используя ресурсы сервера.

Возможные варианты решения

  1. Простой цикл от 1 до 30 тысяч с запуском команды на каждой итерации будет использовать только 1 ядро. Это решение неприемлемо: оно будет работать слишком долго и не задействует все доступные ресурсы сервера.
  2. Можно вручную разбить диапазон на N частей и запустить N циклов вида:

For i in `seq 1 1000 ` do ./do-something.sh -x $i done

Второе решение лучше первого - оно задействует все доступные ядра процессора, но оно все равно неприемлемо. Команды выполняются с непостоянной скоростью. В каком-то из диапазонов могут попасться только легкие команды, которые выполнятся, предположим, за несколько минут, а в каком-то - тяжелые и их выполнение затянется на несколько часов. Таким образом, часть ядер быстро освободится, будет простаивать и ресурсы сервера опять будут использованы неоптимально.

Решение с xargs

Утилита xargs , входящая во все современные дистрибутивы Linux, позволяет выполнить заданную команду для списка аргументов поступивших на стандартный ввод. Полезные ссылки:

В следующем примере берется список файлов текущей директории ls (в примере использован корень проекта на фреймворке Yii2, ничего секретного) и для каждого файла в директории применяется команда file , определяющая тип файла:

Ls | xargs file assets: directory commands: directory composer.json: ASCII text composer.lock: UTF-8 Unicode text config: directory controllers: directory mail : directory migrations: directory models: directory modules: directory requirements.php: PHP script, ASCII text runtime: directory tests: directory vendor: directory views: directory web: directory yii: a /usr/bin/env php script, ASCII text executable yii.bat: DOS batch file , ASCII text

Аргумент -P позволяет задать сколько параллельных потоков будет использовано для выполнения задачи.

Поэкспериментируем. Возьмем следующий скрипт и назовем его do-something.sh:

#!/usr/bin/env bash # Check for command line arguments if [ $# -lt 1 ] then echo "No options found!" exit 1 fi # Get number while getopts "x:" opt do case $opt in x) num=$OPTARG ;; *) echo "No reasonable options found!" ;; esac done rnd=$(shuf -i 1 -100 -n 1 ) rnd=$(echo "$rnd 100" | awk "{printf "%.2f \n ", $1/$2}" ) sleep $rnd echo $num

Этот скрипт берет на вход число и выводит его на экран с задержкой от 0 до 1 секунды. Теперь запустим этот скрипт командой time echo {1..10} | xargs -n 1 ./do-something.sh -x . Эта команда выполняет следующие задачи:

  • генерирует последовательность чисел от 1 до 10: echo { 1 ..10 } ,
  • передает эти числа по одному в наш скрипт (за это отвечает аргумент -n 1 , без него вся последовательность будет воспринята как один длинный аргумент, так как значения разделены пробелом, а не переводом строки),
  • в конце работы скрипта командой time выводит затраченное время.

В результате мы получим примерно такой вывод:

Time echo { 1 ..20 } | xargs -n 1 ./do-something.sh -x 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 real 0m10.301s user 0m0.042s sys 0m0.194s

Результат 1

А теперь запустим ту же команду с опцией -P 4 , что заставит скрипт выполняться в 4 потока:

Time echo { 1 ..20 } | xargs -n 1 -P 4 ./do-something.sh -x 4 2 6 1 3 8 10 5 12 9 7 11 13 15 14 19 16 17 20 18 real 0m2.651s user 0m0.032s sys 0m0.215s

Результат 2

В этом примере мы видим, что команды выполнились в 4 раза быстрее, так как запускались параллельно. То что на результат практически не влияет генератор случайных чисел легко убедиться заменив случайную паузу константой.

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

У xargs есть один недостаток. Давайте заменим в скрипте do-something.sh sleep $rnd на sleep 0.1 . Это сделает задержку не случайной, а постоянной. Теперь еще раз выполним time echo {1..20} | xargs -n 1 -P 4 ./do-something.sh -x:

Time echo { 1 ..20 } | xargs -n 1 -P 4 ./do-something.sh -x 1 3 2 4 5 6 7 8 9 10 12 11 13 14 15 16 17 18 19 20 real 0m0.560s user 0m0.034s sys 0m0.186s

Результат 3

Видно, что результаты выводятся не последовательно, это не всегда приемлемо.

Решение с GNU Parallel

Ниже перевод введения из мануала к утилите:

Утилита командной строки для параллельного запуска задач на одном или нескольких компьютерах. Задача в данном контексте - это одна команда или скрипт, который должен быть запущен для каждого входящего аргумента. Типичный набор аргументов - это список файлов, хостов, пользователей, урлов или таблиц. Аргументы также могут быть переданы через пайп. GNU parallel может разделить аргументы и параллельно передать их командам.

Если вы используете xargs , то вы легко сможете использовать parallel , так как эта утилита поддерживает те же аргументы командной строки что и xargs . Если вы используете циклы в шелл-скриптах, то, вероятно, parallel поможет вам избавиться от них и ускорить выполнение за счет параллельного запуска команд.

GNU parallel возаращает результаты выполнения команд в том же порядке как если бы они были запущены последовательно. Это делает возможным использование результатов работы parallel как входных данных для других программ.

Для каждой входящей строки GNU parallel запустит команду и передаст ей эту строку в качетсве аргументов. Если команда не задана, то входящая строка будет исполнена. Несколько строк будут выполнены одновременно. GNU parallel может быть использована как замена для xargs и cat | bash .

У этой утилиты как минмум 2 видимых преимущества перед xargs :

  • она позволяет запускать команды не в рамках одного сервера, а сразу на нескольких,
  • руководство обещает, что результаты будут выводиться последовательно.

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

Time echo { 1 ..20 } | parallel -n 1 -P 4 ./do-something.sh -x 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 real 0m0.562s user 0m0.135s sys 0m0.096s

Результат 4

Работает! Команда выполнилась примено за те же 0,5 секунд, что и xargs и результат возвращен в правильной последовательности.

Теперь попробуем вернуть обратно случайную задержку, зменим в скрипте do-something.sh sleep 0.1 на sleep $rnd и запустим еще раз. Результат будет возвращен опять в правильной последовательности, несмотря на то, что из-за разной задержки команды запущенные позже могут быть выполнены раньше предыдущих команд (это хорошо видно во втором результате выше).

Единственным недостатком является то, что xargs возвращает результаты как только они готовы, а parallel - только тогда когда выполнение всех команд завершено. Но это цена, которую приходится платить за корректную последовательность результатов. Если запустить parallel с аргументом --bar , то во время работы будет выводиться прогресс бар, показывающий процент выполненных команд.

Теперь испытаем еще одну киллер-фичу parallel - возможность запустить команду на нескольких серверах сразу. Для этого воспользуемся примером из доки: https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Parallel-grep .

# Добавим список серверов в конфиг. В моем случае сервера имеют имена dev и test (echo dev; echo test) > .parallel/my_cluster # Убедимся, что существует файл.ssh/config и забэкапим его touch .ssh/config cp .ssh/config .ssh/config.backup # Временно отключим StrictHostKeyChecking (echo "Host *" ; echo StrictHostKeyChecking no) >> .ssh/config parallel --slf my_cluster --nonall true # Откатываем назад изменения StrictHostKeyChecking в конфиге SSH mv .ssh/config.backup .ssh/config

Теперь сервера из файла.parallel/my_cluster добавлены в.ssh/known_hosts .

Наконец, нужно скопировать скрипт do-something.sh в домашнюю директорию текущего пользователя на удаленных серверах (в моем примере test и dev).

После выполненной подготовки мы можем запустить команду на серверах dev и test добавив к вызову parallel опцию --sshlogin dev,test .

Попробуем:

Time echo { 1 ..3200 } | parallel -n 1 -P 4 --sshlogin test,dev ./do-something.sh -x real 0m0.334s user 0m0.080s sys 0m0.032s

Результат 5

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

  • Ромка"s blog
  • Log in or register to post comments

В Linux существует очень странная команда xargs , которую весьма любят гуру, но не спешат объяснять как она работает. Интернет завален рецептами "как пользоваться xargs", но ни в одном из них внятно не написано самого главного: что эта команда вообще делает.

Самое главное

В общих чертах везде написано одно и то же: команда xargs принимает входной поток (именно поэтому ее всегда предваряет какая-нибудь команда и символ перенаправления потока "|"), и каким-то волшебным синтаксисом выполняет указанную в ней команду.

На самом деле команда xargs делает вот что. (Попробую сформулировать предельно беспристрастно). Она разбивает поток символов, направляемых в нее, на куски. Для разбиения потока она использует символы-разделители. И для каждого выделенного куска она выполняет команду, которая указана в правой части, дополняя справа эту команду символами найденного куска.

Да, в этом определении дважды используется понятие "право". Подробности разжовываются чуть ниже. А пока лучше посмотреть на структуру команды xargs в виде картинки. Синтаксически команда xargs состоит как бы из двух частей - левой и правой:

Причем однозначного визуального разделения, где левая, а где правая часть, просто нет. Если вы пытаетесь понять написанную другим человеком команду xargs, эту "границу раздела" нужно уметь находить самостоятельно. Вот несколько примеров:

Полная команда

Левая часть

Правая часть

Примечание

xargs rm -rf

xargs

rm -rf

xargs -0 rm -rf

xargs -0

rm -rf

xargs -p -l gzip

xargs -p -l

gzip

xargs tar -zcf pl.tar.gz

xargs

tar -zcf pl.tar.gz

xargs -n2 fmv

xargs -n2

xargs -I file mv

xargs -I file

Да, тут нет ошибки

xargs chown temp

xargs

chown temp

xargs kill -9

xargs

kill -9

xargs -p vim

xargs -p

То есть, здесь действует правило: если после xargs идут символы, предваряемые знаком минус "-" , значит это опции команды xargs . Как только пошли символы без знака минус, значит это уже символы правой части. Но нужно учитывать, что некоторые опции xargs требуют после себя еще каких-то данных, которые не будут предваряться знаком минус (см. пример с опцией -I ).

А теперь самое главное: какую же команду выполняет xargs ? Куда она пихает пачку символов, которую она вычленила во входном потоке? Все просто: она кладет эти символы справа от команды, прописанной в правой части. Понимаю, тут два раза используется понятие "право". Тогда вот картинка, которая все расставляет на свои места:

Возьмем конкретный пример. В каталоге лежат файлы:

main.cpp

main.h

version.cpp

version.h

config.cpp

config.h

data.cpp

data.h

Внутри этого каталога выполняется команда:

$ find . -name "*.cpp" | xargs -n 1 rm -rf

Какие команды сгенерирует xargs? Чтобы ответить на это, нужно понять, что будет подано на ее вход. А на вход будет подан результат работы команды find:

./main.cpp

./version.cpp

./config.cpp

./data.cpp

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

rm -rf ./main.cpp

rm -rf ./version.cpp

rm -rf ./config.cpp

rm -rf ./data.cpp

Очень важное замечание про волшебную опцию

Есть одно очень важное замечание. Если вы его не осознаете, то не сможете нормально работать с xargs, и уподобитесь авторам статей, которые думают, что понимают как работает xargs, а на самом деле пишут лютую чушь. В вышеприведенном примере не просто так прописана опция "-n 1" .

Опция "-n 1" заставляет xargs выполнять команду для каждого очередного куска из входного потока. Да, понимаю, что это звучит бредово: ведь команда xargs и так должна делать именно это! В конце концов, в мануале написано следующее: "xargs reads items from the standard input, delimited by blanks (which can be protected with double or single quotes or a backslash) or newlines, and executes the command (default is /bin/echo) one or more times with any initial-arguments followed by items read from standard input." Проблема в том, что по-умолчанию, если не указать "-n 1" , xargs воспринимает весь входящий поток, разбитый пробелами, табами, и переносами строк, как ОДИН аргумент. И по-сути, весь входящий поток просто подставляется в выполняемую команду. Вот так сюрприз от разработчиков!

Вопрос: А как же тогда срабатывают примеры, приводимые в статьях, типа

$ find . -name "*.cpp" | xargs rm -rf

$ find . -name "*.cpp" | xargs wc -l

А срабатывают они просто потому, что сами команды rm , wc и им подобные умеют работать с набором имен файлов. А пользователи ошибочно думают, что это xargs несколько раз вызывает данные команды для каждого имени файла. И чтобы в этом убедиться, можно воспользоваться опцией -t (печать команды, генерируемой xargs , перед ее выполнением). Но чтобы увидеть результат, нужно еще использовать конструкцию перенаправления вывода из потока ошибок 2>&1 (потому что использование опции -t даёт вывод в поток ошибок, а не в стандартную консоль). И вот что можно увидеть.

Если писать команду xargs без опции "-n 1" , то произойдет следующее:

$ find . -name "*.cpp" | xargs -t rm -rf 2>&1

rm -rf ./main.cpp ./version.cpp ./config.cpp ./data.cpp

Видно, что вызвалась только одна команда rm , и ей передан список имен файлов. Просто результат её работы будет выглядет так, как будто она вызывалась отдельно для каждого файла.

Если же воспользоваться опцией "-n 1" , то картина будет другая:

$ find . -name "*.cpp" | xargs -n 1 -t rm -rf 2>&1

rm -rf ./main.cpp

rm -rf ./version.cpp

rm -rf ./config.cpp

rm -rf ./data.cpp

Здесь поведение именно такое, как и обещалось. Помните об этой опции, и не удивляйтесь, что xargs работает как-то не так, если эту опцию не используете. А еще помните, что во многих статьях в Интернете команды с xargs просто неработоспособны. Авторы думают что знают, какой должен быть результат, и даже не проверяют "очевидное", вследствие чего неподготовленый пользователь, который решит повторить то, что написано в статье, ничего толком не поймет.

Есть еще один тонкий момент. В xargs есть ограничение на длину входного потока. И если входной поток слишком большой, xargs таки разобъет его на два или больше куска, и для каждого куска все-таки вызовет отдельную команду, указанную в правой части. Чтобы таких непредвиденных ситуаций не было, пользуйтесь опцией "-n 1" .

Команда xargs без аргументов

Иногда можно встретить обескураживающую конструкцию, типа:

tr -dc A-Za-z0-9_ < /dev/urandom | head -c 10 | xargs

Данная команда генерирует случайный пароль длиной 10 символов. Но что значит команда xargs без аргументов в конце этой команды?

Ответ прост. Команда xargs без аргументов на самом деле считает, что в ее правой части стоит команда /bin/echo . И пропускает входящий поток через команду echo . Зачем это нужно? В данном примере это нужно просто для того, чтобы итоговый результат завершался символом перевода строки. Вот пример, демонстрирующий разницу между командой, в которой нет xargs и есть xargs :

> tr -dc A-Za-z0-9_ < /dev/urandom | head -c 10

7jk2qx4cX8>

> tr -dc A-Za-z0-9_ < /dev/urandom | head -c 10 | xargs

zSlr2HsbSa

Пробелы в именах файлов

Так как xargs считает разделителями пробелы, табы и переводы строк, то возникает проблема с обработкой имен файлов, содержащих пробельные символы.

Обычно, имена файлов на вход программы xargs подаются из результата работы команды find . И для решения этой проблемы у команды find есть опция "-print0" . Она заменяет перенос строки на нуль-символ \x0 . А у команды xargs есть опция "-0" (минус ноль), с помощью которой входной поток разбивается на части, разделенные символом \x0 .

Предположим, в директории появился файл с именем "new file.cpp" . Если не пользоваться опциями преобразования перевода строк в нуль-символ, произойдет следующее:

$ find . -name "*.cpp" | xargs -n 1 -t rm -rf 2>&1

rm -rf ./new

rm -rf file.cpp

и, естественно, файл "new file.cpp" не будет удален. Если же добавить вышеприведенные опции, то команда сработает правильно:

$ find . -name "*.cpp" -print0 | xargs -n 1 -t -0 rm -rf 2>&1

rm -rf ./new file.cpp

и файл будет удален.

А что будет, если не писать опцию "-n" ?

Надо обратить внимание, что в вышеприведенных командах используется опция "-n 1" . А что будет, если ее не писать? В принципе, все сработает точно так же. Но вот как это работает, мало кто сможет объяснить, ибо визуально команды будут одни и те же, а результат разный. Вот пример.

Команда без опции "-n 1" и без опций преобразования нуль-символа:

$ find . -name "*.cpp" | xargs -t rm -rf 2>&1

"rm ..." , и она не удалит файл "new file.cpp" .

А теперь команда без опции "-n 1", но с опциями преобразования нуль-символа:

$ find . -name "*.cpp" -print0 | xargs -t -0 rm -rf 2>&1

rm -rf ./main.cpp ./data.cpp ./config.cpp ./version.cpp ./new file.cpp

В результате сконструирована команда "rm ..." , внешне абсолютно идентичная предыдущей, но она удалит файл "new file.cpp" !

Как это работает объяснить сложно. Ведь опцию "-0" имеет команда xargs , а не команда rm . В man-странице команды rm нет никаких указаний на то, что в случае разделения имен файлов нулевыми символами пробельные символы в именах файлов будут обрабатываться как литералы, а не как разделители. Для автора статьи такое поведение остается загадкой, и пока не нашлось специалиста, который бы объяснил, что же на самом деле происходит.

Самый главный вопрос

А как же конструировать команды, в которых нужно не просто добавить справа найденные последовательности символов? А если нужно и после подставленного справа значения еще что-то дописать? Как быть? А вот никак! Вот такой ответ. С помощью xargs невозможно сконструировать произвольную команду. Можно сконструировать только команду, состоящую из базовой (фиксированой) части и правой (подстановочной) части. И всё!

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

Так неужели в *NIX невозможно сконструировать нужную команду? Конечно, возможно. Для этого можно использовать команду awk и ее функцию system(). Как это делать, написано в статье: .

Об утилите xargs написано очень много — что можно написать еще? Но если, что называется, копнуть поглубже, то выясняется, что во многих публикациях излагаются лишь самые основы, но нет главного: не объясняется, как можно применять xargs в реальной практике. Статей с разбором сложных и нетривиальных вариантов применения этого весьма полезного для системного администратора инструмента, к сожалению, очень мало. Именно поэтому мы написали свою статью и постарались включить в нее как можно больше примеров использования xargs для решения различных проблем.

Сначала мы рассмотрим принцип работы xargs и разберем примеры попроще, а затем перейдем к разбору сложных и интересных кейсов.

Вспоминаем основы

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

В общем виде синтаксис команды xargs можно представить так:

[команда_генератор_списка] | xargs [опции_xargs] [команда]
Рассмотрим, как все это работает, на материале простых и хрестоматийных примеров.

Удаление файлов

Одна из самых частых ситуаций, в которых используется xargs — удаление файлов, найденных при помощи команды find.

Представим себе следующую ситуацию: имеется директория, в которой хранится большое количество файлов. Из нее нужно удалить файлы определенного типа (в нашем примере — файлы с расширением *.sh). Чтобы осуществить эту операцию, нужно передать xargs вывод команды find, и к файлам с указанным расширением будет применена команда -rm:

$ ls one.sh one.py two.sh two.py $ find . -name "*.sh"| xargs rm -rf $ ls one.py two.py

Отметим, что операцию удаления файлов можно осуществить и без xargs, а с помощью команды

$ find . -name "*.sh" -exec rm -rf "{}" \

Описанный способ не сработает, если в имени одного из удаляемых файлов содержится пробел. Имя, состоящее из двух слов, разделенных пробелом, не будет воспринято как единое целое.

Проиллюстрируем это следующим примером:

$ ls new file.sh one.sh one.py two.sh two.py $ find . -name "*.sh"| xargs rm -rf $ ls new file.sh one.py two.py

Как видим, файл, в имени которого имеется пробел, не был удалён.

Чтобы решить эту проблему, используется опция print0 для команды find и опция -0 для команды xargs. Она заменяет стандартный разделитель (перенос строки на нуль-символ (\x0), который и означает конец хранимой строки:

$ find . -name "*.sh" -print0 | xargs -0 rm -rf

Xargs может также помочь, например, быстро удалить все временные файлы, имеющие расширение tmp:

$ find /tmp -name "*.tmp"| xargs rm

Сжатие файлов

Сжать все файлы в текущей директории с помощью gzip можно, введя следующую команду:

$ ls | xargs -p -l gzip

Рассмотрим еще один пример: сжатие с помощью tar всех файлов с расширением *.pl:

$ find . -name "*.pl" | xargs tar -zcf pl.tar.gz

Переименование файлов

С помощью xargs можно осуществлять массовое переименование файлов. Представим себе, что у нас есть группа файлов с расширением *.txt, и нам нужно заменить это расширение на *.sql. Это можно сделать при помощи xargs и потокового текстового редактора sed:

$ ls | sed -e "p;s/.txt$/.sql/" | xargs -n2 fmv

В результате ее выполнения на консоль будет выведен список переименованных файлов.

С помощью xargs можно также добавлять к дополнительные элементы к именам файлов (например, дату):

$ ls | xargs -I FILE mv {} <...>-{}

Вместо <..> можно подставить всё, что угодно.
Фигурные скобки {} в этом примере означают «текущий аргумент» (т.е. текущее имя файла).

Изменение прав для папок и файлов

С помощью xargs можно также ускорить процесс смены прав на файлы и папки для определенного пользователя или группы. Предположим, нам нужно найти все папки пользователя root и заменить их владельца на temp. Эта операция осуществляется при помощи команды:

$ find . -group root -print | xargs chown temp

Чтобы найти все папки группы root и заменить группу на temp, используется команда:

$ find . -group root -print | xargs chgrp temp

Xargs и find: сложные операции

С помощью команд find и xargs можно выполнять и более сложные операции. Вот так, например, можно удалить временные файлы, созданные более 7 дней назад:

$ find /tmp -type f -name "*" -mtime +7 -print0 | xargs -0 rm -f

А вот так — принудительно остановить процессы, которые уже работают больше 7 дней:

$ find /proc -user myuser -maxdepth 1 -type d -mtime +7 -exec basename {} \; | xargs kill -9

Xargs и сut

Xargs довольно часто используется в сочетании с командой cut, позволяющей вырезать строки из текстовых файлов. Рассмотрим некоторые практические примеры. С помощью приведённой ниже команды на консоль будет выведен список всех пользователей системы:

$ cut -d: -f1 < /etc/passwd | sort | xargs echo

А команда вида

File * | grep ASCII | cut -d":" -f1 | xargs -p vim
будет последовательно открывать файлы для редактирования в vim.
Обратим внимание на опцию -p. Благодаря ей команда будет выполняться в интерактивном режиме: перед открытием каждого файла будет запрашиваться подтверждение (y/n).

В заключение приведём ещё один сложный и интересный пример — рекурсивный поиск файлов самого большого размера в некоторой директории:

$ find . -type f -printf "%20s %p\n" | sort -n | cut -b22- | tr "\n" "\000" | xargs -0 ls -laSr

Параллельный запуск процессов

Xargs часто используется для параллельного запуска нескольких процессов. Вот так, например, можно одновременно cжать несколько директорий в tar.gz:

$ echo dir1 dir2 dir3 | xargs -P 3 -I NAME tar czf NAME.tar.gz NAME

В приведенном примере используется ключ -P. Он указывает максимальное количество процессов, которые будут выполняться одновременно. Предположим, что у нас на входе имеется 10 аргументов. Если мы введём команду xargs с ключoм -P 3, то будет запущено 3 экземпляра команды, следующей после xargs, с каждым из этих аргументов.

С помощью xargs можно также параллельно загружать из Интернета множество файлов:

В приведенном примере с указанного адреса будут скачаны все графические файлы с расширением jpg; ключ -P указывает, что требуется скачивать по 10 файлов одновременно.

Предварительные итоги

Подведём предварительные итоги и сформулируем несколько правил работы с xargs.

  1. Xargs не работает с файлами, в имени которых присутствует пробел. Для решения этой проблемы с командой xargs используется опция −0. Пробел в имени файла можно обойти также следующим образом:
    $ xargs -I FILE my_command “FILE”
  2. Команда xargs принимает команды из со стандартного ввода, разделенные пробелом или переводом строки. Чтобы группировать эти команды, можно использовать двойные или одинарные кавычки. Можно также указать разделитель с помощью опции -d;
  3. Если команде xargs не передать вообще никаких аргументов, то по умолчанию будет выполнена команда /bin/echo;
  4. Во многих случаях команду xargs можно заменить циклом for. Например, команда
    $ find . -type f -and -iname "*.deb" | xargs -n 1 dpkg -I
    полностью эквивалента циклу
    $ for file in `find . -type f -and -iname "*.deb"`; do dpkg -I "$file"; done

Нетривиальные примеры

Основы мы вспомнили, типичные варианты использования рассмотрели… Перейдем теперь к более сложным и нетривиальным примерам. До некоторых из них мы додумались самостоятельно, работая над повседневными задачами, а некоторые — почерпнули с сайта http://www.commandlinefu.com (всем желающим научиться тонкостям работы с командной строкой очень рекомендуем время от времени его посещать — там порой можно найти очень полезные советы).

Баним IP-адреса из списка

Чтобы забанить IP-адреса из списка, нужно их добавить в IP tables c правилом DROP. Эта операция осуществляется при помощи команды:

$ cat bad_ip_list | xargs -I IP iptables -A INPUT -s IP -j DROP
Можно проделать и более сложную операцию и забанить все адреса по AS:

$ /usr/bin/whois -H -h whois.ripe.net -T route -i origin AS<номер>|egrep "^route"|awk "{print $2}" |xargs -I NET iptables -A INPUT -s NET -j DROP

Изменяем формат URL

Преобразовать URL вида «http%3A%2F%2Fwww.google.com» в «www ,google.com» можно при помощи команды:

Echo "http%3A%2F%2Fwww.google.com" | sed -e"s/%\(\)/\\\\\x\1/g" | xargs echo -e

Генерируем пароль из 10 символов

Сгенерировать надежный пароль можно при помощи команды вида:

$ tr -dc A-Za-z0-9_ < /dev/urandom | head -c 10 | xargs

Генерировать пароли можно и без помощи xargs: для этого cуществует специализированная утилита pwgen. Некоторые другие способы генерации паролей описаны также .

Ищем бинарные файлы, установленные без использования dpkg

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

$ сat /var/lib/dpkg/info/*.list > /tmp/listin ; ls /proc/*/exe |xargs -l readlink | grep -xvFf /tmp/listin; rm /tmp/listin

Удаляем устаревшие пакеты ядра

$ dpkg -l linux-* | awk "/^ii/{ print $2}" | grep -v -e `uname -r | cut -f1,2 -d"-"` | grep -e | xargs sudo apt-get -y purge

Проблема удаления старых ядер уже обсуждалась на Хабре — см. (по этой же ссылке можно найти любопытные примеры команд).

Преобразуем скрипт в строку

Иногда возникает необходимость преобразовать большой скрипт в одну строку. Сделать это можно так: Добавить метки

Об утилите xargs написано очень много — что можно написать еще? Но если, что называется, копнуть поглубже, то выясняется, что во многих публикациях излагаются лишь самые основы, но нет главного: не объясняется, как можно применять xargs в реальной практике. Статей с разбором сложных и нетривиальных вариантов применения этого весьма полезного для системного администратора инструмента, к сожалению, очень мало. Именно поэтому мы написали свою статью и постарались включить в нее как можно больше примеров использования xargs для решения различных проблем.

Сначала мы рассмотрим принцип работы xargs и разберем примеры попроще, а затем перейдем к разбору сложных и интересных кейсов.

Вспоминаем основы

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

В общем виде синтаксис команды xargs можно представить так:

[команда_генератор_списка] | xargs [опции_xargs] [команда]
Рассмотрим, как все это работает, на материале простых и хрестоматийных примеров.

Удаление файлов

Одна из самых частых ситуаций, в которых используется xargs — удаление файлов, найденных при помощи команды find.

Представим себе следующую ситуацию: имеется директория, в которой хранится большое количество файлов. Из нее нужно удалить файлы определенного типа (в нашем примере — файлы с расширением *.sh). Чтобы осуществить эту операцию, нужно передать xargs вывод команды find, и к файлам с указанным расширением будет применена команда -rm:

$ ls one.sh one.py two.sh two.py $ find . -name "*.sh"| xargs rm -rf $ ls one.py two.py

Отметим, что операцию удаления файлов можно осуществить и без xargs, а с помощью команды

$ find . -name "*.sh" -exec rm -rf "{}" \

Описанный способ не сработает, если в имени одного из удаляемых файлов содержится пробел. Имя, состоящее из двух слов, разделенных пробелом, не будет воспринято как единое целое.

Проиллюстрируем это следующим примером:

$ ls new file.sh one.sh one.py two.sh two.py $ find . -name "*.sh"| xargs rm -rf $ ls new file.sh one.py two.py

Как видим, файл, в имени которого имеется пробел, не был удалён.

Чтобы решить эту проблему, используется опция print0 для команды find и опция -0 для команды xargs. Она заменяет стандартный разделитель (перенос строки на нуль-символ (\x0), который и означает конец хранимой строки:

$ find . -name "*.sh" -print0 | xargs -0 rm -rf

Xargs может также помочь, например, быстро удалить все временные файлы, имеющие расширение tmp:

$ find /tmp -name "*.tmp"| xargs rm

Сжатие файлов

Сжать все файлы в текущей директории с помощью gzip можно, введя следующую команду:

$ ls | xargs -p -l gzip

Рассмотрим еще один пример: сжатие с помощью tar всех файлов с расширением *.pl:

$ find . -name "*.pl" | xargs tar -zcf pl.tar.gz

Переименование файлов

С помощью xargs можно осуществлять массовое переименование файлов. Представим себе, что у нас есть группа файлов с расширением *.txt, и нам нужно заменить это расширение на *.sql. Это можно сделать при помощи xargs и потокового текстового редактора sed:

$ ls | sed -e "p;s/.txt$/.sql/" | xargs -n2 fmv

В результате ее выполнения на консоль будет выведен список переименованных файлов.

С помощью xargs можно также добавлять к дополнительные элементы к именам файлов (например, дату):

$ ls | xargs -I FILE mv {} <...>-{}

Вместо <..> можно подставить всё, что угодно.
Фигурные скобки {} в этом примере означают «текущий аргумент» (т.е. текущее имя файла).

Изменение прав для папок и файлов

С помощью xargs можно также ускорить процесс смены прав на файлы и папки для определенного пользователя или группы. Предположим, нам нужно найти все папки пользователя root и заменить их владельца на temp. Эта операция осуществляется при помощи команды:

$ find . -group root -print | xargs chown temp

Чтобы найти все папки группы root и заменить группу на temp, используется команда:

$ find . -group root -print | xargs chgrp temp

Xargs и find: сложные операции

С помощью команд find и xargs можно выполнять и более сложные операции. Вот так, например, можно удалить временные файлы, созданные более 7 дней назад:

$ find /tmp -type f -name "*" -mtime +7 -print0 | xargs -0 rm -f

А вот так — принудительно остановить процессы, которые уже работают больше 7 дней:

$ find /proc -user myuser -maxdepth 1 -type d -mtime +7 -exec basename {} \; | xargs kill -9

Xargs и сut

Xargs довольно часто используется в сочетании с командой cut, позволяющей вырезать строки из текстовых файлов. Рассмотрим некоторые практические примеры. С помощью приведённой ниже команды на консоль будет выведен список всех пользователей системы:

$ cut -d: -f1 < /etc/passwd | sort | xargs echo

А команда вида

File * | grep ASCII | cut -d":" -f1 | xargs -p vim
будет последовательно открывать файлы для редактирования в vim.
Обратим внимание на опцию -p. Благодаря ей команда будет выполняться в интерактивном режиме: перед открытием каждого файла будет запрашиваться подтверждение (y/n).

В заключение приведём ещё один сложный и интересный пример — рекурсивный поиск файлов самого большого размера в некоторой директории:

$ find . -type f -printf "%20s %p\n" | sort -n | cut -b22- | tr "\n" "\000" | xargs -0 ls -laSr

Параллельный запуск процессов

Xargs часто используется для параллельного запуска нескольких процессов. Вот так, например, можно одновременно cжать несколько директорий в tar.gz:

$ echo dir1 dir2 dir3 | xargs -P 3 -I NAME tar czf NAME.tar.gz NAME

В приведенном примере используется ключ -P. Он указывает максимальное количество процессов, которые будут выполняться одновременно. Предположим, что у нас на входе имеется 10 аргументов. Если мы введём команду xargs с ключoм -P 3, то будет запущено 3 экземпляра команды, следующей после xargs, с каждым из этих аргументов.

С помощью xargs можно также параллельно загружать из Интернета множество файлов:

В приведенном примере с указанного адреса будут скачаны все графические файлы с расширением jpg; ключ -P указывает, что требуется скачивать по 10 файлов одновременно.

Предварительные итоги

Подведём предварительные итоги и сформулируем несколько правил работы с xargs.

  1. Xargs не работает с файлами, в имени которых присутствует пробел. Для решения этой проблемы с командой xargs используется опция −0. Пробел в имени файла можно обойти также следующим образом:
    $ xargs -I FILE my_command “FILE”
  2. Команда xargs принимает команды из со стандартного ввода, разделенные пробелом или переводом строки. Чтобы группировать эти команды, можно использовать двойные или одинарные кавычки. Можно также указать разделитель с помощью опции -d;
  3. Если команде xargs не передать вообще никаких аргументов, то по умолчанию будет выполнена команда /bin/echo;
  4. Во многих случаях команду xargs можно заменить циклом for. Например, команда
    $ find . -type f -and -iname "*.deb" | xargs -n 1 dpkg -I
    полностью эквивалента циклу
    $ for file in `find . -type f -and -iname "*.deb"`; do dpkg -I "$file"; done

Нетривиальные примеры

Основы мы вспомнили, типичные варианты использования рассмотрели… Перейдем теперь к более сложным и нетривиальным примерам. До некоторых из них мы додумались самостоятельно, работая над повседневными задачами, а некоторые — почерпнули с сайта http://www.commandlinefu.com (всем желающим научиться тонкостям работы с командной строкой очень рекомендуем время от времени его посещать — там порой можно найти очень полезные советы).

Баним IP-адреса из списка

Чтобы забанить IP-адреса из списка, нужно их добавить в IP tables c правилом DROP. Эта операция осуществляется при помощи команды:

$ cat bad_ip_list | xargs -I IP iptables -A INPUT -s IP -j DROP
Можно проделать и более сложную операцию и забанить все адреса по AS:

$ /usr/bin/whois -H -h whois.ripe.net -T route -i origin AS<номер>|egrep "^route"|awk "{print $2}" |xargs -I NET iptables -A INPUT -s NET -j DROP

Изменяем формат URL

Преобразовать URL вида «http%3A%2F%2Fwww.google.com» в «www ,google.com» можно при помощи команды:

Echo "http%3A%2F%2Fwww.google.com" | sed -e"s/%\(\)/\\\\\x\1/g" | xargs echo -e

Генерируем пароль из 10 символов

Сгенерировать надежный пароль можно при помощи команды вида:

$ tr -dc A-Za-z0-9_ < /dev/urandom | head -c 10 | xargs

Генерировать пароли можно и без помощи xargs: для этого cуществует специализированная утилита pwgen. Некоторые другие способы генерации паролей описаны также .

Ищем бинарные файлы, установленные без использования dpkg

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

$ сat /var/lib/dpkg/info/*.list > /tmp/listin ; ls /proc/*/exe |xargs -l readlink | grep -xvFf /tmp/listin; rm /tmp/listin

Удаляем устаревшие пакеты ядра

$ dpkg -l linux-* | awk "/^ii/{ print $2}" | grep -v -e `uname -r | cut -f1,2 -d"-"` | grep -e | xargs sudo apt-get -y purge

Проблема удаления старых ядер уже обсуждалась на Хабре — см. (по этой же ссылке можно найти любопытные примеры команд).

Преобразуем скрипт в строку

Иногда возникает необходимость преобразовать большой скрипт в одну строку. Сделать это можно так:

  • селектел
  • selectel
  • Добавить метки