pozíció elmentése kilépéskor

Sziasztok!

Egy bash script kapcsán azt szeretném elérni, hogy kilépéskor a script a POS változó értéként legyen képes elmenteni, és ami annál is fontosabb lenne, hogy pontosan.
Egy JSON adatfolyamot olvas egyébként a script, egy sor, egy üzenet (bár a rekord szeparátor karakter más), és az üzenet egy mezője tartalmazza a POS mezőt, ami alapján később lehetne onnan folytatni az adatfolyam lekérését.
Sajnos a jelenlegi tapasztalat az, hogy a LINE változó még csak-csak tartalmaz adatot, de messze nem azt, ami éppen utolsóként kiíródik a STDOUT-ra a konzolban, a POS vátozó értéke pedig üres, amikor a __cleanup ráhívódik.
Az eredeti koncepció az volt, hogy az üzeneteket alapból nem parse-olom fel, csak kilépéskor olvastatom ki a POS aktuális értékét, és azt elteszem, hogy a következő induláskor onnan folytassa az adatok lekérését. Ez nem igazán jött össze, így most tesztképpen minden üzenetet felparse-olok, aminek hála a script jóval lassabb lett.
Most pedig valójában azt tapasztalom, hogy ha pélédául ^C-t nyomok, akkor a __cleanup kiírja a LINE változó tartalmát, majd még bőven jön ki adat a scriptből STDOUT-on, és az amit a __cleanup korábban kiírt, az messze nem az utolsó üzenet, ami kikerül a konzolra mire a script végül befejezi a futást.
Hogy tudnám ezt jól megoldani?

#!/bin/bash

LINE=
POS=

trap '__cleanup' HUP INT TRAP TERM

__cleanup() {
	echo -e "\nhello! EE${POS}:::${LINE}EE\n"
	exit 1
}

while IFS="\0x1E" read -r LINE; do POS=$(jq '.__POS' <<<"${LINE}") ; echo "${LINE}" ; done < <( Innen olvasunk, ez a parancs írja ki a json-t)

Hozzászólások

Mindenkepp bash-ben akarod? Ennel random scriptnyelv lehet, hogy jobb valasztas, persze attol fuggoen, hogy mi a vegcel. Extrem esetben, ha minden egyes pos-t kiirsz lemezre, es legkozelebb onnan folytatod, akkor nem lesz ismetles, de kinyirja a lemezt. Ha jol ertem, akkor nem baj, ha korabbiakat is - korlatozottan - ujra feldolgozol, szoval lehetne mondjuk kilepeskor, inaktivitaskor, meg mondjuk 5 percenkent menteni.

A strange game. The only winning move is not to play. How about a nice game of chess?

Igen, a bash-t szeretnék/kell használnom erre.
Igen, én is gondoltam a diszkre írásra, de nem túl elegáns megoldás, így el is vetettem.
De igen, az is szempont, hogy ne duplikáljon és ne is hagyjon ki semmit, hanem onnan folytassa, ahonnan korábban abbahagyta.

A legegyszerűbb megoldás, ha a POS értékéket a visszatérési értékben mented el, pl. exit $POS. Bár ez sem annyira tuti, mivel ha egy másik program is futott közben, akkor az felülírja ezt.

A másik lehetséges út, ha a /tmp/-be hegesztesz valami átmeneti fájlt, és abba írod bele, hogy hol tartott a pozíció, pl. echo $POS >> /tmp/blabla. Ezzel nem a diszkre írsz, hanem egy ramdrive-ra, a tartalma rebootkor elveszik, és nem szemeteled vele a rendszert. Igazából erre lett kitalálva a ~/.cache/ mappa is, oda is beteheted, az már szemetel, de egy erre fenntartott helyre teszi, amit később könnyű visszakeresni, és akár kézileg is eltávolítani.

Windows 95/98: 32 bit extension and a graphical shell for a 16 bit patch to an 8 bit operating system originally coded for a 4 bit microprocessor, written by a 2 bit company that can't stand 1 bit of competition.”

