DISCLAIMER. English language used here only for compatibility (ASCII only), so any suggestions about my bad grammar (and not only it) will be greatly appreciated.

понедельник, 31 января 2011 г.

Notes for XFree86 Video Timings howto

Some typo in Chapter 11.2 in time to ticks conversion formulas

   Should be

        VR = VAT * HSF (but not VSF!);
        (VFL - VR) = VBT * HSF (but not VSF!);


Definition notes {{{

    dot clock tick = horizontal tick
    dot = raster dot

        All four terms used here interchangeable. Though dot clock tick
        generally refers to time needed to process (draw?) one dot, here it's
        also used to measure horizontal scan line length: we take scan line
        length as number of dot clock ticks passed until monitor have finished
        to draw it.

    scan line tick = vertical tick
    scan line

        These three terms here also used interchangeable. Scan line tick is
        time needed to process (draw?) one horizontal scan line, but it's also
        can be used to measure vertical screen length.

}}}
Frames and frequencies (in ticks) {{{

    DCF = RR * HFL * VFL, (RR == VSF)
    DCF = HFL * HSF
    HSF = VFL * VSF

    where {{{

        DCF (driving clock frequency / dot clock frequency)

            Number of dots (pixels) per second video adapter can emit.  DCF
            should match to monitor's video bandwidth - higher DCF will have
            no effect.

        RR  (refresh rate)

            Number of times per second entire screen is repainted.  (RR is the
            same as VSF);

        HSF (horizontal sync frequency)

            Number of times per second the monitor can write a horizontal scan
            line (HFL dot clock ticks in length).

        VSF (vertical sync frequency)

            Number of times per second the monitor can write a full screen
            (VFL scan line ticks in length). (The same as RR);

        HFL (horizontal frame length)

            Number of dot clock ticks needed for monitor to scan one
            horizontal line, including the inactive left and right borders;

        VFL (vertical frame length)

            Number of scan line ticks needed for monitor to scan entire
            screen, including the inactive top and bottom borders;

    }}}

    On the one hand, HFL is simply number of dots in one horizontal scan line
    and VFL is simply number of scan lines in the entire screen. But we know
    time required to scan one raster dot (duration of one dot clock tick)

        (1 / DCF),

    and time required to scan one horizontal line (duration of one scan line
    tick)

        (1 / HSF)

    so we also can represent HFL and VFL in seconds (see below).

}}}
Sync pulses (in ticks) {{{

    HFL = HR + HGT1 + HSP + HGT2
    VFL = VR + HGT2 + VSP + VGT2
    HB  = HGT1 + HSP + HGT2
    VB  = VGT1 + VSP + VGT2

    where {{{

        HR (horizontal resolution)

            Number of visible horizontal ticks (dot clock ticks). Or simply
            number of visible raster dots;

        VR (vertical resolution)

            Number of visible vertical ticks (scan line ticks). Or simply
            number of visible horizontal scan lines;

        HGT1 (horizontal guard time 1)

            Number of dot clock ticks to wait before sending HSP;

        HSP (horizontal sync pulse)

            Length in dot clock ticks of horizontal sync pulse;

        HGT2 (horizontal guard time 2)

            Number of dot clock ticks between end of HSP and end of horizontal
            frame;

        VGT1 (vertical guard time 1)

            Number of vertical ticks (scan lines) to wait before sending VSP;

        VSP (vertical sync pulse)

            Length in vertical ticks (scan lines) of vertical sync pulse;

        VGT2 (vertical guard time 2)

            Number of vertical ticks (scan lines) between end of VSP and end
            of vertical frame;

        HB (horizontal blanking)

            Length of horizontal blanking interval (see below) in dot clock
            ticks.

        VB (vertical blanking)

            Length of vertical blanking interval (see below) in scan line
            ticks.

    }}}

    HGT1 (VGT1) also called Front Porch and HGT2 (VGT2) - Back Porch.
    Horizontal (vertical) blanking interval - is interval, when beam turned
    off. It consists from three phases: Front porch, Sync pulse, Back porch.
        - Front porch - the beam is black (== blank) and still moving right
          (past the end of the visible part of scan line, but still at the
          same scan line);
        - Sync pulse  - the beam is blacker than black and rapidly moving from
          right physical screen edge to the left to start scan next line;
        - Back porch  - the beam is black (== blank) and moving right again
          (already at the next (now current) scan line, but before the start
          of its visible part);

    Hence, actually what we call horizontal frame locates on two scan lines,
    but if we map all its parts onto one line, they will not overlap. Here is
    illustration:

                        time
    ------------------------------------------------------------------>

     | Scan line N           | Between    | Scan line (N + 1)
     |                       | scanlines  |
     |                       |            |
    >> HR        >> Front   >> sync      >> Back    >> HR        >> ..
    >>           >> porch   >> pulse     >> porch   >>           >> ..
    >> (visible) >> (black) >> (blacker) >> (black) >> (visible) >> ..
     |                                               |
     |      Horizontal frame N                       |


    and here is another one illustration:

                  |      HR (visible) || Front   | 
                  |                   || porch   | 
                  |                   || (black) | 
      R           =====>=====>======>==___>___>___R
      R                                          /R - sync pulse,
      R ..<.........<.........<.........<........ R - move beam to the left
      R/                                          R - (blacker than black)
      R___>___>___=====>                          R
       | Back    |
       | porch   |
       | (black) |

    where

        =       Beam turned on, visible ticks.
        _       Beam turned off, black.
        .       Beam turned off, blacker than black.
        R       Physical screen borders.
        <       Beam moving to the right.
        >       Beam moving to the left.

}}}
Frames and syncs in seconds {{{

    HR = HAt * DCF
    HB = HBt * DCF

    VR = VAt * HSF
    VB = VBt * HSF

    HFL = HFt * DCF
    VFL = VFt * HSF

    where {{{

        HAt (horizontal active time)

            Time (in seconds) needed by monitor to scan HR raster dots.  Or,
            in other words, time required for HR dot clock ticks.

        VAt (vertical active time)

            Time (in seconds) needed by monitor to scan VR scan lines.  Or, in
            other words, time required for VR scan line ticks.

        HBt (horizontal blank time)

            Time (in seconds) needed for HB dot clock ticks.

        VBt (vertical blank time)

            Time (in seconds) needed for VB scan line ticks.

        HFt (horizontal frame time)

            Time (in seconds) needed for HFL dot clock ticks.

        VFt (vertical frame time)

            Time (in seconds) needed for VFL scan line ticks.

        }}}

}}}
Common notes {{{

    HFL is overall number of raster dots in one horizontal frame, or, in other
    words, in the one horizontal scan line. But the screen physical dimensions
    are constant, though HFL (as well as HR) may vary. So, physical length (in
    milimeters) of one raster dot depends from overall number of raster dots in
    horizontal frame, i.e. depends from HFL.  If we increase HFL, then physical
    length of one raster dot will decrease, and, hence, with constant HR,
    physical length of visible area will decrease and black margins will
    increase. And vice versa: if we decrease HFL, then physical length of one
    raster dot will increase, and, hence, with constant HR, physical length of
    visible part of horizontal frame will increase and black margins will
    decrease.

    Duration of Front porch controls the size of right black margin.  Duration
    of Back porch controls the size of left black margin.

    Hence, to move image to the right (left), we should move sync signal
    relative to HB interval to the left (right), effectively changing front
    and back porches.  To squeeze (stretch) image in both (right/left or
    up/down) directions (with constant HR or VR) we should change HFL (VFL)
    and move sync signal in the same relative position in the HB interval, as
    it was before, effectively changing raster dot physical length, and hence
    area occupied by visible part of horizontal (vertical) frame.  To change
    all four visible area's dimensions (with constant RR), we should change
    DCF, effectively changing (HFL * VFL) product.

}}}

