Friday, April 3, 2009

Tip: Redirecting Multiple Command Outputs

Let's imagine a simple script:
#!/bin/bash

echo 1
echo 2
echo 3
Simple enough. It produces three lines of output:
1
2
3
Now let's say we wanted to redirect the output of the commands to a file named foo.txt. We could change the script as follows:
#!/bin/bash

F=foo.txt

echo 1 >> $F
echo 2 >> $F
echo 3 >> $F
Again, pretty straightforward, but what if we wanted to pipe the output of all three echo commands into less? We would soon discover that this won't work:
#!/bin/bash

F=foo.txt

echo 1 | less
echo 2 | less
echo 3 | less
This causes less to be executed three times. Not what we want. We want a single instance of less to input the results of all three echo commands. There are four approaches to this:

Make A Separate Script
script1:
#!/bin/bash

echo 1
echo 2
echo 3
script2:
#!/bin/bash

script1 | less
By running script2, script1 is also executed and its output is piped into less. This works but it's a little clumsy.

Write A Shell Function
We could take the basic idea of the separate script and incorporate it into a single script by making script1 into a shell function:
#!/bin/bash

# shell function
run_echoes () {
echo 1
echo 2
echo 3
}

# call shell function and redirect
run_echoes | less
This works too, but it's not the simplest way to do it.

Make A List
We could construct a compound command using {} characters to enclose a list of commands:
#!/bin/bash

{ echo 1; echo 2; echo 3; } | less
The {} characters allow us to group the three commands into a single output stream. Note that the spaces between the {} and the commands, as well as the trailing semicolon after the third echo, are required.

Launch A Subshell
Finally, we could do this:
#!/bin/bash

(echo 1; echo 2; echo 3) | less
Placing the list inside () creates a subshell, or another copy of bash and it executes the commands. This has the same result as enclosing the list of commands within {} but with more overhead. The real reason we would want to do this is if, instead of just redirecting the output, we wanted to put all three commands in the background:
#!/bin/bash

(echo 1; echo 2; echo 3) > foo.txt &
This doesn't make much sense for our echo commands (they execute too quickly to bother with), but if we have commands that take a long time to run, this technique can come in handy.

Enjoy!

3 comments:

  1. A very helpful synopsis - I was looking for a way of doing just this and didn't fancy resorting to one of the first two methods mentioned - thank you!

    ReplyDelete
  2. Thanks for tip.
    This didn't work to display multiple command output on single line. I tested and found the solution:

    TITLE: Linux / Unix Tip to display multiple command output on single same one line

    1. This will not work.
    (echo "Kernel version:"; echo `uname -r`; echo "Date:"; echo `date`;)

    The output:

    Kernel version:
    2.6.18-308.1.1.el5
    Date:
    Tue Mar 20 15:03:06 MYT 2012

    2. This will work.

    (echo Kernel version: `uname -r` Date: `date`)

    will output below line:

    Kernel version: 2.6.18-308.1.1.el5 Date: Tue Mar 20 15:06:53 MYT 2012


    The output is now on single line. (Useful to read and process text file as one record per line. This need persuaded me find this solution. Now I can use awk or perl etc. easily to format output display.)

    cheers

    ReplyDelete
    Replies
    1. That's because echo inserts a newline after printing its arguments. To prevent that behavior, simply call echo with -n. I.e.

      (echo -n "Kernel version: "; echo -n `uname -r`; echo -n "Date: "; echo `date`;)

      Of course, your revised command is shorter, but I've shown the above to demonstrate that it can still be done with multiple echo commands.

      Delete