Sajnos a pos értéke egy hosszú string, az exit meg csak integer értéket fogad. De a /tmp-s megoldás életszerű lehet.
Mivel nem akarom egyenként az összes üzenetet parse-olni (nagy erőforrás pocsékolás lenne szerintem) a legjobb lenne talán, ha a teljes üzenetet kiírnám (minden egyes üzenetre a /tmp alá) (természetesen ez is erőforrás pocsékolás), és kilépéskor az ott levő egy darab üzenetet parse-olnám fel és tenném el máshová.

Így most sokkal jobb. Per pillanat jobbat nem tudtam kitalálni, a /run tmpfs felett van, és így az user-specifikus könyvárban hozzáférési problémák sincsenek.

#!/bin/bash

LINE=
POS=
USERID=$(id -u)

trap '__cleanup' HUP INT TRAP TERM

__cleanup() {
	LINE=$(cat < /run/user/${USERID}/eeee )
	POS=$(jq '.__POS' <<<"${LINE}")
	echo -e "\nhello! EE${POS}:::${LINE}EE\n"
	rm /run/user/${USERID}/eeee
	exit 1
}

while IFS="\0x1E" read -r LINE; do tee /run/user/${USERID}/eeee <<<"${LINE}" ; done < <( Innen olvasunk, ez a parancs írja ki a json kimenetet)

A /run alá is beteheted, az is tmpfs ramdrive, csak akkor az annyi bonyodalommal járhat, hogy rendszergizdai jogosultság kellhet hozzá. A /tmp-s megoldáshoz nem kell.

Windows 95/98: 32 bit extension and a graphical shell for a 16 bit patch to an 8 bit operating system originally coded for a 4 bit microprocessor, written by a 2 bit company that can't stand 1 bit of competition.”

Elvileg van user specifikus könyvtár a /run alatt, ahová van írási jogod. A /tmp pedig nem garantált, hogy tmpfs-en van. Pl. nálam sincs. Jó igaz, a /run esetén sincs garancia, de a systemd mellett az szokott lenni.

Igaz, igaz, ezt el is felejtettem, hogy a /var/-ban van egy ./user/$(id -u)/ mappa, ahová a usernek van jogosultsága írni, a XDG_RUNTIME_DIR változóban szokott lenni tárolva. Különben sorra sokkoltok engem a kollégával, hogy a /tmp nem biztosan tmpfs, oké, de hogy a /var/-t melyik állatfajta csatolná fel perzisztensre, systemd-től is függetlenül? El nem tudok képzelni semmi előnyét. Nem systemd-sek közül a Gentoo-ra, Void-ra, Artix-ra emlékszem, és a /tmp, és /run azokon is default tmpfs volt.

Jó, nagyon szélsőséges esetben egy olyan muzeális gépen, vagy beágyazott eszközön, ahol nagyon-nagyon szűkös a RAM, ott lehet elvi síkon értelme, de az már nagyon elkeseredés kategória, mikor ilyenen kell spórolni, hogy tmpfs se legyen, ilyen szélsőséges körülmények között már eleve egy alap Linux rendszer használatával is problémák szoktak lenni.

Windows 95/98: 32 bit extension and a graphical shell for a 16 bit patch to an 8 bit operating system originally coded for a 4 bit microprocessor, written by a 2 bit company that can't stand 1 bit of competition.”

Tisztában vagyok vele, hogy ez most itt Linux, de félve jegyzem meg, hogy hagyományos *X endszereken sem a /tmp sem a /var (ez utóbbi különösen nem) RAM-diszk. (Pl. FreeBSD alatt a /var/db alatti dolgokat nem nagyon szeretném rebootonként elhagyni, de pl. a vi recovery könyvtára is hagyományosan a /var alatt van - és ez még csak nem is FreeBSD specifikus.)

DHCP kliensek lízing fájlja - felejthető

locate adatbázis - fene tudja, ha nem használja az ember, akkor nem gond

zoneinfó fájl - ez már kellemetlen lehet,  visszavonom, összekevertem a localtime-mal

Ami nekem fájna, az a ports, pkg, portsnap, esetleg a freebsd-update könyvtár - bár ebből a ports asszem az igazán kellemetlen: milyen opciókat állítottam be amikor ports-ból fordítottam valamit. Ha ilyened nincs a gépen, akkor kb OS frissítéseket vagy csomagok frissítését lassítod a takarítással.

