bash redirection to pipes (SOLVED)

Giorgos Keramidas keramida at ceid.upatras.gr
Thu Apr 9 21:13:14 EEST 2009


On Thu, 09 Apr 2009 15:59:45 +0300, Γιώργος Πάλλας <gpall at ccf.auth.gr> wrote:
>Giorgos Keramidas wrote:
>>On Tue, 07 Apr 2009 21:42:46 +0300, Γιώργος Πάλλας <gpall at ccf.auth.gr> wrote:
>>> Προσπαθώ να βρω πως θα κάνω ταυτόχρονα redirect το standard error
>>> και το standard output του tar, και τα δυο σε δυο pipes προς δυο
>>> διαφορετικά gpg. Το σκεπτικό είναι να παίρνω ένα encrypted αρχείο με
>>> τη λίστα των αρχείων που έγιναν tar (σαν index ας πούμε) και το
>>> encrypted tar σε ένα άλλο ξεχωριστό αρχείο.
>>>
>>> tar cv /media/abyss/mydata/   2>(gpg -e -r gpall -o lista_arxeiwn.gpg)
>>> 1>(gpg -e -r gpall -o encrypted_tar.gpg)
>>>
>>> Δεν θα έπρεπε αυτό να παίζει;;
>>
>> Κοίτα στα αρχεία της λίστας.
>>
>> Hint: Έχω ήδη κάνει post τη λύση στο:
>>
>> http://groups.google.com/group/local.linux.greek.users/msg/00e33bac46e070ad
>
> Γιώργο, στο mail που είχες στείλει, γράφεις:
>
>> Ή dup() σε shell syntax σε ένα temporary file descriptor:
>>
>>     $ ( echo foo ; echo lala >&2 ) > /dev/null
>>     lala
>>
>>     $ ( ( echo foo ; echo lala >&2 ) 3<&1 1<&2 2<&3 ) > /dev/null
>>     foo
>>
>> Και στις δύο φορές το stdout πάει στο /dev/null.  Απλά τη δεύτερη έχει
>> γίνει `swap' το stdout με το stderr.
>
> Νομίζω ότι αυτό δεν αποτελεί κάποια λύση στο πρόβλημα που περιέγραψα...

Κι όμως.  Αυτό που δεν είναι προφανές από το παλιό μου email είναι ότι
μπορείς να ανακατευθύνεις πρώτα το stdout σε ένα pipe κι ύστερα να
«γυρίσεις» το παλιό stderr στη θέση του stdout.

Έστω ένα μικρό shell script που κάνει:

    echo this is stdout
    echo this is stderr >&2

Ας το πούμε αυτό `foo.sh' κι ας το τρέξουμε 1-2 φορές με το stdout ή το
stderr να πηγαίνει στο `/dev/null':

    $ ./foo.sh > /dev/null
    this is stderr
    $ ./foo.sh 2> /dev/null
    this is stdout
    $

Το 'dup' που έγραψα στο email σημαίνει πως μπορείς να δημιουργήσεις ένα
προσωρινό «file descriptor» με τιμή 3, το οποίο κρατάει το μέρος στο
οποίο δείχνει το τρέχον stderr, π.χ.:

    $ ./foo.sh 3<&2 2>/dev/null
    this is stdout
    $ ./foo.sh 3<&2 2>/dev/null 2<&3
    this is stdout
    this is stderr

Τί γίνεται εδώ τώρα;  Πώς ξαναγύρισε από τον κόσμο των νεκρών bits το
μήνυμα του stderr;

Για να το καταλάβεις αυτό πρέπει να έχεις υπόψη σου πως δουλεύουν τα
file descriptors σε ένα UNIX process και ότι τα redirections που γράφεις
σε ένα command-line έχουν *side-effects* με την σειρά *ακριβώς* που τα
γράφεις εσύ.

Αρχικά το process foo.sh έχει τα τρία βασικά descriptors, τα οποία
δείχνουν και τα τρία στο device του τερματικού:

    ,----------+-----.
    |          |     |
    |          |  0  |--------------.
    |          |     |               \
    |          +-----+                \
    |          |     |                 |
    | foo.sh   |  1  |-----------------+-->  /dev/tty
    |          |     |                 |
    |          +-----+                /
    |          |     |               /
    |          |  2  |--------------'
    |          |     |
    `----------+-----'