четверг, 13 января 2011 г.

Отчет о Новогоднем путешествии "Новогодний Quest 3-8 января 2011 года". Экипаж №40

(отформатированная версия оригинала с форума "Магистрали" )

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggBNrYwumHYCb8TuClRyJWI_5NG8jB0HdaKU6zwQXgkDvQ7FBdd4t526htMxUMN_rGrsxHoODgn7CUIGRTCljLAE4c_K468GaKKkOAOJc_e3qZX1p3iRtx8VUTEF7qu3Yb8wAIKamqXJCL/s800/IMG_8896_360x480.JPG

3 января. Первый день путешествия Москва - Покров - Владимир

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

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNIQ6IFtusFOwYrLvFE94FavVeK9140HcW93j1u1tKCDfDHSmV38D9BE7-vseVO45hM65FOmuwLd31h4ybEsdWUdNj5UH08uEQ6PwBNABnXVv8fYcwAhsw6_3vOwq9yNWEc98K5H5YcN_1/s800/IMG_8664_360x480.JPG

В 8-00 получили задание. Посмотрели карты, бегло пробежали контрольные точки, определили маршрут, около 13 часов выехали. Хотелось поскорее куда-нибудь доехать и отгадать загадку. Первым на нашем пути был Покров. В придорожном магазине нам указали, где стоит памятный крест и шоколадная фея. Радостно взяв свои первые КП, мы бросились к первому встречному мужчине с вопросом об островном монастыре. Мужчина оказался иностранцем, а женщина с ним - его жена - журналисткой. Они давно хотели съездить на остров, но никак не получается. Так в чем же дело? Поехали с нами! По пути наши новые знакомые рассказали нам историю монастыря. Журналистка в свое время сняла о нем фильм. В монастыре, поговорив с сестрами и объяснив кто мы, к нам пригласили матушку, которая открыла нам двери уже запертой церкви. В церкви нас попросили помочь перенести к алтарю целый ящик свечей. Мы, конечно, все сделали, и нам пообещали за это прощение грехов. Если бы все было так просто! Потом мы привезли наших знакомых домой, пообещали выслать им фотографии и поехали во Владимир, поскольку там была забронирована гостиница ( "Золотое кольцо"), которая на ближайшее время становилась нашей базой. С третьего раза заехали на парковку (все куда-то не туда тянуло), поселились в двухместном стандарте (горячая вода + завтрак + интернет, только распечатать несколько листов нам нигде в городе так и не удалось).

