5

My understanding of cp src dest is:

  1. If src is a file and dest doesn't exist, but its parent directory does, dest is created.
  2. If src is a file and dest is a file, dest is overwritten with the contents of src.
  3. If src is a file and dest is a directory, dest/src is created.

I'm trying to avoid case #3. If I write a script assuming that dest won't be a directory, but it turns out at runtime that it is, I don't want cp to put a file in the wrong place and continue silently.

Instead, I want it to either:

  • Delete the whole dest directory and replace it with the desired file.
  • Error out without doing anything.

I'd also prefer for this to happen atomically. Using a separate command like test -d to check if dest is a directory would open up an opportunity for TOCTTOU problems.

Can this be done with cp? If not, can it be done with any other similarly ubiquitous command?

I'm interested in both portable solutions and solutions that rely on non-standard extensions, as long as they're reasonably common.

Maxpm
  • 772

2 Answers2

6

A redirection would do that. It behaves as cp's -T, --no-target-directory (it exits with error if dst is a directory):

$ cat src > dst
3

In terms of absolute compatibility, you could check for a target directory before attempting the copy.

Either error out,

[ -d dest ] && echo "${0##*/}: is a directory: dest" >&2 && exit 1
cp src dest

or remove the target silently

[ -d dest ] && rm -rf dest
cp src dest

There is no atomic operation available to delete a directory and replace it with a file. (You might find a command such as cp --no-target-directory, but the underlying operation is still fundamentally not atomic. All you're doing is reducing the attack surface, not eliminating it.)

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • But OP said I'd also prefer for this to happen atomically. Using a separate command like test -d to check if dest is a directory would open up an opportunity for TOCTTOU problems. – Arkadiusz Drabczyk Jan 04 '21 at 18:58
  • 1
    @ArkadiuszDrabczyk point noted; I was in the process of flagging up a race condition in the second option so I've modified what I was going say in favour of addressing your concern – Chris Davies Jan 04 '21 at 19:02