Bash beenden! = 0, wenn von AWK aufgerufen und mit ^ C unterbrochen

7

Ich habe ein Problem mit dem folgenden (G) AWK-Skript:

do {
    ...
} while (system("sleep 10"))

Meine Absicht ist es, die Schleife zu unterbrechen, wenn der Benutzer im Ruhezustand ^ C drückt, aber es funktioniert nicht.

Ich glaube, das Problem ist, dass Bash mit 0 beendet wird, wenn es mit ^ C unterbrochen wird, zumindest wenn es von AWKs ausgeführt wird system():

$ awk 'BEGIN { print "\n" system("sleep 2") }'
(let the sleep complete)
0

 

$ awk 'BEGIN { print "\n" system("sleep 2") }'
^C
0

Wieso ist es so?

Ist das ein Fehler in Bash oder in (G) AWK?

Gibt es eine einfache Lösung, die keine komplizierte Bash-spezifische Syntax beinhaltet trap?

Das Beste, was ich mir einfallen lassen kann, ist Folgendes:

do {
    ...
} while (42 == system("sleep 10 && exit 42"))

das sieht für mich immer noch wie ein Kludge aus.

Tobia
quelle
1
Sie könnten Perl für diesen Job in Betracht ziehen, da Sie das Verhalten der Signalverarbeitung sehr genau beschreiben können (mehr als bei Ruby oder Python).
Otheus

Antworten:

6

Was awk's zurückgeben system()sollte, ist schlecht spezifiziert .

Was bei awkImplementierungen üblich zu sein scheint, ist, dass bei einem normalen Exit der Exit-Code (die an exit(3)Modulo 256 übergebene Nummer ) zurückgegeben wird. Wenn der Shell-Prozess jedoch durch ein Signal beendet wird, gibt es viele verschiedene Verhaltensweisen.

Beachten Sie auch, dass die C-Funktion system(3)zwar SIGINT (und SIGQUIT) im übergeordneten Element ignorieren soll, es jedoch (zumindest für mich) nicht sehr klar ist, dass die Anforderung auch für awk's gilt system(). Einige awkImplementierungen (wie mawk) sterben an diesem SIGINT (das ist auch das Verhalten, das ich gerne sehen würde, da ich nicht mag, dass meine STRG-C ignoriert wird, nur weil awkzufällig die system()Funktion ausgeführt wird), andere (wie gawkoder traditionelle Implementierungen) Gewohnheit.

Beachten Sie auch, dass einige Shells einige dieser Signale abfangen und schließlich aufrufen können, exit()was sich auf das Verhalten auswirken würde (siehe beispielsweise die Diskussion in den Kommentaren zur Bourne-Shell). Deshalb verwende ich execin den folgenden Beispielen, um die Shell aus der Schleife zu entfernen.

Für den Wert, der von einem SIGINT zurückgegeben wird system()(es gibt noch mehr Variationen, wenn Sie close()1 berücksichtigen ), sehen wir:

$ nawk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ bwk-awk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ mawk 'BEGIN {print system("exec kill -s INT $$")}'
130
$ gawk 'BEGIN {print system("exec kill -s INT $$")}'
0

0.0078125Sein 2 / 256(für einen SEGVvon 11würde man 0,542969 ((128 + 11) / 256) erhalten, wenn ein Kern entleert wurde, andernfalls 0,0429688 (11/256)), nawkdas nawkauf Solaris 10 oder 11 gefundene oder sein Linux-Port im Erbstück Werkzeugkasten, bwk-awkwobei die awkgepflegt von Brian Kernighan selbst (die Kin awk) die Grundlage für die awkauf einigen BSDs gefunden (hier getestet auf Debian GNU / Linux). /usr/xpg4/bin/awkunter Solaris 11 verhält sich wie gawk.

So basierend auf dem Wert svon zurückgegeben system(3)(eine Ganzzahl , wo die Bits 0 bis 6 ist die Signalnummer sind, Bit 7 das Kern-Bit, und Bit 8 bis 15 der Beendigungscode), awk‚s system()obigen gibt entweder:

  • s / 256(traditionelle awkImplementierungen),
  • int(s/256)( gawk),
  • oder in mawk, die gleiche Transformation, wie sie beispielsweise von Shells wie der Bourne- oder C-Shell durchgeführt wird ( (s&127)+128wenn sie getötet werden, s>>8andernfalls), außer dass, wenn ein Core entleert wird, Sie (s&127)+256stattdessen (s&127)+128(der Wert ist (s&255)+128) erhalten.