Még a /var/db/zfsd amin gondolkodni kell (nyilván ZFS + zfsd használatakor). Man zfsd, a végén van egy fél mondat arról, hogy hogyan használja az ott levő fájlt.

Mondjuk az érdekes, hogy az nem gond, hogy a Json előállítása előről kezdődik, csak az gond, hogy a feldolgozás előről kezdődik. Ennyivel gyorsabb az előállítóprogram az elemzőnél?

Ez csak poc kód. Elvileg van lehetőség arra, hogy alapból egy bizonyos pozíció utáni entry-ket kérj el a json-t előállító programtól. Azt a részt még nem tettem bele.

Én olyat csináltam egyszer, hogy a bash script a beállításait saját magában mentette el és utána az új beállításokkal újraindult.

Ha maga a bash script módosítható, megkeresem a részleteket.

Szerkesztve: 2022. 06. 20., h – 16:29

A signalok úgy működnek, hogy akármit csinál éppen a processz interruptként meg van szakítva, arra van rátéve egy új stack frame, és erről van futtatva a signal.

Mi ezzel a probléma? Az, hogy bármilyen nem atomi művelet kellős közepén is megszakadhat a főprogram futása. Így a kimeneten megjelenhet félkész output is, illetve a POS változó is lehet két szabályos állapot között - feltéve, hogy nem atomi módon ugrál az értéke a szabályos állapotok között (pl 32 biten van tárolva, és egyetlen művelettel van kiírva az új értéke X86-on). Ezért általános szabály minden programnyelven, hogy a signal handlerben nem csinálunk semmit a program adatstruktúráival, hanem csak beállítunk egy flag-et, hogy a főprogram értesüljön a signalról.

Azt, hogy a bash hogyan kezeli a signal szinkronizációját, arról fogalmam sincsen, de a biztonság kedvéért én inkább az említett logika szerint csinálnám meg, valahogy így - pszeudókóddal leírva, mert a bash-hez nem értek:

var volatile boolean exitRequest=false;

signalhandler= function(){ exitRequest=true; return; }


while(! EOF && !exitRequest)
{
 processLine();
}
if(!EOF)
{
  save POS and other resume information
}else
{
  clear POS and other resume information to mark clean startup
}
exit

A jelenség az volt, hogy:

LINE1
LINE2
LINE3
LINE4
LINE5
LINE6
LINE7
LINE8
^C
hello! EELINE8-POS:::LINE8EE
LINE9
LINE10
LINE11
LINE12
LINE13
LINE14

De az most látszólag megoldotta, hogy minden egyes üzenetet leírok fájlba és onnan visszaolvasom, ha kilépek.

Ha elég hogy múkod, akkor nem kell túldimenzionálni, de ha biztosra akarsz menni, hogy _soha_ ne hibázzon, akkor fontold meg amit írtam!

Az aszinkron bejövő signal ha pont rosszkor érkezik, akkor előfordulhat, hogy éppen még nem írtad ki a sort, de már éppen frissítetted a POS értéket, vagy fordítva. Várhatóan ritkán, de előfordulhat, ha nincsen garancia arra, hogy a két művelet atomi módon lesz végrehajtva.

Kipróbáltam bash szkriptre átírva a pszeudó kódot, működik:

#!/bin/bash

SIGN=0
trap '__cleanup' HUP INT TRAP TERM

__cleanup() {
    SIGN=1
}

echo CTRL-c for graceful shutdown of loop
while [[ $SIGN != 1 ]]
do
	sleep 1
	echo processed one line
done
echo Graceful shutdown

Igen, a kezdeti verzióhoz képest már én is átrendeztem a scriptet. Összességében a jq-t is a __cleanup-ba mozgattam, de valóban előfordult még olyan, hogy a tee kicsit "félbevágódott", de ezzel a leállási jelzéssel úgy tűnik valóban jobban működik, illetve jóval kisebb az ilyen probléma eshetősége. Így tehát átírtam a scriptemet a javaslatod szerint.