Make cp return an error value if the target exists

Is there a way to make cp (from GNU coreutils on Linux) return a nonzero value in case the target file does already exist?
Or is there any other small utility which is commonly available and which provides this functionality?

This would be useful in shell scripts, to atomically copy a file without accidentially loosing anything, and without user interaction. A similar result can be obtained by comparing the copy against the original, but I would have hoped for a simpler solution.

Asked By: MvG

||

Some cp implementations, including GNU cp, have a non-standard -n switch to not clobber a file if it exists. but GNU cp returns 0 either way.

you could use an if statement in your shell script to test the existence of the file before you copy something to it:

if [ -e /path/to/file ]
  then 
   exit 1
  else
   cp file /path/to/file
fi

or if you want a function you could use something like this:

function cpa(){ 
 if [ -e "$2" ]
  then 
   exit 1
  else 
   /bin/cp "$1" "$2"
  fi 
}
Answered By: h3rrmiller

You could do:

(set -C &&  cat < /path/to/src > /path/to/dest)

It won’t copy anything but the content of the file though (not the permissions, ownership or sparseness as some cp implementations do).

Answered By: Stéphane Chazelas

The following shell snippet copies a file only if the target doesn’t exist, and returns a nonzero status if the target exists.

(set -C; : >"$target" && cp -- "$source" "$target")

This is not atomic: if a file is created after the attempt to clobber $target and before the copy starts, it will be overwritten. Also, cp is not atomic: another process can observe the file being written. This does, however, work correctly if all concurrent writers use the same strategy.

The following more complex snippet first makes a temporary copy, then moves it into place. There is still a race condition: there is a small window of time during which an unrelated program that doesn’t play ball could create the target file.

( set -C
  tmp=$(mktemp -p "$(dirname -- "$target")")
  cp -- '$source" "$tmp"
  if : >"$target"; then
    mv -i -- "$tmp" "$target"
  else
    rm -- "$tmp"
    false
  fi
)

Expanding on my comment to h3rrmiller’s answer, and following the pattern of Gilles’s snippet, here is another method of making a temporary copy, then moving it into place. I believe this method is not vulnerable to any race conditions. It does require the -n flag on mv, which is not part of the POSIX spec. However, it does seem to be widely available. (Verified on the GNU implementation, also BusyBox, FreeBSD, OS X.)

(
  tmp=$(mktemp "${target}.XXXXXX") && # "mktemp -p" doesn't work on BSD
  cp -- "$source" "$tmp" && # you may want to add -p flag
  inode=$(ls -di "$tmp" | cut -d' ' -f1) && # see comment below
  mv -n -- "$tmp" "$target" && # won't overwrite, but doesn't then exit non-0
  if [ "$(ls -di "$target" | cut -d' ' -f1)" != "$inode" ]; then
    rm -- "$tmp"; false
  fi
)

In Linux, you can get the inode using stat -c'%i' "$tmp", but that’s not portable.

Instead of verifying the inode, another idea would be to check whether a file still exists at path $tmp. If the mv -n ... succeeded then there shouldn’t be. (Unless some other process has freshly created $tmp before you checked to see whether it was missing.)

For efficiency, it’d make sense to add a check if $target exists at the start of the routine. If we see that it does exist, we can fail immediately, rather than making the copy, trying to move it, then seeing that that failed so removing the copy. This is left as an (easy) exercise.

Answered By: dubiousjim
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.