4 января. Второй день путешествия Владимир - Лакинск - Петушки - Покров - Новоселово - Владимир

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh54RGZwD0OpQMCA5UNMDhvytbt-velv6tGEfOaysCvmT277TSOfGJEkui_p8WNsJcYgsNKUd4kJZwQBkdf-9xRvGbpi5MiqXmA3iFDpFu2xjSwEF4sf1vMwYZYZVRLWpIESupO3pkTkNia/s800/IMG_8584_480x360.JPG

Был посвящен городам на трассе Москва - Владимир. Лакинск особых трудностей не вызвал. Первые встречные мужики подробно рассказали, где найти памятный камень Лакину и как он выглядит. Спортсменов сами заметили. Поразились и порадовались их энергии и напору. Уличные КП в Петушках нашли быстро, посетили неохваченные в первый день музеи и собрали музейные КП. Пожалели, что нет времени отведать горячего шоколода. Решили обязательно приехать потом.

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFJhQDrTsSyZMuhCITnUd3pnoth-Cjf21B3y4b0JcgjTK8KQERA1ucrc9xyhAigN9bfGpv0yZHmy5jDGEg1rX-Oxt80agUOOFEWJJvhvVGDgjCEe7Nx10pKu7TGt3fcS-ue_2KK8-o5Dp_/s800/IMG_8629_480x360.JPG

А вот памятник Гагарину поискали. Сначала проскочили поворот на мемориал, потом заехали слишком далеко к Андреевской церкви. Там встретили других участников автопробега. Служитель музея рассказал, где стоит стела, и после фотографирования мы уехали. Тихое торжественное место.

5 января. Третий день путешествия Владимир - Суздаль - Гаврилов Посад - Суздаль

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlFOKiVSkpvLQRMdHcx83HIpe3AGztW48hGQ0F1BZDBm-AvE6fL2JcG2yuKdrHJts7V5oVdJoOCYKOCn8pMZA_i1Dby3TDA-SrUlXo2PeWXfprOIvgIfuZ1ysK55INn6w9GTM0QkIOtP6y/s800/IMG_8735_480x360.JPG

Утром собрали все КП по Владимиру. Трижды пытались попасть с улицы Горького на Большую московскую, чтобы выехать в начало Октябрьского проспекта к памятнику воинам-интернационалистам (КП-58), но безуспешно. Один раз упилили по Судогодскому шоссе в поселок Коммунар, но потом все-таки пробрались куда надо по другим улицам. Дворника (КП-59) искали недолго - примерно знали, где он. А вот из Владимира в Суздаль выехали не той дорогой: выскочили на дорогу на Юрьев-Польский. Возвращаться не хотелось - надоело кружить. Решили продолжить движение, и в Старом дворе повернули по указателю направо. Дорога шла через небольшие деревни Малининский, Тарбаево, Туртино. Лед. Ветер свистит. Метель переметает. На горизонте земля соединяется с небом. Мы одни. А дорога вьется вверх-вниз и вдали уходит в небо. Вот так - дорога в небо... Замечтались-загляделись, и на вершине подъема на льду снесло нас на обочину в сугроб. Прочно так сели, по самое брюхо. Никто не проедет, одни мы, расчитывать не на что. Достали лопату - раскапывать! И вдруг - чудо: встречный джип! Останавливается. Спрашивает нас про трос, и через 20 минут после совместных усилий мы снова на дороге и в строю! Ура, мы спасены из снежного плена. Спасибо-спасибо.

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbqObP15EymwF3QgwvwKcdlPI6SVA3B1-TPdBSmVw9AP_KVSBFdS0Pk5RnxT2OyAVAZd-SLWpUDoyYI-yto7ihmyLCDQk2iGw0RiJgwbZKsR7L20D5dqPZB30CqbZ9whgtLq5Tmfz8UMVE/s800/IMG_8742_480x360.JPG

