Append to file only if it exists

Viewed 1957

I've seen several answers on SO about how to append to a file if it exists and create a new file if it doesn't (echo "hello" >> file.txt) or overwrite a file if it exists and create one if it doesn't (echo "hello" > file.txt).

But how do I make sure that echo "hello" only works and appends to the file if it already exists and raises an error if it doesn't?

EDIT: Right now, I'm already checking for the file using [ -f file.txt ]. I was wondering if there's a way in which I could simply use echo.

4 Answers

Assuming the file is either nonexistent or both readable and writable, you can try to open it for reading first to determine whether it exists or not, e.g.:

command 3<file 3<&- >>file

3<&- may be omitted in most cases as it's unexpected for a program to start reading from file descriptor 3 without redirecting it first.

Proof of concept:

$ echo hello 3<file 3<&- >>file
bash: file: No such file or directory
$ ls file
ls: cannot access 'file': No such file or directory
$ touch file
$ echo hello 3<file 3<&- >>file
$ cat file
hello
$

This works because redirections are processed from left to right, and a redirection error causes the execution of a command to halt. So if file doesn't exist (or is not readable), 3<file fails, the shell prints an error message and stops processing this command. Otherwise, 3<&- closes the descriptor (3) associated with file in previous step, >>file reopens file for appending and redirects standard output to it.

I think a simple if as proposed in the other answers would be best. However, here are some more exotic solutions:

Using dd

dd can do the check and redirection in one step

echo hello | dd conv=nocreat of=file.txt

Note that dd prints statistics to stderr. You can silence them by appending 2> /dev/null but then the warning file does not exist goes missing too.

Using a custom Function

When you do these kind of redirections very often, then a reusable function would be appropriate. Some examples:

Run echo and redirect only if the file exists. Otherwise, raise the syntax error -bash: $(...): ambiguous redirect.

ifExists() { [ -f "$1" ] && printf %s "$1"; } 
echo hello >> "$(ifExists file.txt)"

Always run echo, but print a warning and discard the output if the file does not exist.

ifExists() {
  if [ -f "$1" ]; then
    printf %s "$1"
  else
    echo "File $1 does not exist. Discarding output." >&2
    printf /dev/null
  fi
} 
echo hello >> "$(ifExists file.txt)"

Please note that ifExists cannot handle all file names. If you deal with very unusual filenames ending with newlines, then the subshell $( ...) will remove those trailing newlines and the resulting file will be different from the one specified. To solve this problem you have to use a pipe.

Always run echo, but print a warning and discard the output if the file does not exist.

appendIfExists() {
  if [ -f "$1" ]; then
    cat >> "$1"
  else
    echo "File $1 does not exist. Discarding output." >&2
    return 1
  fi
} 
echo hello | appendIfExists file.txt

Just check:

if [ -f file.txt ]; then
  echo "hello" >> file.txt
else
  echo "No file.txt" >&2
  exit 1
fi

There's no way in bash to alter how >> works; it will always (try to) create a file if it doesn't already exist.

For example:

if [ -f "filename" ]; then
echo "hello" >>filename
fi
Related