How to redirect output to file, not creating it if it does not exist?

Viewed 1590

I need to redirect output to a file with > in shell, but the file should not be created if it does not already exist. How to achieve this?

I tried by creating a symlink to the file and redirecting to the symlink, but unfortunately the file is created anyway, if it does not already exist.

Probably I need to redirect to a separate program which does the checking, instead of shell. Anybody knows a program that can do this?

NOTE I need it to work in OpenWrt (i.e., with busybox's versions of utilities).

5 Answers

You did not specify at which time the device file should not exist. If you meant to ask, that the device file's existence should be checked against at the invocation of program, you can use the following in Bash:

[[ -f logfile ]] && program > devfile

An adapted version of @thatotherguy's comment that does basically the same by using dd instead of Bash internals looks like this:

program | dd conv=nocreat of=devfile

Shells have options to prevent truncation, but not to require truncation.

To get FS level atomicity, you'd need to use an open syscall without O_CREAT. One way to do this in a script is to go through Python:

# Usage: nocreat filename cmd [args...]
nocreat() {
  python -c '
import os
import sys
fd=os.open(sys.argv[1], os.O_WRONLY | os.O_TRUNC)
os.dup2(fd,1)
os.close(fd)
os.execvp(sys.argv[2], sys.argv[2:])
    ' "$@"
}

# Equivalent to: echo "an example" > myfile
# Except it requires that the file already exists (atomically)
nocreat myfile echo "an example"

If I recall correctly, Busybox uses an ash shell by default, so you should be fine with POSIX.

When redirecting, you have the option to truncate (>) or append (>>), but nothing built in which will fail if a file does not exist. To achieve this in POSIX alone, you'd need some functionality through a pipe.

append_if_exists() {
  [ -f "$1" ] || return 1
  cat >> "$1"
}

You might use it like:

somecommand | append_if_exists /path/to/some.log

If the target file does not exist, the function returns immediately with an error, never having opened stdin, which should I believe should send a SIGPIPE back to somecommand, which should then terminate.

Note that there's a race here -- a file could be removed after the [ but before the cat causing the redirect to create the file. But that comes with the territory, if you need something atomic, you may need to re-think this in an actual programming language.

The following works for me:

/* Printer device file must not be created if it does not
already exist. This is similar to `cat >', but open()
syscall is without O_CREAT. */

#include <fcntl.h> /* |open| */
#include <stdio.h> /* |fprintf| */
#include <unistd.h> /* |read| */

int main(int argc, char **argv)
{
  int fd;
  if ((fd = open(argv[1], O_WRONLY)) == -1) {
    fprintf(stderr, "open: %m\n");
    return 0;
  }

  char buf[8192];
  ssize_t n, m;

  while((n = read(0, buf, sizeof buf)) > 0) {
    m = write(fd, buf, n);
    if (m == -1) {
      fprintf(stderr, "write: %m\n");
      break;
    }
    if (m != n) {
      fprintf(stderr, "TODO: stuff all bytes in a loop\n");
      break;
    }
  }
  if (n == -1) fprintf(stderr, "read: %m\n");

  close(fd);
  return 0;
}
Related