📜 ⬆️ ⬇️

Bash Co-Processes

One of the new features in Bash 4.0 is coproc. The coproc operator allows you to create a co-process that is associated with the command shell using two channels: one to send data to the co-process, the second to receive from the co-process.

For the first time I found the use of this trying to write a log using exec redirection. The goal was to optionally allow writing the script output to a log file after running the script (for example, due to the --log option of the command line).

The main problem with logging output after the script has started is related to the fact that its output could already be redirected (to a file or channel). If we redirect already redirected output, we will not be able to execute the command as it was intended by the user.

The previous implementation was done using named pipes:
#!/bin/bash echo hello if test -t 1; then # Stdout is a terminal. exec >log else # Stdout is not a terminal. npipe=/tmp/$$.tmp trap "rm -f $npipe" EXIT mknod $npipe p tee <$npipe log & exec 1>&- exec 1>$npipe fi echo goodbye 

From the previous article :
Here, if the standard output stream of the script is not connected to the terminal, we create a pipe (named pipe located in the file system) using mknod , and with the help of trap we delete it after the script ends. Then we run tee , which in the background connects the input stream with the created channel. Keep in mind that in addition to writing to the file everything received from the input stream, tee also outputs everything to the standard output stream. Also, keep in mind that the tee output stream is sent to the same place as the entire output of the script (the calling tee script). Thus, tee output will go to where the output stream of this script is currently directed (that is, to the output stream redirected by the user or the pipeline specified when the script was called on the command line). Thus, we got a standard tee output where we need it: to a redirection or a pipeline of a user-defined channel.

We can do the same with co-processes:
 echo hello if test -t 1; then # Stdout is a terminal. exec >log else # Stdout is not a terminal. exec 7>&1 coproc tee log 1>&7 #echo Stdout of coproc: ${COPROC[0]} >&2 #echo Stdin of coproc: ${COPROC[1]} >&2 #ls -la /proc/$$/fd exec 7>&- exec 7>&${COPROC[1]}- exec 1>&7- eval "exec ${COPROC[0]}>&-" #ls -la /proc/$$/fd fi echo goodbye echo error >&2 

In case our standard output goes to the terminal, we simply use exec to redirect our output to the correct log file. If the output is not connected to a terminal device, then we use coproc to start tee as a co-process and redirect our output to tee input and tee output to redirect to where it was originally planned.
Running tee using coproc essentially works just like tee in the background (for example, tee log & ), the main difference is that both channels (input and output) are connected to it. By default, Bash places the file descriptors of these channels in the COPROC array.

Keep in mind that these channels must be created before using redirects in a team.
Pay attention to the part of the script in which the output of the script is not related to the terminal. The next line duplicates our standard output in file descriptor 7.
 exec 7>&1 

Then we start tee with redirecting its output to file descriptor 7.
 coproc tee log 1>&7 

Now tee will write everything that he reads from standard input to a file called log and file descriptor 7, which is our current standard output.
Now we will close the file descriptor 7 (remember that tee is also “file”, which is open at 7 as standard output):
 exec 7>&- 

Since we have closed 7, we can use it again, so we transfer the channel connected to input 7 tee :
 exec 7>&${COPROC[1]}- 

Then we move our standard output to the channel attached to the standard input tee (our file descriptor 7) by:
 exec 1>&7- 

And finally, we close the channel attached to the tee output, since we no longer need it:
 eval "exec ${COPROC[0]}>&-" 

In this case, eval is necessary here, because otherwise Bash considers that the value of $ {COPROC [0]} is a command. On the other hand, this is not required above ( exec 7> & $ {COPROC [1]} - ), because Bash knows that “7” initiates work with the file descriptor, and is not considered a command.
Also note the commented line:
 #ls -la /proc/$$/fd 

This is useful for viewing files opened by the current process.
')
Now we have the desired effect: our standard output will be sent to tee . Tee has an “input” to our log file and the recording goes both to the channel and to the file, which was originally planned.
So far I can not think of any other tasks for co-processes, at least not being contrived. See the bash man page for more information on co-processes.

Source: https://habr.com/ru/post/280754/


All Articles