2016-07-18 6 views
2

Der folgende Code beschreibt mein Problem.Übergeben Sie die Ausgabe vom ersten Befehl als Eingabe an den zweiten Befehl, sodass der erste Befehl in der aktuellen Shell ausgeführt wird.

# File: foo.sh 

# We have a work function that may define variables and produce some 
# complex output. 
work() 
{ 
    a=foo 
    ps -ef 
} 

# This function uses the work function. 
f() 
{ 
    # We want to filter the complex output produced by work function but 
    # at the same time we want that any variables defined by work 
    # function is available in this function. 
    work | head -n 2 

    # Since pipeline launches subshells, the variable defined by work 
    # function is no longer available in this function. 
    echo a: $a 
} 

f 

Hier ist der Ausgang.

$ sh foo.sh 
UID  PID PPID C STIME TTY   TIME CMD 
root   1  0 0 Jul15 ?  00:00:03 /sbin/init 
a: 

Da das Rohr schafft eine Subshell der die Variablen definiert in work() bedeutet, gehen verloren, wenn wir zu f() zurückgegeben, ich das Problem auf diese Weise fixiert.

# File: bar.sh 

# We have a work function that may define variables and produce some 
# complex output. 
work() 
{ 
    a=foo 
    ps -ef 
} 

# This function uses the work function. 
f() 
{ 
    # We want to filter the complex output produced by work function but 
    # at the same time we want that any variables defined by work 
    # function is available in this function. 
    work > out.txt 
    head -n 2 out.txt 
    echo a: $a 
} 

f 

Hier ist der Ausgang.

$ sh bar.sh 
UID  PID PPID C STIME TTY   TIME CMD 
root   1  0 0 Jul15 ?  00:00:03 /sbin/init 
a: foo 

Obwohl das zweite Skript für dieses Spielzeug Beispiel gut funktioniert, kann es (wenn auch lösbare Probleme) zu Problemen führen, wenn f() mich in einem Rekursion Aufruf endet, die zu out.txt führen würden durch die gerufene-f überschrieben werden () wenn der Aufrufer-f() sich immer noch darauf verlassen würde. Natürlich kann dies gelöst werden, indem ausreichend auf die Verwendung von out.txt geachtet wird, d. H. Verwendung von out.txt nur bevor f() sich selbst aufruft und die Verwendung von out.txt unter dem Aufruf vermeidet.

Dies hat mich gefragt, ob es eine Möglichkeit gibt, dieses Problem zu lösen, ohne auf eine temporäre Datei zu erstellen. Gibt es eine Möglichkeit, die Ausgabe von work an einen anderen Befehl zu übergeben, ohne auf Datei-I/O zurückgreifen zu müssen, so dass work in der aktuellen Shell ausgeführt wird? Ich suche nach Lösungen, die auf jeder POSIX-Shell ausgeführt werden können.

+0

Es gibt eine ** schrecklich **, aber einfach [und tragbar ] Möglichkeit, es zu tun, wenn 'work' zweimal ausgeführt wird, ist kein Problem für Sie (und vorausgesetzt, der zweite Aufruf von 'work' verhält sich genau so wie der erste Aufruf): 'work> &/dev/null && work | head -n 2' – pah

+0

POSIX-Shell-Funktionen verwenden standardmäßig * globale * Variablen, also diese 'unset bar; foo() {bar = baz; }; oof() {foo; echo $ bar; }; oof', gibt zurück: 'baz'. Um den Standardwert zu ändern, verwenden Sie 'local', wie folgt:' unset bar; foo() {lokaler Balken = baz; }; oof() {foo; echo $ bar; }; oof', die nichts zurückgibt. Angesichts dessen sieht es so aus, als ob dieses Q shell * functions * mit shell * scripts * verwechselt hätte. Shell-Skripts werden normalerweise in einer Subshell ausgeführt und können ihre elterliche Umgebung nicht ändern. – agc

+0

@agc Ich verstehe nicht, wie Ihr Kommentar für meine Frage relevant ist. Wie ist 'local' hier überhaupt relevant? Ich möchte die Variablendefinitionen nicht verlieren.Ich möchte, dass die Variablendefinitionen intakt bleiben *. Außerdem ist "local" nicht garantiert in einer POSIX-konformen Shell verfügbar. Das Schlüsselwort 'local' wird nicht von POSIX angegeben. Zum Beispiel unterstützt 'yash' das Schlüsselwort' local' nicht. Ich glaube, Sie haben meine Frage völlig falsch verstanden. –

Antwort

2

