Mám shell skript to je čtení ze standardního vstupu . Ve výjimečných případech nebude nikdo připraven poskytnout vstup a skript musí vyčerpat časový limit . V případě vypršení časového limitu musí skript provést nějaký čistící kód. Jaký je nejlepší způsob, jak to udělat?
Tento skript musí být velmi přenosný , včetně unixových systémů 20. století bez kompilátoru C a vestavěných zařízení s busyboxem, takže na Perl, bash, jakýkoli kompilovaný jazyk a dokonce i plný POSIX.2 se nelze spolehnout. Konkrétně $PPID
, read -t
a pasti dokonale vyhovující POSIX nejsou k dispozici. Zápis do dočasného souboru je také vyloučen; skript se může spustit, i když jsou všechny souborové systémy připojeny pouze pro čtení.
Aby to bylo složitější, chci také, aby byl scénář přiměřeně rychlý když to nevyprší. Konkrétně skript používám také ve Windows (hlavně v Cygwinu), kde jsou fork a exec obzvlášť nízké, takže jejich použití chci omezit na minimum.
Stručně řečeno, mám
trap cleanup 1 2 3 15
foo=`cat`
a chci přidat časový limit. Nemohu nahradit cat
s read
vestavěný. V případě vypršení časového limitu chci provést cleanup
funkce.
Pozadí:Tento skript hádá kódování terminálu vytištěním několika 8bitových znaků a porovnáním pozice kurzoru před a za. Začátek skriptu testuje, že je stdout připojen k podporovanému terminálu, ale někdy prostředí lže (např. plink
nastaví TERM=xterm
i když je volána s TERM=dumb
). Příslušná část skriptu vypadá takto:
text='Éé' # UTF-8; shows up as Ãé on a latin1 terminal
csi='␛['; dsr_cpr="${csi}6n"; dsr_ok="${csi}5n" # ␛ is an escape character
stty_save=`stty -g`
cleanup () { stty "$stty_save"; }
trap 'cleanup; exit 120' 0 1 2 3 15 # cleanup code
stty eol 0 eof n -echo # Input will end with `0n`
# echo-n is a function that outputs its argument without a newline
echo-n "$dsr_cpr$dsr_ok" # Ask the terminal to report the cursor position
initial_report=`tr -dc ;0123456789` # Expect ␛[42;10R␛[0n for y=42,x=10
echo-n "$text$dsr_cpr$dsr_ok"
final_report=`tr -dc ;0123456789`
cleanup
# Compute and return initial_x - final_x
Jak mohu upravit skript tak, že pokud tr
nepřečetl žádný vstup po 2 sekundách, je zabit a skript provede cleanup
funkce?
Přijatá odpověď:
Co s tím:
foo=`{ { cat 1>&3; kill 0; } | { sleep 2; kill 0; } } 3>&1`
To znamená:spusťte příkaz produkující výstup a sleep
ve stejné skupině procesů, skupině procesů jen pro ně. Kterýkoli příkaz vrátí jako první, zabije celou skupinu procesů.
Divil by se někdo:Ano, trubka se nepoužívá; obchází se pomocí přesměrování. Jediným účelem je, aby shell spouštěl dva procesy ve stejné skupině procesů.
Související:Zdá se, že skript pro zmenšení a kompresi diskových obrazů DMG v tomto scénáři nefunguje dobře?Jak Gilles zdůraznil ve svém komentáři, toto nebude fungovat ve skriptu shellu, protože proces skriptu by byl zabit spolu se dvěma podprocesy.
Jedním ze způsobů¹, jak vynutit spuštění příkazu v samostatné skupině procesů, je spustit nový interaktivní shell:
#!/bin/sh
foo=`sh -ic '{ cat 1>&3; kill 0; } | { sleep 2; kill 0; }' 3>&1 2>/dev/null`
[ -n "$foo" ] && echo got: "$foo" || echo timeouted
Ale s tím mohou existovat výhrady (např. když stdin není tty?). Přesměrování stderr slouží k odstranění zprávy „Ukončeno“, když je interaktivní shell zabit.
Testováno pomocí zsh
,bash
a dash
. Ale co oldies?
B98 navrhuje následující změnu fungující na Mac OS X s GNU bash 3.2.57 nebo Linux s pomlčkou:
foo=`sh -ic 'exec 3>&1 2>/dev/null; { cat 1>&3; kill 0; } | { sleep 2; kill 0; }'`
–