Благополучно проезжаем Суздаль, мчимся в Гаврилов Посад, по пути замечая какая красота проносится мимо. Быстро находим там все точки и возвращаемся на ночевку в Суздаль (забронирована гостиница "Золотой ручей", напротив Кремля, все хорошо, но дороговато). Вечером погуляли по городу.

6 января. Четвертый день путешествия Суздаль - Юрьев-Польский - Городище - Подолец - Юрьев-Польский

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghZhjynqSRizg_biDkSMi3VMlG0bG2klfCxW2k3r5W-McLoqwKmTRCU57oRi33FapMdmQ5E_UsFMgMMKBrO48LMl3Z48QOG628x7FusngfHxGm9yi0iHYvucC96BAltKD_yVEvTDR0fqsp/s800/IMG_8782_360x480.JPG

Утро было посвящено памятникам в Суздале. Звонница в Спасо-Ефимьевском монастыре (КП-48) трудностей не вызвала. Удалось даже послушать звоны. Церковь Александра Невского нам указали жители (очень понравилась), а вот церковь Николая Чудотворца (КП-51) в Музее деревянного зодчества не нашли. Пришлось поехать в Кремль и на Кремлевской улице потолкаться с лошадьми. Это нас позабавило, мы быстренько сняли церковь и направились дальше.

Путь наш лежал в Юрьев-Польский. Поехали дорогой через Крапивье - Гавриловское - Цибеево - Обращиха. Эта дорога оказалась более оживленной, чем через Старый двор, и тоже очень красивой. В Юрьев-Польском сняли нужные точки быстро и поехали по деревням. Там тоже нашли все без проблем. Очень понравился Подолец, полюбовались и поехали назад. В пути поснимали закаты и дорогу. Канун Рождества. В гостинице нас ждала бутылка шампанского - подарок от администрации в честь праздника.

7 января. Пятый день путешествия Юрьев-Польский - Кольчугино - Киржач - Александров - Сергиев Посад - Александров

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCf0NHA34Wk1tO0mcpcbU7lg2slaYPKuTMwC6gf78cPh7CMEvlEUMh5vYBbuhspVZo0HAc9CdSSBQdqvgETTombFrm2BOxQUoj9w476xeqysQ1WpPTfwUYmY7ZNxGyOGTbX0cNTnH1Wgb-/s800/IMG_8858_480x360.JPG

В этот день мы объехали наибольшее количество точек. В Кольчугино искали долго главную площадь с бюстом Кольчугина (КП-17), проверили три из четырех въездов в город в поисках сталевара, а вот Ильича нашли быстро - знали, где он спрятался. В Завалино произвела впечатление переправа через мостик из труб, а в Карабаново - проезд через арки под ж.д.

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIBGrFOlorJeT57eq6PRv1M6IqrymDjlXIxgWVqD5fexl4ccID9h01uD_VuOU8V6OxhIxj2hmk24K03M6SAD7e2-KXwVEnZsU-KahM6MPDVq6x8dKX07fs5KyNhxkrsFNYjaPKny5zNCPQ/s800/IMG_8898_360x480.JPG

Очень понравился Махрищский монастырь, Киржач. Вечером прибыли в Александров. Музей уже был закрыт. Чтобы сэкономить время на следующий день, решили съездить в Сергиев Посад. Около Лавры быстро собрали все точки, скит нашли тоже легко, а вот скульптуру Николая Чудотворца пришлось поискать: спросить не у кого, сами найти не можем, темно, собаки воют, страшно. Огорченные, даже уезжать собрались, но вдруг по пустынной улице идет человек, мы - к нему. Рассказал нам все, оказалось - священник, нас расспросил - кто такие, зачем пожаловали. Мы ему про свой долгий путь поведали. Одобрил, благословил. К 12 часам ночи мы вернулись в гостиницу в Александров.

8 января. Шестой день путешествия Александров - Лукьянцево - Сергиев Посад-Москва

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEja6yyFHNrofUqU3QaqLKK9DqLywRntZeu_gYDupvfnikvQpxWan143gzBAcEvajvug5_IDFU-XjP9_t6VjwteAbFx55yniLCks2CfX2pRyzwvfGZCLv-T7UaDaHGX3vJO1KqKjsGz3cEvn/s800/IMG_8925_360x480.JPG

С утра - в Лукьянцево. Там на ледяной дороге встретили деда на велосипеде "Украина" и пообщались с местными молодыми людьми, которые смотрели на нас с завистью: Вы из "Магистрали"? Да. Здорово. Мы тоже хотим так же путешествовать. Следующий раз обязательно поедем.

