Сайт о телевидении

Сайт о телевидении

» » Xargs: многообразие вариантов использования. Как сконструировать нужную команду с помощью xargs. Понимание xargs

Xargs: многообразие вариантов использования. Как сконструировать нужную команду с помощью xargs. Понимание xargs

В 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 - разбивает входной поток на аргументы и передает их выполнение любой команде (по умолчанию echo). Читает либо со стандартного ввода, либо через pipe.

Режимы обработки xargs входящих данных

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

    Режим, включающийся параметром -L. Практически идентичен предыдущему, за исключением того, что xargs запоминает, какой аргумент на какой строке находится. Более того, если строка оканчивается пробелом или табуляцией, следующая строка считается продолжением текущей.

    По строкам. Включается при использовании опции -I или -0. При этом вся строка считается одним целым аргументом, несмотря на пробелы и табуляции внутри. Для -I концом строки является символ "\n" а для -0 символ "\0"

    Пример: поиск в /home/user1 всех файлов, имена которых оканчиваются на ".txt", и копирование их в другую директорию. find /home/user1 -name "*.txt" | xargs cp -av --target-directory=/home/backup/ --parents

    Найти во всех файлах, расположенных в каталоге /etc/samba вхождение словосочетания logon home. $ find /etc/samba | xargs grep -ni "logon home"

    Удалить список файлов. Пути к файлу построчно записаны в файл delfiles.txt xargs rm < delfiles.txt

Откажитесь от неудачных приемов работы в UNIX

Часто используя какую-либо систему, мы привыкаем к некоторым шаблонам работы. Но не всегда эти шаблоны оказываются оптимальными. Иногда мы даже приобретаем плохие привычки, которые приводят к беспорядку и неуклюжести в работе. Один из наилучших путей исправления таких недостатков – выработка привычки использовать хорошие приемы, которые препятствуют беспорядку. Эта статья предлагает 10 методов работы с командной строкой UNIX, достойных стать привычками, которые помогут вам избежать много общих недостатков и повысить эффективность работы. Подробное описание каждого из методов приведено после списка.

Усвоим 10 хороших привычек

Десять хороших методов, к которым стоит привыкнуть:

Создавайте деревья каталогов одной командой

Листинг 1 иллюстрирует один из наиболее распространенных неудачных методов работы в UNIX: пошаговое определение дерева каталогов.

Листинг 1. Пример плохого метода работы #1: Пошаговое определение дерева каталогов
~ $ mkdir tmp ~ $ cd tmp ~/tmp $ mkdir a ~/tmp $ cd a ~/tmp/a $ mkdir b ~/tmp/a $ cd b ~/tmp/a/b/ $ mkdir c ~/tmp/a/b/ $ cd c ~/tmp/a/b/c $

Намного быстрее использовать опцию -p mkdir и создавать все родительские и дочерние каталоги одной командой. Но даже администраторы, которые знают об этой опции, до сих пор работают пошагово, создавая в командной строке каждый подкаталог в отдельности. Берегите свое время - привыкайте к хорошим методам:

Листинг 2. Пример хорошего метода работы #1: Определение дерева каталогов одной командой
~ $ mkdir -p tmp/a/b/c

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

Листинг 3. Еще один пример хорошего метода работы #1: Определение сложного дерева каталогов одной командой
~ $ mkdir -p project/{lib/ext,bin,src,doc/{html,info,pdf},demo/stat/a}

Раньше была только одна причина пошагово определять дерево каталогов - реализация mkdir не поддерживала эту опцию, но теперь это не актуально для большинства систем. IBM, AIX, mkdir, GNU mkdir и остальные системы, соответствующие единой спецификации UNIX, сейчас имеют эту опцию.

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

~ $ mkdirhier project/{lib/ext,bin,src,doc/{html,info,pdf},demo/stat/a}

Изменяйте пути; не переносите архив

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

Листинг 4: Пример хорошего метода работы #2: Использование опции -C для распаковки архивного файла.tar
~ $ tar xvf -C tmp/a/b/c newarc.tar.gz

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

Объединяйте ваши команды с операторами управления

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

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

Используйте оператор управления && для объединения двух команд таким образом, что вторая команда будет выполняться, только если первая возвратила нулевое значение. Другими словами, если первая команда выполняется успешно, вторая команда тоже запускается. Если первая команда не выполняется, то вторая команда не запускается совсем. Например:

Листинг 5. Пример хорошего метода работы #3: Объединяйте команды с операторами управления
~ $ cd tmp/a/b/c && tar xvf ~/archive.tar

В этом примере содержимое архива извлекается в каталог ~/tmp/a/b/c, если этот каталог существует. Если каталог не существует, команда tar не запустится и из архива ничего не будет извлечено.

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

Точно так же оператор управления || разделяет две команды и запускает вторую команду, только если первая команда возвратила ненулевое значение. Другими словами, если первая команда была успешной , вторая команда не запустится. Если первая команда завершилась с ошибкой, то вторая запустится . Этот оператор часто применяется для тестирования с условием: существует ли заданный каталог, если нет, то он его создает:

Листинг 6. Еще один пример хорошего метода работы #3: Объединяйте команды с операторами управления
~ $ cd tmp/a/b/c || mkdir -p tmp/a/b/c

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

Листинг 7. Комбинированный пример хорошего метода работы #3: Объединение команд с операторами управления
~ $ cd tmp/a/b/c || mkdir -p tmp/a/b/c &&

Будьте внимательны при использовании кавычек в работе с переменными

Всегда будьте осторожны с расширением оболочки и именами переменных. Вообще-то, это хорошая идея - брать обращения к переменным в двойные кавычки, только если нету особых причин не делать этого. Аналогично, если непосредственно за именем переменной следует буквенно-цифровой текст, имя переменной следует заключить в фигурные скобки ({}), чтобы отличать ее от окружающего текста. Иначе оболочка интерпретирует замыкающий текст как часть имени переменной - и наиболее вероятный результат - нулевой код возврата. В Листинге 8 приведены примеры вариантов использования кавычек с именами переменных и получаемые результаты.

