AWK és a getline

Csak azért írom, más ne tépje ki a haját. Jelenség: GNU awk-ban a getline beolvas egy sort, de további sorokat nem hajlandó. A megoldás itt olvasható. Tehát:

cmd = "ezt hajtsd végre shellben";
cmd | getline változó;
close(cmd); # <- ez kell!

Igen, a close() argumentumában vissza kell idézni a teljes shell parancsot pontosan, onnan fogja azonosítani, melyik az a pipe, amelyet le kell zárnia.

Hozzászólások

Nálam még ez is benne van a man-ban:

       If using a pipe, coprocess, or
       socket to getline, or from print or printf within a loop, you must use
       close() to create new instances of the command or socket.  AWK does not
       automatically close pipes, sockets, or coprocesses when they return
       EOF.

Szóval close nélkül EOF után a további getline hibát ad vissza, de ezt le kell kezelni; ha pedig close volt, akkor újra elindítja a programot.

Addig nekem sem tűnt fel a close() hiánya, amíg egyetlen sort olvastam be, mert az sikerült. Feldolgozta majd kilépett az awk programom, így észre sem vettem. Viszont amikor a BEGIN {} szekcióban egy ciklusból hívtam azt a függvényemet, amelyikben volt egy cmd | getline var szerkezet, akkor a függvényem első hívása jól működött, az összes többi nem akadt el, visszatért ugyan, csak a var változóba már nem olvasta be, amit a cmd a pipe-ba tolt. Azt nem tudom, hogy a cmd egyáltalán lefutott-e, de talán. Nem futott le, emlékszem, nagyon gyorsan futott, a socat-nek viszont van egy time out-ja, szóval már a cmd-t sem futtatta.

Próbáld ciklusba írni, máris nem lesz olyan jó a helyzet close() nélkül!

Szerk.: külön tisztességtelen dolog az élettől, hogy egyszerű példaprogaramon nem jön ez elő. Legyen annyi elég, hogy van, amikor előjön, s az általam linkelt dokumentum szerint is kell a close().

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

Ténylegesen ebben a környezetben jött elő, ezt hívogattam egy másik függvényből ciklusban:

function command(a,
                    i, s, cmd) {
    s = "L";
    for (i=1; i<=8; i++) {
        s = s "\\x" sprintf("%02x", a[i]);
    }
    cmd = "echo -ne '" s "' | socat -t 0.2 - 'file:" port ",nonblock,rawer,b115200' | od -An -v -tx1";
    cmd | getline s;
    close(cmd);
    return s;
}

Az s változót csak újrahasznosítottam. Az a[ ] tömbben számok vannak, amely soros portra megy, illetve onnan jön válasz. A close() hívása nélkül a bejövő adat nem került az s változóba, iiletve így nem tért azzal vissza.

A hívó függvényben:

    while (1) {
        split(command(a), b);
        t = systime();
        printf("%s %s %s %s %s %s %s %s\n", t, b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
#       system("sleep 1");
    }

Az a[ ] inicializálva van.

Szerk.: A válaszban szóközök választanak el két karakteres hexadecimális számokat. Nem sok, néhány byte. Ezért jó így a split().

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

Amit linkeltem, abban is ez áll:

close(filename)

or

 

close(command)

The argument filename or command can be any expression. Its value must exactly equal the string that was used to open the file or start the command--for example, if you open a pipe with this:

 

"sort -r names" | getline foo

then you must close it with this:

 

close("sort -r names")

Once this function call is executed, the next getline from that file or command will reopen the file or rerun the command.

A két kiemelés tőlem.

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

Amúgy tiszta hülye vagyok, elfelejtettem shellben programozni. Azt akarom, hogy legyen timestamp is. Ezt írtam:

    cmd = "echo -ne '" s "' | socat -t 0.2 - 'file:" port;
    cmd = cmd  ",nonblock,rawer,b115200' | { date '+%s.%N'; od -An -v -tx1; }";

Aztán meg ezt:

    cmd = "echo -ne '" s "' | socat -t 0.2 - 'file:" port;
    cmd = cmd  ",nonblock,rawer,b115200' | { date '+%s.%N' </dev/null; od -An -v -tx1; }";

De mintha az od nem kapná meg a névtelen függvénybe pipe-oltakat. Legfeljebb átveszem egy bash read-del.

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

No akkor ugorgyunk neki megint.

a) a példámat ha jobban megnézed, eleve ciklusban hívtam a cmd | getline formát

b) felnyitottam a bibliát(*), ez áll benne szó szerint:

It is also possible to pipe the output of another command directly into getline. Eg: the statement

while ( "who" | getline )

n++

executes the Unix program who (only once) and pipes its output into getline. The output of who is a list of users logged in. Each iteration of the while loop reads one more line from this list and increments the variable n ..."

c) ha minden getline használat uitán mondasz egy close(cmd) -t, akkor új és új processzeket fog kreálni. Ha ezzel szemben nincs close, akkor az eredeti processz fut és onnan olvas a getline. Amely egyébként EOF esetén 0-t ad vissza, később viszont -1 -et, ami viszont TRUE. Sőt. nemlétező fájlból átirányításkor vagy hibás parancs kimenete esetén is.  Erről is ír a biblia, kb ezt:

"Ez a forma hibás:

while ( getline <"file" )

helyette ezt kell írni:

while ( getline < "file" > 0 )

- ugyanis az elsőnél nemlétező fájl esetén jó kis végtelen ciklust kapsz." Fenti példában az a szép, hogy a kisebb jel átirányítás, a nagyobb viszont a relációs operátor - azaz hogy csak addig fusson a while, amíg a getline el nem éri a fájl végét

 

Azaz tartom magam ahhoz, hogy PEBKAC, és nem gawk hiba. (Én egyébként ezért is tartok (most már) 4-féle awk-t a FreeBSD-men: the one true awk (nawk), gawk, mawk és most már goawk is. Nyilván akkor szívás van, ha építesz a gawk speciális tudására.)

 

 

(*) The AWK book by A & W & K

Azt egy pillanatig sem állítottam, hogy ez awk bugos. Csak annyi történt, hogy nem ismertem a close() használatát awk-ban. Le kell zárjam, mert újabb socat-okat, od-okat és date-eket, no meg echo-kat akarok futtatni.

Egyébként van még egy hely, ahol megszívtam ezt. A printf() >file esetében. Kiírtam a stringemet, de a következő körben appendelte, mert nem zártam le a file-t. Most lezárom, így kénytelen legközelebb újranyitni, s felülírni a korábbit.

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