Музей в Александрове очень понравился, а дороги - нет, самые разбитые. Нам сказали, что 31 декабря мэра города посадили за взятки. "Тебя посодют, а ты не воруй!" К 13-30 подъехали к Мытищам, потолкались в пробке. Вот мы и дома. Общий пробег составил 1334 км.

Послесловие.

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmh87SL0KIhj3QSo6y620IMLdkI6qxeO0QVb5rdpjuyWvLtiZjqgYKyPurkWJwclhWd7aQxp_mIp5887DHvXCOA28RtwrGd8c9NtjcLGwU5Ovfg4K8g8Io12CIidlDfjbbs841KR6Nbn_P/s800/IMG_8672_270x360.JPG

Понравилось. Очень. Большое спасибо организаторам, работникам музеев, местным жителям, которые знают и любят свои города. Глоток свободы и свежего воздуха. Заряд надолго. Обязательно вернемся.

А также отдельная благодарность создателям открытой и бесплатной карты OpenStreetMap и навигационной программы Navit.

С уважением, экипаж № 40.

четверг, 16 декабря 2010 г.

[bash] Move array elements

Here is the task:
    1. move all array elements to offset (index) 'off'. I.e. such, that first
       array element will be at index 'off'.
    2. make holes (sequences of unset elements) in the array by moving
       different array parts to different offsets (indexes) off1, off2,
       off3,.. .

This can't be done by

    arr=( [i]="${arr[@]}" )

because "${[@]}" expansion works not as it should: it treats assignment prefix
specially, and in such case works the same as "${[*]}".

So, we should use some workaround, like

    #!/bin/bash

    declare -a arr=(
        1
        2
        3
        4
        5
    )
    declare -i off=15
    declare -p arr
    b=${arr[0]}
    unset arr[0]
    arr=( [off]=$b "${arr[@]}" )
    declare -p arr

This can be done simplier, but i'm not sure which version of bash this
requires

    #!/bin/bash

    declare -a arr=(
        1
        2
        3
        4
        5
    )
    declare -i off=15
    declare -p arr
    arr=( [off]=${arr[0]} "${arr[@]:1}" )
    declare -p arr

Second task can be solved like this

    #!/bin/bash

    declare -a arr=(
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
    )
    declare -i off1=15 len1=2 i1=0
    declare -i off2=21 len2=3 i2=$((i1 + len1))
    declare -i off3=40 len3=4 i3=$((i2 + len2))
    declare -i off4=70        i4=$((i3 + len3))
    declare -p arr
    arr=( 
        [off1]=${arr[i1]}   "${arr[@]:i1 + 1: len1 - 1}"
        [off2]=${arr[i2]}   "${arr[@]:i2 + 1: len2 - 1}"
        [off3]=${arr[i3]}   "${arr[@]:i3 + 1: len3 - 1}"
        [off4]=${arr[i4]}   "${arr[@]:i4 + 1}"
    )
    declare -p arr

Note, that all existed holes in the array after such operations would be lost.

[bash] Write filenames to array.

Here is two general tasks:
    1. Assign strings (e.g. filenames) separated by '\0' from some input
       stream to corresponding array elements.
    2. Convert array into stream consisting from strings separated by '\0'.

I.e we have some bash script, which somewhere get such stream, e.g. by `find`
    
    find $root -wholename "*/$project" -prune -print0

and then we want to place this filenames into array elements. But there is a
problem: we can't use '\0' in IFS, so we can't split find's output stream
using bash word splitting expansion. And we can't use any other character to
separate filenames, because filename may contain any character.