Όταν ανακατευθύνεις το file descriptor 3 να δείχνει «εκεί που δείχνει
τώρα και το 2» καταλήγεις να έχεις ένα ακόμα process specific descriptor
που δείχνει στο ίδιο σημείο:

    ,----------+-----.
    |          |     |
    |          |  0  |--------------.
    |          |     |               \
    |          +-----+                \
    |          |     |                 |
    | foo.sh   |  1  |-----------------+-->  /dev/tty
    |          |     |                 |
    |          +-----+                /
    |          |     |               /
    |          |  2  |--------------'
    |          |     |              '
    |          +-----+             '
    |          |     |            '
    |          |  3  | . . . . . '
    |          |     |
    `----------+-----

Αμέσως μετά ανακατευθύνεις το file descriptor 2 να δείχνει στο /dev/null:

    ,----------+-----.
    |          |     |
    |          |  0  |--------------.
    |          |     |               \
    |          +-----+                \
    |          |     |                 |
    | foo.sh   |  1  |-----------------+-->  /dev/tty
    |          |     |                 |
    |          +-----+                /
    |          |     |               /
    |          |  2  |------------- / ---->  /dev/null
    |          |     |             /
    |          +-----+            /
    |          |     |           /
    |          |  3  |----------'
    |          |     |
    `----------+-----

Εδώ τελειώνει η πρώτη εκτέλεση, και το κείμενο του stderr χάνεται στον
απεριόριστο «χώρο» /dev/null.

Στη δεύτερη εκτέλεση όμως, αμέσως μετά το setup του file descriptor 2 να
δείχνει προς το /dev/null υπάρχει το redirection `2<&3', το οποίο για το
shell σημαίνει: «ανακατεύθυνε το file descriptor 2 εκεί που είναι αυτή
τη στιγμή το fd 3».  Οπότε καταλήγεις πάλι στο σχήμα:

    ,----------+-----.
    |          |     |
    |          |  0  |--------------.
    |          |     |               \
    |          +-----+                \
    |          |     |                 |
    | foo.sh   |  1  |-----------------+-->  /dev/tty
    |          |     |                 |
    |          +-----+                /
    |          |     |               /
    |          |  2  | . . . . . . .+
    |          |     |             /
    |          +-----+            /
    |          |     |           /
    |          |  3  |----------'
    |          |     |
    `----------+-----

Και η έξοδος του `foo.sh' που περνάει από το `echo this is stderr >&2'
φτάνει μια χαρά στο τερματικό σου.

Έχοντας ένα τέτοιο σχήμα υπόψη σου, μπορείς δηλαδή:

  - να κάνεις swap προσωρινά το fd=2 με το fd=1

  - να περάσεις το παλιό stderr (fd=1 πλέον) από φίλτρο

  - να κάνεις άλλο ένα swap τα fds 1 <=> 2

  - να περάσεις το παλιό stdout (πάλι fd=1 πλέον) από δεύτερο φίλτρο

Παράδειγμα από 2 ανεξάρτητα compression pipes:

    $ ( ( ./foo.sh 3<&1 1<&2 2<&3 ) | gzip -9c - > stderr.gz ) 3<&2 2<&1 1<&3 | gzip -9c - > stdout.gz
    $ gzip -cd stdout.gz
    this is stdout
    $ gzip -cd stderr.gz
    this is stderr

Βέβαια, όλο αυτό πέρα από το «ακαδημαϊκό» ενδιαφέρον που έχει για
κάποιον που τη βρίσκει να παίζει με το shell, γίνεται ΠΟΛΥ πιο εύκολα σε
μια πιο «μοντέρνα» scripting γλώσσα, χωρίς όλα τα ενδιάμεσα fork και
redirect.  Απλά έχει πλάκα που _μπορεί_ να γίνει κι αυτό σε sh(1) :-)

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <http://lists.hellug.gr/pipermail/linux-greek-users/attachments/20090409/93486a0f/attachment.pgp>


More information about the Linux-greek-users mailing list