Hier könnten Sie stattdessen Folgendes tun:

awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10")}'

Aber es würde immer noch dazu führen awk, mit einigen awkImplementierungen wie getötet zu werden mawk. Wenn Ihr shist bashoder yash, könnten Sie tun:

awk 'BEGIN{print system("set -m; sleep 10; exit")}'

Wird sleepalso in einer eigenen Prozessgruppe ausgeführt (und nur es erhält das SIGINT).

Eine andere Alternative könnte darin bestehen, SIGINT vor dem Aufruf zu ignorieren awk. Die meisten Shells (und das ist eine POSIX-Anforderung) können einen Signalhandler jedoch nicht ändern, wenn das Signal beim Start bereits ignoriert wurde. Also Dinge wie:

(
  trap '' INT
  awk 'BEGIN{print system("trap exit\\ 1 INT; sleep 10; exit")}'
)

Wird nicht funktionieren. zshhat diese (selbstverschuldete) Einschränkung jedoch nicht. Wenn Sie also wissen, dass sie zshverfügbar ist, können Sie Folgendes tun:

(
  trap '' INT
  awk 'BEGIN{print system("exec zsh -c \"TRAPINT() exit 1; sleep 10\"")}'
)

Welche funktionieren würde , ob awkist mawk, gawkoder eine andere und würde vermieden , mit Auftragssteuerung zu Chaos mit. An dieser Stelle lohnt es sich jedoch, die Verwendung von perl/ python/ ruby... in Betracht zu ziehen, anstatt awkdie Signalverarbeitung an Ihre Bedürfnisse anzupassen.

Anmerkungen

1 Nach close()einer Pipeline wie in:

awk 'BEGIN {cmd = "kill -s INT $$"; cmd | getline; print close(cmd)}'

Erstens ^Cunterbricht dieses Mal awkalle Implementierungen, die ich versucht habe (es gibt keine solche Anforderung, SIGINT / SIGQUIT für popen(3)/ zu ignorieren pclose(3)(ein natürlicher Weg, dies zu implementieren getline), wie es für ist system(3)).

Aber wenn es um den Exit-Status geht (wo sist der von pclose(3)/ waitpid(2)like für system()oben zurückgegebene Wert ), sehen wir:

  • Solaris nawk: funktioniert nicht, so kann man close()in Solaris nicht anrufen nawk.
  • /usr/xpg4/bin/awkunter Solaris. Gibt immer 0 zurück, auch wenn exit(1)der Prozess abgeschlossen ist. Offensichtlich ein Konformitätsfehler.
  • gawkund bwk-awk: gibt s( exit 1gibt 256, getötet von SIGINT ergibt 2, getötet von einem SIGSEGV von 11 mit Kern ergibt 139).
  • mawk: wie für system(), sieht aus wie mawkist die einzige Implementierung, die darüber nachgedacht hat.
Stéphane Chazelas
quelle
Schlecht spezifiziert oder nicht, es sieht für mich wie ein Fehler in GAWK aus. Naja. Danke
Tobia
Auch set -man sich scheint der Trick zu tun! system("set -m; sleep 10")ist tatsächlich die sauberste Lösung der Reihe und funktioniert zuverlässig über alle AWKs hinweg. Genial!
Tobia
@ Tobia. Ja, funktioniert auch mit yash. Beachten Sie, dass die Verwendung von set -mnicht portabel ist (funktioniert nicht mit anderen shmir bekannten Implementierungen ). Und es verhindert, dass Sie awkim Hintergrund laufen .
Stéphane Chazelas
Du hast recht. Es funktioniert nicht mit Dash, was /bin/shin vielen Systemen die Standardeinstellung ist :-(
Tobia
Sieht so aus, als würden Sie nicht das Original verwenden, nawksondern etwas anderes.
schily