One possible method (i don't know other, though it may be) is to transform
stream to bash code and then `eval` it.
   
But here is another problem: we can't simply escape string with double or
single quotes, because string may contain un-escaped double or single quotes
inside (find does not escape characters in filenames, and hence we assume,
that string contain un-escpaed characters, i.e written "as is"). For example

     a"'      b.txt

than after escaping with double quotes

    "a"'      b.txt"

or with single quotes

    'a"'      b.txt'

In both cases some part of string remain unescaped and not-matched quote
appears. (Write anothre example with command).


So, we can't escape string by commands like sed or awk, which perfom text
editing without string parsing. But we can use bash itself to parse and escape
string properly, and then output escaped result, like this

    eval "$(find $root -wholename "*/$project" -prune -print0 \
            | sort -z -s \
            | xargs -0 -x bash -c '
                            arr=( "$@" );
                            declare -p arr
                        ' escape_filename)"

(the last argument 'escape_filename' is used as $0. It may be any, but
required for correct work. For details see chapter 7.4.2 from 'info find')

Script for bash instance, invoked by xargs, may do some other operations with
strings, like

    j=15;
    eval "$(find $root -wholename "*/$project" -prune -print0 \
            | sort -z -s \
            | xargs -0 -x bash -c "
                            set -- \"\${@#$root/}\"
                            set -- \"\${@%$project}\"
                            arr2=( [$j]=\"\$1\" \"\${@:2}\" );
                            declare -p arr2
                        " escape_filename)"

Here we delete leading ($root) and trailing ($project) portions of filename
and then assign resulted set of strings to array starting at index $j.
Variables $root, $project and $j are substituted by main bash process before
executing pipeline.

If you use in this script array name you want assign to in main script, no
further editing of output will be needed.


Here is another example to what incorrect quoting may lead to. If we have in
input stream string like this

     a' rm -rf ~ '

then quote it with single quotes and add assignment (with sed, for example)

    var='a' rm -rf ~ ''

when it will be eval-ed it execute command

    rm -rf ~

Here is sample script

    #!/bin/bash

    str="a' ls ~ '"
    to_eval="$(echo "$str" | sed -e"s/^/var='/;s/$/'/")"
    eval "$to_eval"

четверг, 7 октября 2010 г.

[bash][part][draft]Return values from bash function

DISCLAIMER. English language used here only for compatibility (ASCII only), so any suggestions about my bad grammar (and not only it) will be greatly appreciated.

UPD. 2010.09.07
If we need to return several values from function, we can return through 
    - either setting some global variables,
    - or through pipe ("stdout").
    - or use both.
      
1. Global variables.

    If we do not want to hardcode these global variable names into script, we
    can pass their names as arguments and then use `eval` to assign values in
    child function,

        f() {
            ..
            eval "$1=\"a    b\""
        }
        f g1
        declare -p g1

    but in this case there is possible name conflicts between local function's
    variables and global ones. If local variable has the same name as global,
    it replaces it and we no longer able to set global variable in this
    child function.
    
2. Pipe.

    If we use pipe, we'll first gather all values from local child function's
    variables and send them into pipe, but then, in caller function, we'll
    parse pipe content to separate these values again and assign to proper
    variables. This is waste. Also, order, in which results will be outputed
    into pipe, should be fixed since cutting of result into separate values
    again and assigning them to proper variables into caller function assumes
    certain order.  And if this order sometimes accidently will change,
    returned values will be messed.

        f3() {
            local l1='a   b'
            local l2='   c d'
            echo "\"$l1\""
            echo "\"$l2\""
            return 0
        }
        eval "$(echo "$(f3)" | sed -e'1s/^/g1=/;2s/^/g2=/')"
        declare -p g1 g2

3. Mixed.

    But we can use both those techniques at the same time to eliminate all
    listed above problems each of them have.

    Names of global variables to be set should be passed through arguments,
    but in the pipe we'll send not raw values, but assignment statements with
    already expanded values, and in caller function we should simply execute
    them (assignment statements) in `eval`.  In such case we get:
        - no name conflicts, since we're no longer need global variables into
          child function: we need only their names to generate proper output,
          but values to them will be assigned into `eval` in caller function.
        - no parsing of pipe by caller, since child function knows names of
          variables to which caller want to assign values, and send the output
          with already prepared assignment statements (values in this
          assignment statements must be expanded in child function, since
          child's local variables will be deleted upon it returns).
        - Order of variables in child function's output may be any (except,
          very special cases, where one variable use the value of previous
          ones), since all assignments already written and no parsing
          required.

        f() {
            local l1='local   1'
            local l2='local 2'
            echo "g1=\"$l1\""
            echo "g2=\"$l2\""
            return 0
        }
        declare g1=''
        declare g2=''
        eval "$(f g1 g2)"
        declare -p g1 g2

Note1:
    - when function is called through command substitution, then it'll be
      executed in the subshell (unlike, when it's called normally, it is
      executed in the main shell), so you can _not_ set any parent shell's
      variable inside it. Thus, all parent shell's variable, which should be
      set must be written into pipe in assignment statements form.
    - you can not reinitialize array in parent shell using 'arr=( ${arr[@]} )'
      syntax, when writing to pipe, if array elements contain shell
      'metacharacters' (not IFS characters!). This is because in `eval`-ed
      script all values will be already expanded (yet before it'll be written
      into pipe) and will not contain any quotes, so they'll be broken into
      tokens (words and operators) by shell 'metacharacters' far before any
      expansions (including word splitting by 'IFS' characters) will take
      place.
    - the only way to reinitialize array in parent shell is to write either
      'arr[index]=value' for each element or `declare -p arr` into pipe.

Here is example.

# ./t.sh {{{

#!/bin/bash

f() {
    echo "SUBSH=$BASH_SUBSHELL" >/dev/tty
    arr1[3]='    g h  '
    arr2[3]='i    k  l'
    IFS=" "
    echo "arr1=( ${arr1[*]} )"
    echo "$(declare -p arr2)"
    return 0
}
declare -a arr1=(
    'a   b'
    'c      '
)
declare -a arr2=(
    '    d'
    '   e  f   '
)
eval "$(f)"
declare -p arr1 arr2

# }}}

Note2:

Even if we don't need any results from function, which write result into pipe,
following considerations should be looked at:
    - such function should always be invoked in subshell, even if we don't
      need its results. Otherwise, it'll be executed in the same shell as
      parent function and its actions with FDs may result in unexpected FD
      table state for parent.
    - FD=1 of such function should always be connected to something like
      '/dev/null'. Otherwise, it can write results somewhere you don't expect
      them for.

Example-1.

Here both functions f() and f2() return result through pipe, but function f()
does not need what f2() returns, so f2() is called (before f() moves pipe)
without subshell and without pipe connected. This results in the following
problems:
    - f2() closes 'fd_t_stdout' in the parent function's FD table, though f()
      thinks it's still open. This resluts in "9: Bad file descriptor error",
      when f() tries to move pipe and f() loses correct value for stdout.
      Hence, f() writes into pipe both output intended for 'stdout' and
      intended for 'pipe'.
    - f2() writes its output intended for 'pipe' into the _same_ pipe, as f()
      uses, because it was invoked before f() moves its own pipe.

# cat ./t.sh {{{
#!/bin/bash

declare -r -i fd_t_stdout=9
declare -r -i fd_t_pipe=7
declare -r log_file='./2.tmp'
declare -r read_file='/dev/zero'
[ -f "$log_file" ] && rm -f "$log_file"
exec 2>>"$log_file" <"$read_file"

f2() {
    echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to stdout"

    echo "f2(): restore pipe" >>"$log_file"
    exec 1>&$fd_t_pipe-
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to pipe"
}
f() {
    echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    f2
    echo "f(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): write to stdout"

    echo "f(): restore pipe" >>"$log_file"
    eval "exec 1>&$fd_t_pipe-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): write to pipe"
}
eval "exec $fd_t_stdout>&1"
v="$(f)"
echo "v: '$v'"
eval "exec $fd_t_stdout>&-"

# }}}
# ./t.sh >|./1.tmp {{{
f(): start: $$: 12681, BASH=12683, SUBSH=1
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w  FIFO    0,6        31967 pipe
t.sh    12683 root    2w   REG    8,7   43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12683 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): start: $$: 12681, BASH=12683, SUBSH=1
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w  FIFO    0,6        31967 pipe
t.sh    12683 root    2w   REG    8,7  438 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12683 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): move pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12683 root    2w   REG    8,7  805 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12683 root    7w  FIFO    0,6        31967 pipe
f2(): restore pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w  FIFO    0,6        31967 pipe
t.sh    12683 root    2w   REG    8,7 1175 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
f(): move pipe
./t.sh: line 36: 9: Bad file descriptor
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w  FIFO    0,6        31967 pipe
t.sh    12683 root    2w   REG    8,7 1492 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
f(): restore pipe
./t.sh: line 43: 7: Bad file descriptor
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12683 root    0r   CHR    1,5          920 /dev/zero
t.sh    12683 root    1w  FIFO    0,6        31967 pipe
t.sh    12683 root    2w   REG    8,7 1812 1590776 /home/sgf/new_tree/src/send_sms/2.tmp

