2012-11-13 9 views
7

In einem bash Skript zuweisen würde Ich mag die Ausgabe der times eine Array-Variablen gebautet zuweisen, aber ich fand keinen besseren Weg, alsBash: die Ausgabe von ‚mal‘ builtin einer Variablen

tempnam=/tmp/aaa_$$_$RANDOM 
times > ${tempnam} 
mapfile -t times_a < ${tempnam} 

Ich schreibe die Ausgabe in eine temporäre Datei und lese sie im Array times_a zurück, da Pipelines oder $(times) in einer Subshell ausgeführt würden und die falschen Werte zurückliefern würden.

Eine bessere Lösung ohne die temporäre Datei?

Antwort

6

Das grundlegende Problem, das Sie lösen müssen, ist, wie Sie die Ausführung von time und die Variablenzuweisung in derselben Shell ohne eine temporäre Datei ausführen können. Beinahe jede Methode, die Bash zur Verfügung stellt, um die Ausgabe einer Sache an eine andere zu leiten, oder die Ausgabe eines Befehls zu erfassen, hat eine Seite, die in einer Unterschale arbeitet. Hier

ist eine Möglichkeit, dies ohne eine temporäre Datei zu tun, aber ich werde Sie warnen, es ist nicht schön, es zu anderen Shells nicht tragbar ist, und es erfordert mindestens Bash 4:

coproc co { cat; }; times 1>&${co[1]}; eval "exec ${co[1]}>&-"; mapfile -tu ${co[0]} times_a 

ich werde dies für Sie brechen:

coproc co { cat; } 

Dieses eine Koprozeß schafft; ein Prozess, der im Hintergrund abläuft, aber Sie erhalten Rohre, die mit der Standardeingabe und der Standardausgabe kommunizieren. Dies sind die FDs ${co[0]} (Standard von cat) und ${co[1]} (Standard in cat). Die Befehle werden in einer Subshell ausgeführt, so dass wir dort keines unserer Ziele ausführen können (times ausführen oder in eine Variable einlesen), aber wir können cat verwenden, um die Eingabe einfach an die Ausgabe zu übergeben und dann diese Pipe zu verwenden mit times und mapfile in der aktuellen Shell zu sprechen.

times >&${co[1]}; 

Run times, Umleitung seines Standard in dem cat Befehl zum Standard werden.

eval "exec ${co[1]}>&-" 

Schließen Sie das Eingabeende des Befehls cat. Wenn wir dies nicht tun, wird cat weiter auf Eingabe warten, seine Ausgabe bleibt offen, und mapfile wird weiterhin darauf warten, was dazu führt, dass Ihre Shell hängen bleibt. exec, wenn keine Befehle übergeben, wendet einfach seine Umleitungen auf die aktuelle Shell; Umleiten zu - schließt eine FD. Wir müssen eval verwenden, weil Bash Probleme mit exec ${co[1]}>&- zu haben scheint, die FD als den Befehl anstelle von Teil der Umleitung interpretierend; Mithilfe von eval kann diese Variable zuerst ersetzt und dann ausgeführt werden.

mapfile -tu ${co[0]} times_a 

Schließlich lesen wir tatsächlich die Daten aus dem Standard aus dem Coprocess. Es ist uns gelungen, den Befehl times und den Befehl mapfile in dieser Shell auszuführen und keine temporären Dateien zu verwenden, obwohl wir einen temporären Prozess als Pipeline zwischen den beiden Befehlen verwendet haben.

Beachten Sie, dass dies eine subtile Rasse hat. Wenn Sie diese Befehle nacheinander ausführen, statt alle als einen Befehl, schlägt der letzte fehl. Denn wenn Sie den Standard cat in schließen, wird der Prozess beendet, wodurch der Coprozess beendet und FDs geschlossen werden. Es scheint, dass, wenn alle auf einer Zeile ausgeführt werden, mapfile schnell genug ausgeführt wird, dass der Coprozess immer noch geöffnet ist, wenn er ausgeführt wird, und somit aus der Pipe lesen kann; aber vielleicht habe ich Glück. Ich habe keinen guten Weg gefunden.

Alles in allem ist es viel einfacher, nur die temporäre Datei zu schreiben. Ich würde mktemp verwenden, um einen Dateinamen zu generieren, und wenn Sie in einem Skript sind, fügen Sie eine Falle, um sicherzustellen, dass Sie Ihre temporären Dateien säubern vor dem Verlassen:

tempnam=$(mktemp) 
trap "rm '$tempnam'" EXIT 
times > ${tempnam} 
mapfile -t times_a < ${tempnam} 
+1

Ich wünsche ich dies noch upvote könnte, ich habe gelernt, viel lesen das, wirklich nette Antwort! – higuaro

0

Eine Möglichkeit, etwas Ähnliches zu tun

eine Umleitung der Ausgabe mit einem Prozess Substitution
times > >(other_command) 

Es ist combided wäre. Auf diese Weise wird times in der aktuellen Shell ausgeführt und die Ausgabe zu einem neuen Subprozess umgeleitet. Daher macht es keinen Sinn, dies mit mapfile zu tun, da dieser Befehl nicht in der gleichen Shell ausgeführt wird, und das ist wahrscheinlich nicht das, was Sie wollen. Die Situation ist ein wenig schwierig, da Sie keinen Aufruf an die in einer Subshell ausgeführten Shell-Builtins haben können.

1

Wow, gute Frage.

Eine Verbesserung wäre die Verwendung von mktemp, so dass Sie nicht auf Zufälligkeit angewiesen sind, um Ihre Datei einzigartig zu halten.

TMPFILE=$(mktemp aaa_XXXXXXXXXX) 
times > "$TMPFILE" 
mapfile -t times_a < ${tempnam} 
rm "$TMPFILE" 

Auch verwende ich für anstelle von mapfile (weil ich mapfile nicht habe).

Aber, ja, ich sehe nicht, wie Sie es ohne Dateien oder Named Pipes tun können.

Verwandte Themen