In Bash, how to not create the redirect output file once the command fails

3.5k views Asked by At

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?

3

There are 3 answers

5
Jonathan Leffler On BEST ANSWER

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.

11
Amit Kumar On

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
0
rici On

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.