Comments 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)
Я думаю это решаемо (даже без именованных пайпов).
Просто возможно будет громоздко.
Чуть позже напишу решение.
Просто возможно будет громоздко.
Чуть позже напишу решение.
UFO just landed and posted this here
Круто.
Получается что >(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, сложнее решить проблемы с пробелами в именах файлов и хуже согласуется с естественным языком
Я думаю у вас не к месту использован термин «линейное программирование».
Как-то так получилось что линейное программирование не связано никак с программированием.
Как-то так получилось что линейное программирование не связано никак с программированием.
Sign up to leave a comment.
Функциональное программирование в шелле на примере xargs