Watch Out for Data Loss When Redirecting to a File in a Shell Script

speckx1 pts0 comments

Watch Out for Data Loss When Redirecting to a File in a Shell Script — Nick JanetakisLearn Docker With My Newest Course<br>Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples.<br>Start Learning Docker &rarr;

Updated on July 7, 2026<br>in<br>#linux<br>Watch Out for Data Loss When Redirecting to a File in a Shell Script

There's a number of subtle things going on when you redirect output to a file,<br>here's how to do it safely and verify it with strace.<br>Quick Jump:<br>Prefer video? Here it is on YouTube.<br>I&rsquo;ve written 10,000+ lines of shell scripts over the years and I can&rsquo;t believe<br>I only learned about this recently.<br>Let&rsquo;s say you have a normal shell script. I pretty much always set these<br>options to protect myself. They&rsquo;ll make your script exit if any commands you<br>run encounter an error, if a pipeline fails or if a referenced variable is<br>unset:<br>#!/usr/bin/env bash

set -o errexit<br>set -o pipefail<br>set -o nounset

echo "Hello"

Easy right? The expected output is &ldquo;Hello&rdquo; which is what we get.<br>What about echo "Hello" > results? Also easy, it will create a new results<br>file in the directory where you ran this script with the contents of &ldquo;Hello&rdquo;.<br>This is shell syntax to redirect output to a file.

Showing the Problem<br>Without looking it up or trying it, take a guess what happens if you put this<br>line at the bottom of the script. Don&rsquo;t just run it in your terminal, since the<br>same options aren&rsquo;t set:<br>echo "Hello" | awk '{system("sleep 5"); print}' >results

To give you a hint, the awk command is just sleeping for 5 seconds. We&rsquo;re<br>using that instead of sleep 5 because the sleep command doesn&rsquo;t accept<br>STDIN where as awk does.<br>I don&rsquo;t know about you but initially I thought this would happen:<br>&ldquo;Hello&rdquo; would be printed to STDOUT<br>It would get piped to awk which accepts STDIN<br>awk would sleep for 5 seconds and print &ldquo;Hello&rdquo; to STDOUT<br>A results file would be created with the contents &ldquo;Hello&rdquo;<br>The above mental model is how most pipelines are read. It flows from left to<br>right.<br>What Really Happens<br>The file descriptor for the results file is created immediately and a 0 byte file is createdYou can verify this by running the command and catting the file in terminal #2

echo prints &ldquo;Hello&rdquo; and pipes it into awk<br>awk receives this input and begins executing its system command<br>awk sleeps for 5 seconds<br>awk prints &ldquo;Hello&rdquo; to STDOUT which is already connected to the open results file, this yields our expected text in the file<br>The important takeaway is as soon as this pipeline starts to run the file<br>you&rsquo;re redirecting to can be considered truncated to a 0 byte file and there&rsquo;s<br>3 possible states this file can be in after the script is called:<br>All commands finish successfully and it&rsquo;s what you expect<br>One of the commands fails and the scripts exits with a partially written file<br>The script exits before processing has occurred (power outage) and the file is empty<br>What If You Use Tee Instead?<br>If you&rsquo;re comfortable writing shell scripts, you might think about trying:<br>echo "Hello" | awk '{system("sleep 5"); print}' | tee results 1>/dev/null

This has the same problem as > since the results file will get immediately<br>created.<br>Technically when you run the above, echo, awk and tee are being run in<br>parallel. Then tee is like &ldquo;oh, I need to write to the results file, let me<br>open a file descriptor for that&rdquo;. At this point awk can start streaming data<br>into the file at its discretion.<br>Why This Is a Problem<br>It comes down to potential unexpected data loss. Here&rsquo;s 1 of many examples:<br>Imagine you&rsquo;re doing something like a database backup. It&rsquo;s not unreasonable to<br>think you&rsquo;d run your database dump command (pg_dumpall, etc.), maybe pipe it<br>into gzip and then redirect it to a file.<br>If you&rsquo;re keeping 1 day&rsquo;s worth of backups by overwriting the existing file,<br>if your current day&rsquo;s backup command fails to run for some reasons, you could<br>end up with a partial backup that overwrote yesterday&rsquo;s valid backup.<br>In a disaster scenario where you have to restore, you suddenly have data loss.

Using strace to Pry Deeper<br>The more I use native Linux on the desktop, the more I find myself interested<br>in lower level details. I wish I had unlimited time:<br>$ strace --follow-forks -e trace=openat ./demo

If you run the above with either version you&rsquo;ll see this line early on which<br>demonstrates a truncated 0 byte file is being created:<br>[pid 1964503] openat(AT_FDCWD, "results", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Also, while no timestamps are shown, when you run the above command with the<br>tee version you&rsquo;ll see something like this pop out at about the same time:<br>strace: Process 1966073 attached<br>strace: Process 1966074 attached<br>strace: Process 1966075 attached

This demonstrates all...

file rsquo hello results script ldquo

Related Articles