7

Usually we may redirect a command output to a file, as following:

cat a.txt >> output.txt

As I tried, if cat failed, the output.txt will still be created, which isn't my expected. I know I could test as this:

if [ "$?" -ne "0"]; then
    rm output.txt
fi

But this may cause some issues overhead when there's already such output.txt prior to my cat execution. So I also need store the output.txt state before cat, if there's already such output.txt before cat execution, I should not rm output.txt by mistake... but there may still be problem on race condition, what if any other process create this output.txt right before my cat very closely?

So is there any simple way that, if the command fails, the redirection output.txt will be removed, or even not created?

6
  • Are you saying that you want to atomically replace a file with the output of a command, but only if it returns success? Commented Jun 10, 2015 at 6:03
  • some_command > /tmp/output.txt && mv /tmp/output.txt output.txt ? Commented Jun 10, 2015 at 6:04
  • @thatotherguy, I should use >> rather than >, not replace but append. If there's no such output file, it's OK to create one, but only if the command returns success. Commented Jun 10, 2015 at 6:11
  • @anishsane, thanks, but it would be better if no temporary file used. Commented Jun 10, 2015 at 6:15
  • 1
    The command will start pushing stdout once started. The exit status is available only after it finishes. Anyway, for short stdout, this could work. If the process produces longer outputs, it may fail: output=$(some_command) && echo "$output" > output.txt. I would still prefer temp file approach. Commented Jun 10, 2015 at 6:28

3 Answers 3

3

Fixed output file names are bad news; don't use them.

You should probably redesign the processing so that you have a date-stamped file name. Failing that, you should use the mktemp command to create a temporary file, have the command you want executed write to that, and when the command is successful, you can move the temporary to the 'final' output — and you can automatically clean up the temporary on failure.

outfile="./output-$(date +%Y-%m-%d.%H:%M:%S).txt"
tmpfile="$(mktemp ./gadget-maker.XXXXXXXX)"

trap "rm -f '$tmpfile'; exit 1" 0 1 2 3 13 15

if cat a.txt > "$tmpfile"
then mv "$tmpfile" "$outfile"
else rm "$tmpfile"
fi

trap 0

You can simplify the outfile to output.txt if you insist (but it isn't safe). You can use any prefix you like with the mktemp command. Note that by creating the temporary file in the current directory, where the final output file will be created too, you avoid cross-device file copying at the mv phase of operations — it is a link() and an unlink() system call (or maybe even a rename() system call if such a thing exists on your machine; it does on Mac OS X) only.

Sign up to request clarification or add additional context in comments.

5 Comments

The OP isn't the only one who benefitted from this answer. Apparently I still have a very large amount left to learn.
I think you missed the syntax of Amit's redirection which was 2&>1 instead of 2>&1. If you fix the redirection you will see that the first command will work.
The error in redirection made cat to attempt displaying the content of file 2 which doesn't exist. Hence the command cat file 2&>1 will result in a return value of non zero becuase file 2 is not found.
@alvits: Yes, I did (miss the typo in Amit's commands where he used 2&>1; instead of 2>&1;). I also rolled back my edit expounding on it, and left a self-derogatory comment after his answer. (And well spotted! Thank you.)
You're still the great Jonathan. Typo errors spare no one. Cheers.
0

You can't tell that the command has failed until it terminates, and by then it might have produced some output.

Probably a more useful condition is to avoid creating the output file until the command actually produces some output, and not worry about its status code.

This comes close:

command | { IFS= read -rn1 -d '' a &&
            { printf %s "$a" >> output.txt
              cat >> output.txt
            }
          }

However, if the first character output by command is a NUL byte, the NUL won't be written to the output file. Since the extension of the output file is .txt, that's unlikely in this particular case, but it could be handled by adding the command

[[ -z $a ]] && printf '\0' >> output.txt

after the printf and before the cat.

Comments

0

I think this will work, check this out.

 [ -e output.txt ] && (mv output.txt output.txt_bkp)
 cat a.txt > /dev/null 2>&1;[ $? -eq 0 ] && (cat a.txt > output.txt)

another way as suggested by Jonathan,

[ -e output.txt ] && (mv output.txt output.txt_bkp)
 if cat a.txt > /dev/null 2>&1
 then
      cat a.txt > output.txt
 fi

11 Comments

What's with echo $? Isn't [ $? -eq 0 ] sufficient?
i know that would be sufficient but in some shell we have to echo it, i have checked it, that is why preferred including echo.
You should use if cat a.txt > /dev/null 2>&1; then ... to test whether cat finishes. No need to mess with $? directly. I would be curious to know details about when you think Bash requires your echo … well, I'll resist the temptation to say 'nonsense' until you've identified when it is actually necessary, but I really don't think there's going to be a legitimate scenario where that much indirection is necessary. And don't do experimentation running as root unless you particularly like living dangerously.
The reason [ $? -eq 0 ] didn't work in your code is due to error in redirection, hence it yields a non zero output. When using [ ``$?`` -eq 0 ], $? is 0 because you spawned a sub-shell. If you fixed the redirection from 2&>1 to 2>&1 then you'll be amazed how it works. With the wrong redirection as you did, even with inexistent file you'll get an ok output.
Aargh: I didn't notice the erroneous I/O redirection — and spent some time fabricating an answer using your erroneous I/O redirection because despite everything, it jogged an ancient memory. However, the difference between 2&>1 and 2>&1 is very major (one runs cat in the background with 2 as an extra (non-existent) argument, which causes cat to fail), and the other redirects standard error to the same place that standard output is going. My carelessness. Oh well! You can see what I wrote in the edit log...but...slaps self again...careless...must be bedtime over here!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.