# }}}
# cat ./1.tmp {{{
f2(): write to stdout
v: 'f2(): write to pipe
f(): write to stdout
f(): write to pipe'

# }}}

Example-2.

If f() moves its pipe before calling f2(), but calls it also without subshell,
this will not be better:
    - f2() will write its output intended for 'pipe' into 'stdout', since
      stdout instead of pipe will be opened at FD=1, when f2() starts.
    - f2() replaces FD='fd_t_pipe' value into parent function's FD table, so
      saved f()'s pipe will be replaced with 'stdout' (since 'stdout' will be
      at FD=1 in f2()). So, f() loses correct value for its pipe and writes
      into 'stdout' both output intended for pipe and intended for 'stdout'.

# cat ./t.sh {{{
#!/bin/bash

declare -r -i fd_t_stdout=9
declare -r -i fd_t_pipe=7
declare -r log_file='./2.tmp'
declare -r read_file='/dev/zero'
[ -f "$log_file" ] && rm -f "$log_file"
exec 2>>"$log_file" <"$read_file"

f2() {
    echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to stdout"

    echo "f2(): restore pipe" >>"$log_file"
    exec 1>&$fd_t_pipe-
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to pipe"
}
f() {
    echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    f2
    echo "f(): write to stdout"

    echo "f(): restore pipe" >>"$log_file"
    eval "exec 1>&$fd_t_pipe-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): write to pipe"
}
eval "exec $fd_t_stdout>&1"
v="$(f)"
echo "v: '$v'"
eval "exec $fd_t_stdout>&-"

