Комментарии 45
да…
в целом, статья любопытна, но с трудом представляю где это может быть полезно. с «более читаемый» я бы поспорил + не стоит забывать об области применения тех или иных технологий
Я вообще большой противник всякого рода мета-программирования и прочей формы интеллектуального просирания ресурсов в никуда. И к использованию xargs я пришёл через длительный опыт разного рода скриптования, в т.ч. «на ходу». Я точно могу сказать, что xargs — одна из весьма полезных утилит, а списковый подход при обработке — это гигантская экономия кнопкодавления и уменьшение числа ошибок. Причём речь идёт не о написании программ, а именно о коньюктурной работе.
Вот, например, как выглядит типовой однострочник для XCP:
xe sr-list type=lvmoiscsi --minimal|xargs -d, -n 1 -I U xe pbd-list sr-uuid=U --minimal|xargs --verbose -d, -n 1 -P 16 -I U xe pbd-unplug uuid=U
Взять все SR с типом lvmoiscsi, взять все pbd с sr-uuid из списка, для каждого (с параллельностью 16) сделать unplug.
Сравнить с:
for a `xe sr-list type=lvmoiscsi --minimal|tr, ' '`;do for b in `xe pbd-list sr-uuid=$a --minimal|tr, ' '`;do echo $b;xe pbd-unplug uuid=$b;done;done
Заметим, второе — не паралелльное, жутко путанное и длиннее. Даже не столько по буквам, сколько по числу осмысленных конструкций, требующих внимания при написании.
Вот, например, как выглядит типовой однострочник для XCP:
xe sr-list type=lvmoiscsi --minimal|xargs -d, -n 1 -I U xe pbd-list sr-uuid=U --minimal|xargs --verbose -d, -n 1 -P 16 -I U xe pbd-unplug uuid=U
Взять все SR с типом lvmoiscsi, взять все pbd с sr-uuid из списка, для каждого (с параллельностью 16) сделать unplug.
Сравнить с:
for a `xe sr-list type=lvmoiscsi --minimal|tr, ' '`;do for b in `xe pbd-list sr-uuid=$a --minimal|tr, ' '`;do echo $b;xe pbd-unplug uuid=$b;done;done
Заметим, второе — не паралелльное, жутко путанное и длиннее. Даже не столько по буквам, сколько по числу осмысленных конструкций, требующих внимания при написании.
Спасибо за ёмкий пример, его бы во введение. Не против я вашего подхода, да и проблем не вижу пока. Но на практике: в простых вещах извращаться противопоказано, а в сложных — будет уже другой/не shell.
* вы так выгодно для себя вложенные циклы пишете в одну строку, что использование циклов в принципе не очень привлекательно. ИМХО, (по статье) то что можно одной строкой написать — вы пишете несколькими, а то что несколькими оформляется — пишете в одну
* вы так выгодно для себя вложенные циклы пишете в одну строку, что использование циклов в принципе не очень привлекательно. ИМХО, (по статье) то что можно одной строкой написать — вы пишете несколькими, а то что несколькими оформляется — пишете в одну
Если еще не успели, то присмотритесь к GNU Parallel, выполняет те же функции, что и xargs, но лишен многих детских болезней, дает больше контроля над процессом и обладает большим функционалом.
Вообще говоря, вся суть метапрограммирования и заключается в основном в экономии кнопкодавления, а так же ликвидации просирания ресурсов мозга на разбор бессмысленно навороченных конструкций. Если решение с метапрограммированием сложнее наивного — это бездарно примененное метапрограммирование :)
Дело в том, по какой метрике оценивается «экономия». Человеку, тащущему метапрограммирование, хочется нетривиальной задачи и потом элементарное добавление новых фич/запросов. Человеку, который фичи/запросы даёт, интересует время реализации оных.
И получается, что «два месяца на метапрограммирование, потом фича за пол-часа» вместо «три дня унылого кодения на каждую фичу» для программиста — круто и хорошо, а для менеджера проекта — нет, потому что фич всего десять, а всё остальное за горизонтом планирования.
И получается, что «два месяца на метапрограммирование, потом фича за пол-часа» вместо «три дня унылого кодения на каждую фичу» для программиста — круто и хорошо, а для менеджера проекта — нет, потому что фич всего десять, а всё остальное за горизонтом планирования.
Мы, возможно, говорим о чём-то разном. В моем любимом Ruby переписывание нетривиального куска, повторенного 3-4 раза, с использованием метапрограммирования займет в итоге меньше строк и меньше времени, чем репликаиция куска еще столько же раз и вылавливание в нем какого-нибудь бага.
(Это, конечно, если использовать его к месту, а не как в текущем проекте: замечательный файлик в двести строк, состоящий из запутанных конкатенаций, который генерирует эквивалент примерно десяти строк рукописного кода.)
(Это, конечно, если использовать его к месту, а не как в текущем проекте: замечательный файлик в двести строк, состоящий из запутанных конкатенаций, который генерирует эквивалент примерно десяти строк рукописного кода.)
Мы говорим о разном и о разном масштабе. То, что вы говорите, это мелочь и ерунда. Я ж в примере указал сроки, о которых речь (можно ещё умножить на несколько человек в команде).
Единственное непонятно — нафига использовать stderr не по назначению, когда размножение строк можно было точно так же и через stdout сделать: tee /dev/stdout
Вас не затруднит привести пример, как stdout продублировать на два различных stdin? Давно уже ищу элегантное решение, но пока кроме tee /dev/stderr да волшебства в /dev/fd ничего не попадалось.
А, сорри, не разглядел сразу что последующее объединение происходит не сразу, а через вызов.
Если придумаю что-то — напишу сюда :)
Если придумаю что-то — напишу сюда :)
Привожу пример:
Применительно к данной статье:
echo "a" | xargs -I % sh -c 'echo "%/1"; echo "%/2"'
Применительно к данной статье:
echo "/usr/bin/gnote"| xargs -I % sh -c 'echo "%"; ldd "%"|cut -d" " -f3|grep "\S"'
Это правда не будет работать если в строках будут кавычки "".
Но для данной задачи — сойдет. По крайней мере лучше чем хак через stderr
Но для данной задачи — сойдет. По крайней мере лучше чем хак через stderr
Занятно, спасибо, Но мне не подходит 8( Я ищу способ обрабатывать потоки в миллионы строк.
Именно однострочником надо?
Потому что через именованные пайпы можно безо всякого оверхеда это делать, но там отдельными командами нужно создать/удалить пайп
Потому что через именованные пайпы можно безо всякого оверхеда это делать, но там отдельными командами нужно создать/удалить пайп
mkfifo /tmp/fifo1;
echo "a" | tee /tmp/fifo1 | (cat /tmp/fifo1 | (cmd1); cat - |(cmd2)); r
m -f /tmp/fifo1
Вместо cmd1 и cmd2 подставить соответственно команды для каждой ветки
У именованных пайпов есть очень большой недостаток — если команду вынесли, то пайпы останутся на диске. Соответственно написать хоть какую обёртку на этот случай уже не получется красиво — остаётся захламлённый уголок. В идеале я бы хотел получить некий способ взять поток данных и разделить его на N независимых потоков. Можно даже полное копирование и дальнейшую фильтрацию awkом.
PS Кстати, обратную задачу я уже решил использовав github.com/vi/fdlinecombine/ 8)
PS Кстати, обратную задачу я уже решил использовав github.com/vi/fdlinecombine/ 8)
Я думаю это решаемо (даже без именованных пайпов).
Просто возможно будет громоздко.
Чуть позже напишу решение.
Просто возможно будет громоздко.
Чуть позже напишу решение.
НЛО прилетело и опубликовало эту надпись здесь
Круто.
Получается что >(cat) это и есть тот «именованный» пайп к команде внутри () автоматически удаляемый при завершении родительского процесса, о чем просили выше в комментах.
«Именованный», потому что у него есть имя (/dev/fd/XXX), которое можно передать в ком.строке, но физически файла нет.
Спасибо
Получается что >(cat) это и есть тот «именованный» пайп к команде внутри () автоматически удаляемый при завершении родительского процесса, о чем просили выше в комментах.
«Именованный», потому что у него есть имя (/dev/fd/XXX), которое можно передать в ком.строке, но физически файла нет.
Спасибо
А вот это да, круто. Ветвление pipe'ов.
В общем такое решение:
В перл передается поток через stdin, там открываются локальные пайпы для stdin каждого переданного аргумента (являющегося произвольной командой допустимой в шелле) и в каждый такой пайп выводится копия потока.
А уже задача каждой команды как то обработать или просто вывести свою копию потока в stdout.
В данном примере указаны команды «cat -» и «cat -», т.е. создаются 2 копии потока и просто выводятся в stdout.
Естественно без отладки из головы каждый раз такую команду не наберешь, поэтому для практического применения вероятно нужно оформить в виде алиаса или отдельного скрипта (это легко сделать, т.к. тело команды не надо менять для разного числа аргументов).
echo "a b" | perl -e 'open($OUT[$_], "|$ARGV[$_]") or die "$!:$ARGV[$_]\n"for 0..$#ARGV; while (my $l = <STDIN>) { for (0..$#ARGV) { my $OUT = $OUT[$_]; print $OUT $l}} close($_) for @OUT' "cat -" "cat -"
В перл передается поток через stdin, там открываются локальные пайпы для stdin каждого переданного аргумента (являющегося произвольной командой допустимой в шелле) и в каждый такой пайп выводится копия потока.
А уже задача каждой команды как то обработать или просто вывести свою копию потока в stdout.
В данном примере указаны команды «cat -» и «cat -», т.е. создаются 2 копии потока и просто выводятся в stdout.
Естественно без отладки из головы каждый раз такую команду не наберешь, поэтому для практического применения вероятно нужно оформить в виде алиаса или отдельного скрипта (это легко сделать, т.к. тело команды не надо менять для разного числа аргументов).
Нельзя. Если на /dev/stdout вывести, то вывод уползёт в ldd, а не окажется «за» ldd (что делает вывод в /dev/stderr и 2>&1).
Вместо awk '{print $3}' можно использовать cut -f3 (не всегда — cut, например, не может использовать понятие «обобщенный пробел» в качестве разделителя). Для продвинутой обработки также весьма годится perl -e '' (скормить stdout программе) и, более интересный вариант, perl -ne '' (скормить stdout программе, которая перед этим обернется в цикл по строкам stdout)
Простите, парсер съел содержимое кавычек
perl -e '[program]'
и
perl -e '[cycle_body]'
perl -e '[program]'
и
perl -e '[cycle_body]'
А есть ещё «perl -pne», который вдобавок выводит на печать построчно
cut -d\ -f3 (два пробела после \)
И только если разделитель — одиночный пробел.
Но если условия подходят — это короче и проще, чем awk.
И только если разделитель — одиночный пробел.
Но если условия подходят — это короче и проще, чем awk.
На мой вкус, ничего функционального в таком подходе нет: xargs — возможность вызывать команды, которые сами не умеют работать с аргументами с stdin'а. По хорошему, просто обёртка над циклом в духе «while read var; do $OMG_FUNCTION $var & done;».
Я ничуть не умаляю достоинства xargs, штука удобная… Но вроде, это такой же императивный подход, как и обычно. И да, не используя xargs, я всё равно могу выделить те же куски конвеера и так же обернуть их в функции. (Хотя лаконичней от этого код не станет).
Also, можно было бы использовать tag code и переносы строк ) И rss целы, и читатели довольны.
Я ничуть не умаляю достоинства xargs, штука удобная… Но вроде, это такой же императивный подход, как и обычно. И да, не используя xargs, я всё равно могу выделить те же куски конвеера и так же обернуть их в функции. (Хотя лаконичней от этого код не станет).
Also, можно было бы использовать tag code и переносы строк ) И rss целы, и читатели довольны.
Очень годно! Действительно шелл велик!
Два замечания
1) Вместо флуда stderr лучше создать именованный пайп и гнать данные туда. Однострочник не получится, но будет более правильно и безопасно (мало ли кто в stderr напишет в этом шелле).
2) Размер файла лучше читать через stat, а не du.
Два замечания
1) Вместо флуда stderr лучше создать именованный пайп и гнать данные туда. Однострочник не получится, но будет более правильно и безопасно (мало ли кто в stderr напишет в этом шелле).
2) Размер файла лучше читать через stat, а не du.
По поводу сырца в ужасе — а как же экранирование переводов строк для длинных команд? По-моему, удобно :)
Не нашёл манула обещанного.
А можно пару слов про значения аргументов -n -P для xargs?
C -n вроде понятно, а вот откуда такие -P и как они зависят от -n?
C -n вроде понятно, а вот откуда такие -P и как они зависят от -n?
xargs хорошая штука хотя бы потому, что передает допустимой длины argv[] и самостоятельно выполняет программу нужное количество раз.
Мой любимый пример — сделать что-нибудь с миллионом файлов.
Можно еще делать command $(generate-list-of-arguments), но легко натолкнуться на Argument list too long, сложнее решить проблемы с пробелами в именах файлов и хуже согласуется с естественным языком
Мой любимый пример — сделать что-нибудь с миллионом файлов.
Можно еще делать command $(generate-list-of-arguments), но легко натолкнуться на Argument list too long, сложнее решить проблемы с пробелами в именах файлов и хуже согласуется с естественным языком
Я думаю у вас не к месту использован термин «линейное программирование».
Как-то так получилось что линейное программирование не связано никак с программированием.
Как-то так получилось что линейное программирование не связано никак с программированием.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Функциональное программирование в шелле на примере xargs