bash capture both stdout and stderr without named pipe or temporary file

Körülbelül a blogbejegyzés címével megegyező kulcsszavakkal próbaltam megoldást találni arra a triviális IPC programozási problémára, ami a shell tipusú szkriptnyelveken kívül minden nyelvben egyszerű, straight-forward megvalósítható, nem kell hozzá mágia, trükk.
bash-ben még kell.
Remélem, hogy csak még ilyen a helyzet, és egyszer fejlesztenek bele olyat amivel ez megoldható.
különben van bizodalmam a jó irányú fejlődése iránt. pl. nagyon örültem, amikor belekerült a "<>" redirection, a /dev/tcp álfájl, a "caller" és "coproc" parancs.

Tehát egyre keresgélek a megoldások közt, de csak-csak azt látom, hogy "What you cannot do is capture stdout in one variable, and stderr in another, using only FD redirections".
Persze meg lehet trükközni a processz kimeneteinek prefixálásával majd a prefixek alapján való külön válozókba válogatásával. De ez nekem nem igazán jött be.
Aztán találtam egy ötletes megoldást, amivel elkezdtem játszani.
htamas megoldása magasabb programozási szinten operálva hoz letre olyat, amit a bash nem nyújt a szkriptjei számára: kétvégű pipe - mint a pipe(2) rendszerhívás.
Lehet bash-ben file descriptor-okat másolni, lezárni, mind fájlhoz, mind anonymous pipe-hoz, csakhogy épp új pipe-ot nem lehet kérni. Pontosabban olyan új pipe-ot, aminek mindkét vége az irányító scriptből érhető el.
Viszont a jól ismert "|" szintaxissal létrejön egy pipe, csakhogy azon a kötelezően megadandó gyerekprocesszek osztoznak.
Semmi baj, lopjuk el :)

FD másolás:
- dup(2) módra: exec {newfd}>&2 # új fd száma a $newfd változóban
- dup2(2) módra: exec 1>&2 # újonc bashiszták is ismerik
FD lezárás:
- exec 1>&-
- exec {fd}>&- # olvasatomban azonos a következővel
- eval "exec $fd>&-"

Be is csomagoltam ezen kódot egy pipe() nevű bash utility függvénybe, amit
"pipe reader writer" parancssorral hívva egyből kapunk egy használható pipe-ot, és a két végének FD-jeit a $reader és $writer változókban.
Teszteltem, teszteltem, de mintha nem lenne determinisztikus a működése.
Mondom, biztos én vétettem el valamit, amikor külön fgv-be raktam a pipe lezárást, vagy amikor kicseréltem a tail-t a kevésbé költségesnek vélt "sleep inf"-re.
De nem. Egyszer tudok olvasni a $reader-ből, egyszer nem.
Nosza, debuggoljunk! észrevettem, ha az exec elé tettem debug parancsokat, akkor (szinte mindig) jól működött az algoritmus - mi az? Heisenbug?
Nem. Race condition.
Ugyanis "sleep inf | sleep inf &" sorban a bash előbb forkol ki és azután kapcsolja össze egy pipe-on keresztül az egyik processz stdout-ját a másik stdin-jével. Tehát a FD-k módosítása már a jobs, exec és az utána lévő parancsokkal párhuzamos. Így előfordulhat, hogy az exec-cel nem az új FD-ket másolom le a saját processzemnek, hanem a szintén saját stdin-t és stdout-ot.

A lent közölt módosításom ezt a race condition-t oldja fel egy ciklussal.
További szépítés lenne, ha találnék egy soha véget nem érő beépített parancsot a sleep helyére, amivel elkerülhető hogy ennek a parancsnak a hiánya elrontsa a pipe() függvényt.
Javaslom a bash developer csapatnak a sleep builtin implementálását!

Ez az eset is alátámasztja azt a véleményemet, hogy a gyenge hardware serkenti az innovációt:
ezt a race condition-t nehezebben fedeztem volna fel egy gyorsabb gépen, ahol hamarabb, az exec előtt lefutott volna a fork.


pipe()
{
	local pid1 pid2 myin myout lnk0 lnk1 wtr rdr

	myin=`readlink /proc/self/fd/0`
	myout=`readlink /proc/self/fd/1`

	sleep inf | sleep inf &
	pid2=$!
	pid1=$(jobs -p %+)

	while true
	do
        	lnk0=`readlink /proc/$pid2/fd/0`
	        lnk1=`readlink /proc/$pid1/fd/1`
	        if [ "$lnk0" != "$myin" -a "$lnk1" != "$myout" ]
	        then
	                break
	        fi
	done

	exec {wtr}>/proc/$pid1/fd/1 {rdr}</proc/$pid2/fd/0

	disown $pid2
	kill $pid1 $pid2

	eval $1=$rdr
	eval $2=$wtr
}

close()
{
	local fd
	for fd in "$@"
	do
		eval "exec $fd>&-"
	done
}

használat:


pipe reader writer
output=`program 2>&$writer`
close $writer
errors=`cat <&$reader`
close $reader

---
Update 16/08/01 - kattinthatalan link jav.

Hozzászólások