3

I hope this isn't a duplicate, but I did some due diligence trying to find and answer. I want to pipe the original output to a second command, only if the first command fails.

e.g. cat file.txt | command1 || command2 Where both command1 and command2 read from STDIN.

I want command1 to read everything from cat file.txt, but, if it fails. I want to run all that input into command2.

EDIT: I now realize that my question was a tad ambiguous. I said command1 and command2 both read from STDIN, but what I meant was that I wanted both to receive the STDOUT from cat file.txt (not necessarily that specific command)

I think my initial desire was to put something like this in an alias. Being 5 months later, I don't remember how this was solved. Most likely by writing a bash script.

3
  • 3
    What's wrong with command1 <file.txt || command2 <file.txt?
    – Kusalananda
    Commented Dec 21, 2020 at 22:55
  • 2
    Will comand1 do any reading before it fails? Commented Dec 21, 2020 at 22:57
  • 2
    Expanding on @ctrl-alt-delor's comment: if input is coming from a pipe, and command1 reads from it before failing, whatever it read is gone, and cannot be re-read by command2 (unless you did something like store it in a temp file first). Commented Dec 21, 2020 at 23:05

3 Answers 3

3

Welcome to Stack Exchange! Lot's of good info here. Thank you for your due diligence first.

The issue I see with your command is that it is "Begging the Question." In other words, it assumes you know the answer before you even start.

Let's have a closer look: cat file.txt
The contents of "file.txt" will go to STDOUT.
If the "cat" command generates any errors, they will do to STDERR.
AFTER the command completes, the exit code is set. 0 = success; anything else is an error.

The issue here is, you don't know if you should execute command1 or command2 until the output has already been sent. Which means, it's gone.

Compare that to this:
cat file.txt | sort
This is, effectively, 2 commands:
1: cat file.txt 1> temp_file
2: sort < temp_file
Ok not REAL Unix commands, done this way for clarification.
1: Send STDOUT to "tempfile" (that is the 1> part). Also send STDERR to the console.
2: Run the sort command, and read STDIN from "tempfile".
Putting the pipe | between the two commands obviates the need for a temp file. The pipe means: Redirect STDOUT from "cat" to STDIN of "sort".

Your issue:
You want to redirect STDOUT the the STDIN of another command, but you do not yet know which command that is, so you cannot redirect it.

Before I get to a possible solution, let me give you a little background:
In Unix, there is a way to execute command the way you want to. To wit:
my_command argument1 argument2 && echo "Success" || echo "Failure"

After my_command runs (and returns its exit status) with STDOUT & STDERR on the console, run one or the other of the echoes.
&& means "Do this if the last command was successful"
|| means "Do this if the last command failed"

You are very close with your original command. You have:
cat file.txt | command1 || command2
Let's change that to this:
cat file.txt && command1 || command2
That gets you everything you want, except for the output.

Question 1:
If cat file.txt works, then should command1 read STDOUT and ignore STDERR?
Likewise, if it fails, then should command2 read STDERR and ignore STDOUT?
Or, should either command read both STDOUT & STDERR?

I'll answer both questions for completeness.
Either way, you'll need a way to store the output for later processing. Normally a temp_file.

Capture STDOUT & STDERR in the same temp_file:
cat file.txt > /tmp/temp_file 2>&1

Capture STDOUT to temp_file#1 and STDERR to temp_file#2:
cat file.txt > /tmp/out_file 2 >/tmp/err_file

Question 2:
Do command1 and/or command2 allow for a file name on the command line?
command1 temp_file

Do command1 and/or command2 allow for a redirect from a file?
command2 < temp_file

Do command1 and/or command2 read STDIN?
cat temp_file | command1

Note: The Word Count command is an example.
wc temp_file
cat temp_file | wc
wc < temp_file
The second form may be problematic in the && || form. I include it here for completeness.

Ultimately, you will have to decide what command1 and command2 need to work.

We can now combine these together:
cat file.txt > /tmp/out_file 2>/tmp/err_file && command1 < /tmp/out_file || command2 < /tmp/err_file

Then delete the temp files:
rm /tmp/out_file /tmp/err_file

That should give you what you want.
I'm sure other guru's here can dream up a way of not using a temp_file. I like to keep thins simple and easily understandable because I am not in a programming shop and I have no idea who will have to maintain my code.

4
  • The question does not say that command2 should parse the standard error from command1, but the original file stream.
    – thanasisp
    Commented Dec 22, 2020 at 10:27
  • It was ambiguous. I don't know how well versed the OP is, or additional readers in the future. That's why I included the possibility, and how to do it. Besides, if the command fails, will there even be STDOUT? It depends on the command. In the example provided by the OP, if the cat command fails, there is no STDOUT, only STDERR. So why, then, would command2 need to read STDOUT? IS that an oversite on the OPs question, or is he really not running a cat command? Best to answer both ways and the OP decide.
    – Scottie H
    Commented Dec 22, 2020 at 16:40
  • Consider revisiting the markup in your answer. Parts of the text is very difficult to read.
    – Kusalananda
    Commented Dec 22, 2020 at 17:10
  • Thanks @Kusalananda. Not sure what happened there, Guess I'm still working on my "Mark-up" badge. :)
    – Scottie H
    Commented Dec 22, 2020 at 17:20
1

I think the only way you can do this is by writing the command1 output to a file and reading that in command2:

set -o errexit
command1 < file.txt > 1.out
command2 < 1.out

Alternatively

if command1 < file.txt > 1.out
then
    command2 < 1.out
fi

Of course, at this point the asynchronous processing benefit of a pipeline is gone.

2
  • The question does not say that command2 should parse some output processed by command1, but the original file stream.
    – thanasisp
    Commented Dec 22, 2020 at 10:28
  • It says "the original output", which I took to mean the output of the first pipeline.
    – l0b0
    Commented Dec 22, 2020 at 18:35
1

The simple thing to do is to save the file stream into a file and then

command1 < file || command2 < file

or

if command1 < file; then
    echo 'first command was successful'
else 
    command2 < file
fi

or any similar syntax, depending on what you want to output.

If you want the same file stream to be consumed by 2 commands the same time, you have to use something like tee, or any similar approach, which means either command2 will have to start processing before command1 has returned, or some kind of buffering should be used, together with some signaling maybe. The good and scalable buffering is a file on the disk.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .