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.

5
  • Do you use GNU coreutils? – Arkadiusz Drabczyk Jan 4 at 18:26
  • @ArkadiuszDrabczyk No, I happen to not use GNU coreutils. But I'm interested in hearing about non-standard extensions in addition to more portable options, even if I can't necessarily use them right now. – Maxpm Jan 4 at 18:31
  • 2
    It's a shame because GNU cp -T, --no-target-directory would do what you want. What implementation of cp do you use then? – Arkadiusz Drabczyk Jan 4 at 18:38
  • 2
    @ArkadiuszDrabczyk I sometimes work on machines with BusyBox cp. – Maxpm Jan 4 at 19:16
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
1
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.)

2
  • 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 4 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 – roaima Jan 4 at 19:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.