Страница 1 из 1

Размер pipe буфера?

СообщениеДобавлено: 10 дек 2011, 16:41
Сергей Дубров
Нужно однострочным скриптом (будет вызываться из перлового скрипта с помощью system()) заменить в нескольких файлах (dns-зоны) одну подстроку на другую. Количество и имена файлов заранее неизвестны. В нормальных nix-ах это делается без проблем, как-то так:
Код: Выделить всё
find . -maxdepth 1 -name "*" -type f -print | xargs sed -i /SOA/s/serv1/serv2/

или даже ещё проще:
Код: Выделить всё
sed -i /SOA/s/serv1/serv2/ *

Но есть система (OpenBSD), на которой sed не понимает ключа '-i' (in place). Решение с выводом во временные файлы с последующим переименованим выглядит неудобным и громоздким. Попытался изобразить нечто такое:
Код: Выделить всё
find . -name "*" -type f -print | xargs -I {} sh -c "sed /SOA/s/serv1/serv2/ {} | cat > {}"

В конце пришлось ставить именно | cat > {}, а не просто > {}. Сначала показалось, что нормально работает скрипт, но оказалось, что он обрезает размер выходного файлов до 32768 или до 49152 (32768+16384) байт (размеры меняются между этими двумя значениями от запуска к запуску). Видимо, это некий лимит внутреннего буфера pipe. Все файлы меньше указаного размера преобразуются без проблем 'in place'.

Кроме очевидного варианта с установкой GNU sed на OpenBSD (с сожалению, невозможно, по некоторым причинам), есть ли варианты, меняющие "на-лету" размеры буфера pipe? Или у меня просто глаз замылился и есть другие решения? Напомню, никаких внешних скриптов-файлов, всё должно уложиться в одну строку.

Re: Размер pipe буфера?

СообщениеДобавлено: 10 дек 2011, 17:09
Сергей Дубров
[quote="Сергей Дубров"]
Код: Выделить всё
find . -name "*" -type f -print | xargs -I {} sh -c "sed /SOA/s/serv1/serv2/ {} | cat > {}"

Только отправил сообщение, как сразу же в голову пришёл вариант решения с промежуточным файлом:
Код: Выделить всё
find . -name "*" -type f -print | xargs -I {} sh -c "mv {} $.$; sed /SOA/s/ns1/ns2/ $.$ | cat > {}; rm $.$"

Вроде работает :)

Re: Размер pipe буфера?

СообщениеДобавлено: 11 дек 2011, 14:29
Константин Ошмян
Строго говоря, первый вариант (где одной командой и читается из файла, и тут же в него пишется через ">") - неправилен идеологически. Поскольку, формально, шелл должен сразу же обрубать этот файл до нулевого размера (конструируя конвейер и перенаправления ввода/вывода), а только затем запускать команды из этого конвейера. То, что на OpenBSD такая конструкция вообще при каких-то условиях выдаёт некий результат - особенности реализации, но это непереносимо и закладываться на это нельзя.

Ну а правильное решение (с временным файлом) вы уже нашли :-)

Re: Размер pipe буфера?

СообщениеДобавлено: 11 дек 2011, 16:47
Сергей Дубров
Константин Ошмян писал(а):То, что на OpenBSD такая конструкция вообще при каких-то условиях выдаёт некий результат - особенности реализации, но это непереносимо и закладываться на это нельзя.

Я наткнулся на это решение на одном линуховом форуме, там оно обсуждалось, как вполне рабочее. Рассматривали только причины, по которым в конце пайпа приходилось ставить "...sed... {} | cat > {}" вместо более простого "...sed... > {}". А сама работоспособность сомнению не подвергалась. Была, правда, ещё одна странность в том варианте: в начале пайпа стояла ещё одна cat - "xargs -I {} sh -c "cat {} | sed /SOA/s/...".

Константин Ошмян писал(а):Ну а правильное решение (с временным файлом) вы уже нашли :-)

Константин, за что ты меня на 'Вы'? :)

Решение-то я вроде нашёл, но в реальной жизни пришлось его чуть допиливать. Вариант, приведённый мною выше:
Код: Выделить всё
find . -name "*" -type f -print | xargs -I {} sh -c "mv {} $.$; sed /SOA/s/serv1/serv2/ $.$ | cat > {}; rm $.$"

был модифицирован для реальных имён и path-ов:
Код: Выделить всё
find /home/maintainer/tmp/master -name "*" -type f -print | xargs -I {} sh -c "mv {} /home/maintainer/tmp/master/$.$; sed /SOA/s/serv1.inp.nsk.su./serv2.inp.nsk.su./ /home/maintainer/tmp/master/$.$ | cat > {}; rm /home/maintainer/tmp/master/$.$"
...и тут же сломался - вместо ~40 выходных файлов он генерил всего 5-6. Подозреваю, что здесь вылезло ограничение на длину ({} заменяется find-ом на полный путь к файлу).

Победил тупо - перед find-ом вставил cd /home/maintainer/tmp/master; и получил вариант, который точно работал, без указания полных путей.

Re: Размер pipe буфера?

СообщениеДобавлено: 12 дек 2011, 00:54
Константин Ошмян
Сергей Дубров писал(а):
Константин Ошмян писал(а):Ну а правильное решение (с временным файлом) вы уже нашли :-)
Константин, за что ты меня на 'Вы'? :)
Извини, не хотел обидеть :)
Я ж и не сказал: "Вы" (персонально), я сказал безлично: "вы" (дескать, ну - вы, там, у себя в Новосибирске... мол, мастаки всякие решения находить!) :wink: :lol:

Re: Размер pipe буфера?

СообщениеДобавлено: 26 окт 2012, 16:23
Константин Ошмян
О, наткнулся на очень полезную статью:
Greg's (also known as GreyCat's) в своей wiki Bash Pitfalls писал(а):This page shows common errors that Bash programmers make
Замечание с символическим номером 13 как раз описывает данную проблему. А вообще статья - из разряда "must have", даже я со своим многолетним опытом скриптописательства нашёл в ней для себя много нового.

Там же рядом: Bash FAQ и ProcessManagement

Re: Размер pipe буфера?

СообщениеДобавлено: 26 окт 2012, 17:08
Сергей Дубров
Константин Ошмян писал(а):О, наткнулся на очень полезную статью:
Greg's (also known as GreyCat's) в своей wiki Bash Pitfalls писал(а):This page shows common errors that Bash programmers make
Замечание с символическим номером 13 как раз описывает данную проблему. А вообще статья - из разряда "must have", даже я со своим многолетним опытом скриптописательства нашёл в ней для себя много нового.

На хабре был опубликован перевод этой статьи:
http://habrahabr.ru/post/47706/
http://habrahabr.ru/post/47915/
http://habrahabr.ru/post/48053/