Листинг 8. Пример хорошего метода работы #4: Использование (и не использование) кавычек в работе с переменными
~ $ ls tmp/ a b ~ $ VAR="tmp/*" ~ $ echo $VAR tmp/a tmp/b ~ $ echo "$VAR" tmp/* ~ $ echo $VARa ~ $ echo "$VARa" ~ $ echo "${VAR}a" tmp/*a ~ $ echo ${VAR}a tmp/a ~ $

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

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

Листинг 9. Пример хорошего метода работы #5: Использование обратного слэша в длинных строках
~ $ cd tmp/a/b/c || \ > mkdir -p tmp/a/b/c && \ > tar xvf -C tmp/a/b/c ~/archive.tar

В качестве альтернативы приведена еще одна конфигурация, которая также работает:

Листинг 10. Альтернативный пример хорошего метода работы #5: Использование обратного слэша в длинных строках
~ $ cd tmp/a/b/c \ > || \ > mkdir -p tmp/a/b/c \ > && \ > tar xvf -C tmp/a/b/c ~/archive.tar

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

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

Объединяйте команды в список

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

Запуск списка команд в дополнительной оболочке

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

Листинг 11. Пример хорошего метода работы #6: Запуск списка команд в дополнительной оболочке
~ $ (cd tmp/a/b/c/ || mkdir -p tmp/a/b/c && \ > VAR=$PWD; cd ~; tar xvf -C $VAR archive.tar) \ > | mailx admin -S "Archive contents"

В этом примере содержимое архива извлекается в каталог tmp/a/b/c, в то время как вывод сгруппированных команд, включая список извлеченных файлов, отправляется по почте на адрес admin .

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

Запуск списка команд в текущей оболочке

Используйте фигурные скобки ({}) для заключения списка команд для запуска в текущей оболочке. Убедитесь, что вы добавили пробелы между скобками и командами, т.к. в противном случае оболочка может неправильно интерпретировать скобки.. Также убедитесь, что после последней команды в списке стоит точка с запятой (;), как в следующем примере:

Листинг 12. Еще один пример хорошего метода работы #6: Запуск списка команд в текущей оболочке
~ $ { cp ${VAR}a . && chown -R guest.guest a && \ > tar cvf newarchive.tar a; } | mailx admin -S "New archive"

Применяйте xargs к результатам работы поиска find

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

Листинг 13. Пример классического применения команды xargs
~ $ find | \ > xargs

Тем не менее, не думайте, что xargs - это только дополнение к find ; это один из тех инструментов, привыкнув к которому, хочешь работать с ним постоянно, например, в следующих случаях:

Передача списка с разделяющими пробелами

В этой простой иллюстрации xargs похож на фильтр, который берет на вход список (каждый элемент на отдельной строке) и ставит его элементы на одну строку, разделяя их пробелами:

Листинг 14. Пример результата работы команды xargs
~ $ xargsabc a b c ~ $

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

Листинг 15. Пример использования команды xargs
~/tmp $ ls -1 | xargs December_Report.pdf README a archive.tar mkdirhier.sh ~/tmp $ ls -1 | xargs file December_Report.pdf: PDF document, version 1.3 README: ASCII text a: directory archive.tar: POSIX tar archive mkdirhier.sh: Bourne shell script text executable ~/tmp $

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

Листинг 16. Пример хорошего метода работы #7: Использование инструмента xargs для фильтрации текста в одну строку
~/tmp $ ls -l | xargs -rw-r--r-- 7 joe joe 12043 Jan 27 20:36 December_Report.pdf -rw-r--r-- 1 \ root root 238 Dec 03 08:19 README drwxr-xr-x 38 joe joe 354082 Nov 02 \ 16:07 a -rw-r--r-- 3 joe joe 5096 Dec 14 14:26 archive.tar -rwxr-xr-x 1 \ joe joe 3239 Sep 30 12:40 mkdirhier.sh ~/tmp $

Будьте осторожны, используя xargs

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

Знайте, когда использовать grep для подсчета - а когда от него лучше отказаться

Избегайте использовать после grep команду wc -l для подсчета количества полученных строк. Опция -c в grep позволяет подсчитать количество строк, которые соответствуют заданным шаблонам и в целом работает быстрее, чем комбинация " wc после grep ", как в следующем примере:

Листинг 17. Пример хорошего метода работы #8: Подсчет числа строк с и без grep
~ $ time grep and tmp/a/longfile.txt | wc -l 2811 real 0m0.097s user 0m0.006s sys 0m0.032s ~ $ time grep -c and tmp/a/longfile.txt 2811 real 0m0.013s user 0m0.006s sys 0m0.005s ~ $

Опция -c является хорошим способом подсчета не только из-за фактора скорости. При использовании нескольких файлов grep с опцией -c возвращает отдельное значение для каждого файла, по одному на каждой строке, тогда как направление вывода в wc дает общий результат для всех файлов.

Однако, этот пример интересен не только с точки зрения быстродействия – он иллюстрирует еще одну распространенную ошибку. Этот метод подсчета предоставляет только количество строк, в которых найдены соответствия образцу - и хорошо, если это то, что вы ищете. Но в случаях, где строки могут иметь несколько совпадений с конкретным образцом, эти методы не дадут реального числа соответствий образцу . Для подсчета числа совпадений используйте wc . Для начала запустите команду grep с опцией -o , если ваша версия поддерживает это. Эта опция выводит только соответствие образцу, по одному на каждую строку, но не саму строку. Ее нельзя использовать вместе с опцией -c , поэтому используйте wc -l для подсчета числа строк, как в следующем примере:

Пример хорошего метода работы #8: Подсчет числа совпадений с образцом с grep
~ $ grep -o and tmp/a/longfile.txt | wc -l 3402 ~ $

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

Сравнение определенных полей вывода, не только строк

Инструмент, подобный awk , предпочтительнее grep , когда вы хотите найти соответствие образцу только в конкретных полях строк вывода, а не во всей этой строке.

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

Листинг 19. Пример плохого метода работы #9: Использование grep для поиска образца в определенных полях
~/tmp $ ls -l /tmp/a/b/c | grep Dec -rw-r--r-- 7 joe joe 12043 Jan 27 20:36 December_Report.pdf -rw-r--r-- 1 root root 238 Dec 03 08:19 README -rw-r--r-- 3 joe joe 5096 Dec 14 14:26 archive.tar ~/tmp $

В этом примере grep фильтрует строки и выводит все файлы, в имени или дате изменения которых содержится Dec . Поэтому такой файл, как December_Report.pdf, подходит, даже если он не был изменен с января. Это, возможно, не то, что вы хотите. Для поиска соответствия образцу в конкретном поле лучше использовать awk , в котором относительный оператор соответствует конкретному полю, как показано в следующем примере

Листинг 20. Пример хорошего метода работы #9: Использование awk для поиска образца в определенных полях
~/tmp $ ls -l | awk "$6 == "Dec"" -rw-r--r-- 3 joe joe 5096 Dec 14 14:26 archive.tar -rw-r--r-- 1 root root 238 Dec 03 08:19 README ~/tmp $

Более подробно об использовании awk можно прочитать в разделе .

Не используйте передачу вывода cat

Основная и распространенная ошибка в использовании grep заключается в передаче вывода cat в grep в grep для поиска по содержимому отдельного файла. Это абсолютно ненужная потеря времени, потому что такие инструменты, grep , принимают названия файлов в качестве аргументов. Поэтому использовать cat в этой ситуации совершенно не нужно, как в следующем примере:

Листинг 21. Пример хорошего метода работы #10: Использование grep с cat и без него.
~ $ time cat tmp/a/longfile.txt | grep and 2811 real 0m0.015s user 0m0.003s sys 0m0.013s ~ $ time grep and tmp/a/longfile.txt 2811 real 0m0.010s user 0m0.006s sys 0m0.004s ~ $

Эта ошибка допускается во многих случаях. Так как большинство инструментов получают стандартный ввод в виде аргумента, используя тире (-), даже аргумент для использования cat для разнообразия многочисленных файлов с stdin часто не действителен. На самом деле необходимо выполнить конкатенацию перед использованием cat с одной из его нескольких опций фильтрации.

Заключение: привыкайте к хорошему

Мы рассмотрели несколько методов работы в командной строке. Плохие привычки замедляют работу и часто приводят к непредвиденным ошибкам. Эта статья рассказывает о 10 новых способах, которые могут помочь вам избавиться от многих наиболее распространенных ошибок. Привыкайте к этим хорошим методам и улучшайте свои навыки работы с командной строкой Unix.

Долгими зимними вечерами я сидел и думал "вот придёт светлое время, я сяду и как следует разберусь с этой загадочной утилиткой xarg". Ну вот время похоже и пришло - я сел разбираться. Первое, что мне бросилось в глаза, это то, что man к ней довольно загадочный, и с первого раза не просветляет. на википедии понимания тоже не добавила, а скорее даже запутала, поэтому я решил провести своё собственное расследование, и написать по этому поводу небольшой мануальчик. Как известно, пока объясняешь и сам поймёшь:)

Итак, xargs.

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

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

1. Обычный. По умолчанию разделителем аргументов считается любой пробельный символ: пробел, табуляция, вертикальная табуляция или перевод строки. Но как и в командной оболочке можно использовать "" или \ что бы предотвратить разбиение аргумента.

2. Обычный, с группировкой. Режим, включающийся параметром -L. Практически идентичен предыдущему, за исключением того, что xargs запоминает, какой аргумент на какой строке находится. Более того, если строка оканчивается пробелом или табуляцией, следующая строка считается продолжением текущей.

3. По строкам. Включается при использовании опции -I или -0. При этом вся строка считается одним целым аргументом, несмотря на пробелы и табуляции внутри. Для -I концом строки является символ "\n" а для -0 символ "\0"

Проведём пару испытаний, что бы лучше понять всё это. Создадим файл test с следующим содержимым(== в файл заносить не надо):
==
arg1
arg2 space
"arg3 quoted"
arg4\ escaped
arg5 with
continue
==
(После "arg5 with" должен быть пробел)
А так-же напишем небольшой скрипт tp, который будет выводить свои аргументы разделяя их символом ":" и количество:
==
#!/bin/bash
echo -n "@$#"
while [[ $1 != "" ]]; do echo -n ":$1"; shift; done
echo
==

Обычный режим(выделение аргументов по пробельным символам):
x $ cat test | xargs ./tp
@8:arg1:arg2:space:arg3 quoted:arg4 escaped:arg5:with:continue
Файл был разбит на аргументы по пробельным символам, но строки взятые в кавычки и экранированные символом "\" остались целыми.

Обычный режим с группировкой по строкам не отличается от предыдущего на этом этапе.

Разбиение по строкам. Создадим второй тестовый файл следующей командой:
x $ cp test testz && printf "\0arg6" >> testz
Проверим
x $ cat testz | xargs -0 ./tp
@2:arg1
arg2 space
"arg3 quoted"
arg4\ escaped
arg5 with
continue
:arg6

Как можно видеть аргумента всего 2. Первый длинный, сохранивший переводы строк, кавычки и \, а второй arg6. В файле они разделены нулевым символом.

По поводу разделения параметров можно ещё сказать о опции -d, которая указывает новый разделитель. Например попробуем использовать "3" как разделитель.
x $ cat test | xargs -d 3 ./tp
@2:arg1
arg2 space
"arg: quoted"
arg4\ escaped
arg5 with
continue
Произошло разделение файла на 2 части на месте символа "3". Что примечательно, таким образом можно эмулировать опцию -0
x $ cat testz | xargs -d "\x00" ./tp
@2:arg1
arg2 space
"arg3 quoted"
arg4\ escaped
arg5 with
continue
:arg6

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

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

Теперь рассмотрим, как формируются группы.

1. Если опций нет, то группа одна, в неё попадают все аргументы из потока ввода. Группа бесконечного размера, так сказать:)

2. Опция -L n задаёт группировку по строкам. В команду передаются аргументы находящиеся на n строках. Продемонстрирую на примерах.
Группировка по 1 строке:
x $ cat test | xargs -L 1 ./tp
@1:arg1
@2:arg2:space
@1:arg3 quoted
@1:arg4 escaped
@3:arg5:with:continue
Видно, что вторая строка содержит 2 аргумента, потому как они оба на одной строке. А а последняя вообще 3, так как предпоследняя строка "удлинняется" за счёт пробела в конце.

Теперь группировка по 2 строки. В команду попадают строки 1 и 2; 3 и 4; и сиротка 5-ая:
x $ cat test | xargs -L 2 ./tp
@3:arg1:arg2:space
@2:arg3 quoted:arg4 escaped
@3:arg5:with:continue

3. Группировка по аргументам, задаваемая опцией -n x. Тут всё прозрачно: аргументы группируются по x штук и передаются в команду.
По одному аргументу:
x $ cat test | xargs -n 1 ./tp
@1:arg1
@1:arg2
@1:space
@1:arg3 quoted
@1:arg4 escaped
@1:arg5
@1:with
@1:continue
По 2 аргумента:
x $ cat test | xargs -n 2 ./tp
@2:arg1:arg2
@2:space:arg3 quoted
@2:arg4 escaped:arg5
@2:with:continue

3. Режим с подстановкой - опция -I. Для начала надо напомнить, что в данном режиме аргументы из потока ввода разбираются по особому. Каждая строка это один целый аргумент, склеивание строк не производится. Во вторых, у опции -I имеется параметр - строка, которая заменяется в команде на аргумент:
x $ echo -e "A B\nC D" | xargs -I _ ./tp =_+_=
@1:=A B+A B=
@1:=C D+C D=
Легко заметить, что символ _ задан как строка подстановки аргумента, которая используется в команде 2 раза. Так же видно, что аргументы выделяются целыми строками, и пробел не влияет на разбор. Команда вызывается для каждого аргумента.

С подстановкой всё. Рассмотрим оставшиеся важные опции
-r - не выполнять команду, если нет аргументов:
x $ cat /dev/null | xargs ./tp
@0
x $ cat /dev/null | xargs -r ./tp
x $
Как видим, во втором случае команда не выполнилась.

P - xargs будет запрашивать подтверждение на выполнение каждой команды.

На этом небольшой мануал завершён. Он оказался не совсем кратеньким, зато надеюсь понятненьким;)

Посмотрело: 717

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

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

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

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

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

[команда_генератор_списка] | xargs [опции_xargs] [команда]

Рассмотрим, как все это работает, на материале простых и хрестоматийных примеров.

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

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

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

$ ls
one.sh one.py two.sh two.py

$ 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

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

file * | grep ASCII | cut -d":" -f1 | xargs -p vim

будет последовательно открывать файлы для редактирования в vim.
Обратим внимание на опцию -p. Благодаря ей команда будет выполняться в интерактивном режиме: перед открытием каждого файла будет запрашиваться подтверждение (y/n).

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

$ find . -type f -printf " s %pn" | sort -n | cut -b22- | tr "n" "