# }}}
# ./t.sh >|./1.tmp {{{
f(): start: $$: 12713, BASH=12715, SUBSH=1
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w  FIFO    0,6        32160 pipe
t.sh    12715 root    2w   REG    8,7   43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12715 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f(): move pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12715 root    2w   REG    8,7  409 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12715 root    7w  FIFO    0,6        32160 pipe
t.sh    12715 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): start: $$: 12713, BASH=12715, SUBSH=1
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12715 root    2w   REG    8,7  893 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12715 root    7w  FIFO    0,6        32160 pipe
t.sh    12715 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): move pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12715 root    2w   REG    8,7 1349 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12715 root    7w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): restore pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w   REG    8,7   22 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12715 root    2w   REG    8,7 1752 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
f(): restore pipe
./t.sh: line 43: 7: Bad file descriptor
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12715 root    0r   CHR    1,5          920 /dev/zero
t.sh    12715 root    1w   REG    8,7   63 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12715 root    2w   REG    8,7 2105 1590776 /home/sgf/new_tree/src/send_sms/2.tmp

# }}}
# cat ./1.tmp {{{
f2(): write to stdout
f2(): write to pipe
f(): write to stdout
f(): write to pipe
v: ''

# }}}

Example-3.

And here is how this can be done correctly. Shortly, we should call f2() like

    ( f2 >/dev/null )

In this case, f2() result will be discarded (but we do not need it, right?), but
f() will output its result as expected.

# cat ./t.sh {{{
#!/bin/bash

declare -r -i fd_t_stdout=9
declare -r -i fd_t_pipe=7
declare -r log_file='./2.tmp'
declare -r read_file='/dev/zero'
[ -f "$log_file" ] && rm -f "$log_file"
exec 2>>"$log_file" <"$read_file"

f2() {
    echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to stdout"

    echo "f2(): restore pipe" >>"$log_file"
    exec 1>&$fd_t_pipe-
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f2(): write to pipe"
}
f() {
    echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): move pipe" >>"$log_file"
    eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    ( f2 >/dev/null )
    echo "f(): write to stdout"

    echo "f(): restore pipe" >>"$log_file"
    eval "exec 1>&$fd_t_pipe-"
    lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file"
    read -rsn1

    echo "f(): write to pipe"
}
eval "exec $fd_t_stdout>&1"
v="$(f)"
echo "v: '$v'"
eval "exec $fd_t_stdout>&-"

# }}}
# ./t.sh >|./1.tmp {{{
f(): start: $$: 12777, BASH=12779, SUBSH=1
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12779 root    0r   CHR    1,5          920 /dev/zero
t.sh    12779 root    1w  FIFO    0,6        32627 pipe
t.sh    12779 root    2w   REG    8,7   43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12779 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f(): move pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12779 root    0r   CHR    1,5          920 /dev/zero
t.sh    12779 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12779 root    2w   REG    8,7  409 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12779 root    7w  FIFO    0,6        32627 pipe
t.sh    12779 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): start: $$: 12777, BASH=12784, SUBSH=2
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12784 root    0r   CHR    1,5          920 /dev/zero
t.sh    12784 root    1w   CHR    1,3          898 /dev/null
t.sh    12784 root    2w   REG    8,7  893 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12784 root    7w  FIFO    0,6        32627 pipe
t.sh    12784 root    9w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12784 root   10w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): move pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12784 root    0r   CHR    1,5          920 /dev/zero
t.sh    12784 root    1w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
t.sh    12784 root    2w   REG    8,7 1410 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12784 root    7w   CHR    1,3          898 /dev/null
t.sh    12784 root   10w   REG    8,7    0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f2(): restore pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12784 root    0r   CHR    1,5          920 /dev/zero
t.sh    12784 root    1w   CHR    1,3          898 /dev/null
t.sh    12784 root    2w   REG    8,7 1874 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12784 root   10w   REG    8,7   22 1121662 /home/sgf/new_tree/src/send_sms/1.tmp
f(): restore pipe
COMMAND   PID USER   FD   TYPE DEVICE SIZE    NODE NAME
t.sh    12779 root    0r   CHR    1,5          920 /dev/zero
t.sh    12779 root    1w  FIFO    0,6        32627 pipe
t.sh    12779 root    2w   REG    8,7 2248 1590776 /home/sgf/new_tree/src/send_sms/2.tmp
t.sh    12779 root    9w   REG    8,7   43 1121662 /home/sgf/new_tree/src/send_sms/1.tmp

# }}}
# cat ./1.tmp {{{
f2(): write to stdout
f(): write to stdout
v: 'f(): write to pipe'

# }}}