A kludge, (in yash getestet), um die Variable zu Dateideskriptor 3 und Bypass head, dann kopieren es zurück zu STDIN zur Ausgabe:

{ { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1 

Output:

UID  PID PPID C STIME TTY   TIME CMD 
root   1  0 0 Jul15 ?  00:00:41 /sbin/init splash 
a: foo 

Beachten Sie zwei Ebenen von geschweiften Klammern:

  1. Kurz vor dem Ende, Datei-Deskriptor 3 zurück zu STDIN ohne head zu verschieben.
  2. Um work und echo in der gleichen Shell zu halten.

der gleiche Code wie oben, in einer Funktion f:

f() { { { work ; echo a: $a 1>&3 ; } | head -n 2 ; } 3>&1 } 
1

I eine Funktion pseudopipe zur Ausführung von Rohren in der gewünschten Art und Weise (dh des Rohres der erste Befehl implementiert ausgeführt in der aktuellen Shell). Beachten Sie jedoch, dass die Implementierung temporäre Dateien erstellt und somit nicht die Anforderung zur Vermeidung von Datei-E/A erfüllt.

Verbrauch:

Jede der folgenden Formen und andere Varianten funktionieren:

pseudopipe "cmd1|cmd2" 
pseudopipe 'cmd1|cmd2' 
pseudopipe cmd1\|cmd2 

Einschränkungen der aktuellen Implementierung:

  • nur Rohre aus zwei Befehle werden korrekt behandelt

  • Die Befehle dürfen kein "|" -Zeichen enthalten. Zum Beispiel wird nicht die folgende wie erwartet:

    pseudopipe "work|grep '|'" 
    
  • Ihr Skript nicht die reservierten Variablen PSEUDOPIPE_TMPDIR, PSEUDOPIPE_CMD1 und PSEUDOPIPE_CMD2

Implementierung und Beispiel verwenden müssen :

#!/bin/sh 

pseudopipe() 
{ 
    PSEUDOPIPE_TMPDIR=$(mktemp -d) 
    PSEUDOPIPE_CMD1=$(echo "[email protected]"|sed 's/|/\n/g'|head -n 1) 
    PSEUDOPIPE_CMD2=$(echo "[email protected]"|sed 's/|/\n/g'|tail -n 1) 
    mkfifo "$PSEUDOPIPE_TMPDIR"/fifo 
    eval "$PSEUDOPIPE_CMD2 < $PSEUDOPIPE_TMPDIR/fifo &" \ 
     "$PSEUDOPIPE_CMD1 > $PSEUDOPIPE_TMPDIR/fifo ;" \ 
     "rm -r $PSEUDOPIPE_TMPDIR" 
    wait $! 
} 

work() 
{ 
    a=foo 
    ps -ef 
} 

f() 
{ 
    a=bar 
    echo "before pseudopipe: a: $a" 
    pseudopipe "work|head -n 2" 
    echo "after pseudopipe: a: $a" 
} 

f 

Ausgang:

$ sh pseudopipe.sh 
before pseudopipe: a: bar 
UID  PID PPID C STIME TTY   TIME CMD 
root   1  0 0 Jul22 ?  00:00:06 /sbin/init splash 
after pseudopipe: a: foo 
1

Das Problem ist, dass Sie mehrere Stücke von Informationen aus dem Sub-Shell wollen. Eine Lösung ist, alles setzen Sie von dem Sub-Shell in das Standardausgaberohr wollen und alles wieder auf der übergeordnete Shell Seite bearbeiten:

work() 
{ 
    a=foo 
    echo $a 
    ps -ef 
} 

Dann Sie einige Parsing Arbeit haben auf der Muttermantelseite zu tun. Hier habe ich den Separator zu einem Newline und wandeln die Rückkehr String in ein Array mit einer Zeile an jedem Array-Index:

f() 
{ 
    output=$(work | head -n 3) 
    IFS=$'\n' 
    lines=($output) 
    a=${lines[0]} 
    echo ${lines[1]} 
    echo ${lines[2]} 
    echo a=$a 
} 

f 

Eine andere Lösung ist, Named Pipes und jede Variable, die Sie in in seinen eigenen Interesse zuweisen benanntes Rohr. Auf der Seite Subshell Sie die Rohre erstellen und schreiben:

mkfifo /tmp/variable_a 
echo $a > /tmp/variable_a 

auf dem Eltern-Shell Seite Sie aus dem gleichen Named Pipe lesen:

read a < /tmp/variable_a 
rm -f /tmp/variable_a 
Verwandte Themen