[Megoldva] Bash rekurzió

Ugyan megoldottam a gondomat, de érdekel az, hogy miért nem működött egy korábbi script változatom. A picike scriptem annyit csinál, hogy ssh-n keresztül felmásol a router-em RAM-jába file-okat. Az rsync nem jó, mert nincs elég hely ahhoz, hogy a router-re feltegyem az rsync-et. A lényeg, hogy szimbolikus linket is kell másolni. A program rekurzívan bejárja az alkönyvtárakat. Ez a változat működik:
#!/bin/bash

BASE='/tmp'

copy() {
    unset list
    i=0
    while read -r; do
        list[i++]="$REPLY"
    done < <(ls -1)
    lenlist=$i
    i=0
    while [ $i -lt $lenlist ]; do
        file="${list[i++]}"
        len=${#file}
        if cd "$file" &>/dev/null; then
            ssh locsemege@router "
                cd \"$BASE/$dir\"
                mkdir \"$file\"
            "
            dir+="/$file"
            (copy)
            dir="${dir%/*}"
            cd ..
            continue
        fi
        if [ -h "$file" ]; then
            ssh locsemege@router "
                cd \"$BASE/$dir\"
                ln -s \"`readlink $file`\" \"$file\"
                "
            continue
        fi
        if [ "${file:len-1}" != '~' ]; then
            scp -C "$file" locsemege@router:"$BASE/$dir/$file"
        fi
    done
}

ssh locsemege@router 'rm -Rf /tmp/www; mkdir /tmp/www'
dir='www'
cd /home/locsemege/router/www
copy

A gondom az, hogy amennyiben nem másoltam az aktuális file listát egy tömbbe, hanem akár pipe-oltam

ls -1 | while read -r; do
...
done

akár process helyettesítést használtam

while read -r; do
...
done < <(ls -1)

akár a file listát irányítottam a ciklusba


list="`ls -1`"
while read -r; do
...
done <<<"$list"

az történt, hogy amikor a legmélyebb ciklusból visszatért a copy függvény, az őt hívó ciklusból is azonnal kilépett. Debugoltam, az utóbbi esetben a file lista nem sérült, a visszatérés után megvolt. Pedig a rekurzív hívást gömbölyded zárójelbe írva subshellben teszem: (copy). A változók ennek megfelelően nem is íródnak felül, a jelenlegi programverzió működik.

Az a gyanúm, az átírányításkor lehet valami olyasmi, hogy a rekurzión belül a file descriptorok esetében nincs mentés, vagy nem tudom, lényeg, hogy ha a belső read visszatér EOF-fal, a külső, hívó ciklus is azonnal visszatér, tehát hiába vannak még a listának feldolgozatlan elemei a read EOF-fal tér vissza. Pedig nem szeretném. Azt nem mondom, hogy nem kéne, mert nyilván én nem tudok valamit.

Az utolsó feltételben azért vizsgálom a filenév utolsó karaktereként a tilde jelet, mert az editor által létrehozott backup file-okat nem szeretném a router-re másolni.

Miért nem működtek a triviális programváltozataim, miért kellett egy tömbből feldolgoznom a listaelemeket rekurzív hívás esetén, miért nem működött az átirányítás?

Megoldás.

Hozzászólások

hol van Zahy, hogy megmondja a frankót?

(Mellesleg meg mernék esküdni, hogy ugyanez volt már itt is k.anyázási téma . baromira ismerős, noha fejből nem emlékszem az ssh ilyetén hülyeségére. Mondjuk mivel én elég sokszor úgy debuggolok, hogy a fizikailag izgalmas parancs elé írok egy echo-t, elég hamar ki kellett volna derülnie, hisz az echo nem zabálja fel az stdin-t.)

=====
tl;dr
Egy-két mondatban leírnátok, hogy lehet ellopni egy bitcoin-t?

Talán épp én nyafoghattam ezen, csak hát nem tanulok a saját hibámból. :( Az ffmpeg csinálta ugyanezt. Debugoltam én, annyit láttam, hogy nem íródik felül a lista, meg annyit is, hogy nem a continue a bűnös, mert átírtam if ... ; then ... elif ...; then ... fi alakúra is, ugyanez volt a baj. Az ssh-ra meg nem gyanakodtam, pedig logikus, hiszen ssh-val lehet távolban programot futtatni, amit innen lehet pipe-ról etetni. Másra gondoltam, így az általad említett echo-t, ami kommentet csinált volna az ssh-ból, nem alkalmaztam. Arra tippeltem, a rekurzió miatt összekuszálódnak a file leírók, vagy nem tudom.

Azért gyakorlati haszna is volt ennek a topic-nak. Valamiért a kézenfekvő tar nem jutott eszembe, de javasolták, s ezen ajánlás alapján lett egy 3 soros, lényegesen gyorsabban futó script belőle. :)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

tar-ral nem lett volna egyszerűbb (az szokott lenni a routereken)?

cd /home/locsemege/router/www
tar czf - * | ssh locsemege@router 'rm -Rf /tmp/www; mkdir /tmp/www;cd /tmp/www;tar xvzf -'

De, csak nem jutott eszembe! :) Na jó, ez a gyakorlati része. Meg az, hogy a scriptem is működik. Viszont érdekel továbbra is, hogy miért nem működött a rekurzió, ha a rekurzív hívás abban a ciklusban van, amelyik a beleírányított listát dolgozza épp fel, miközben a hívás subshellben történik.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Köszönöm a kézenfekvő tippet, ha már voltam olyan ügyetlen, hogy a nyilvánvaló nem jutott eszembe. :D Ez lett belőle:

#!/bin/bash

cd /home/locsemege/router
tar --exclude-backups -czf - www | ssh locsemege@router 'rm -Rf /tmp/www; cd /tmp; tar -xzf -'
cd -

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE


#!/bin/bash

i=1

copy() {
   echo "ok - $i"
   i=$[$i+1]
   copy                 # nézd meg zárójelesen, (copy)-val is
}

copy

Nézd meg mindkét esetben egy másik terminálban, hány procesz fog keletkezni. Biztosan zárójellel akartad?

Amit te írtál, így egy fork bomba, már feltéve, hogy ott a zárójel. Ha nincs ott a zárójel, úgy csak egy sima memory leak. :) Mert néha vissza is kellene térni. Nekem épp azért kellett a subshell, hogy ne az éppen aktuális változóim íródjanak felül, hanem egy új memóriaterületen legyen foglalva az újabb copy hívás változóterülete. Ez épp azért kell, hogy visszatérés után a hívó copy ott folytathassa, ahol abbahagyta.

A scriptem működik jól. Nem ezzel van a bajom, hanem azzal, hogy amennyiben átirányításon belül - legyen az pipe, process substitution, vagy here string - történik a rekurzív hívás, úgy visszatérés után a hívó függvény is visszatér, nem dolgozza fel a lista még fennmaradó elemeit. Tehát vagy ugyanazt a memóriát használják átirányításkor, vagy a subshellben nem keletkezik valamiféle másodpéldány abból a változóból, amely a read EOF státuszát tárolja, vagy valami ilyesmi. Ehhez kellene például egy hozzáértő Zahy. ;)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Már a hülyeségemen kívül? :) Semmi. Igazad van, akkor még a subshell is elhagyható lenne, ha jól látom, ez javítana a memóriahasználaton és a futásidőn is. Nem, mintha ebben a felhasználásban ezek annyira hatalmasak lettek volna.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Különben valamit nyilván csúnyán benézek, mert ez meg működik:

#!/bin/bash

copy() {
    echo '>'
    while read -r; do
        if cd "$REPLY" &>/dev/null; then
            pwd
            (copy)
            cd ..
            continue
        fi
        echo "$REPLY"
    done < <(ls -1)
    echo '<'
}

cd /home/locsemege/router/www
copy

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Anélkül, hogy pontosan tudnám, mi a baj, van egy tippem. Csak az, amit sokadjára szívok meg. A ciklusmagban valami - itt vélhetően az ssh - megeszi a standard inputot, így aztán mire a read-re kerül a sor, már nincs feldolgozandó elem.

Azt hiszem, egy ssh -n megoldotta volna a gondomat.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Igen, ez volt a baj, teszteltem. A ciklusmagban az ssh felzabálta az stdin-t, amelyet én valójában a while-ban lévő read-nek szántam. A megoldás az ssh parancs -n kapcsolója:

     -n      Redirects stdin from /dev/null (actually, prevents reading from
             stdin).  This must be used when ssh is run in the background.  A
             common trick is to use this to run X11 programs on a remote
             machine.  For example, ssh -n shadows.cs.hut.fi emacs & will
             start an emacs on shadows.cs.hut.fi, and the X11 connection will
             be automatically forwarded over an encrypted channel.  The ssh
             program will be put in the background.  (This does not work if
             ssh needs to ask for a password or passphrase; see also the -f
             option.)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Hali, sajnos most épp nem vagyok hibakeresős üzemmódban, de elsőre az 'unset list' helyett 'local list; declare -a list'-et próbálnék