cp behaves weirdly when . (dot) or .. (dot dot) are the source directory

This answer reveals that one can copy all files – including hidden ones – from directory src into directory dest like so:

mkdir dest
cp -r src/. dest

There is no explanation in the answer or its comments as to why this actually works, and nobody seems to find documentation on this either.

I tried out a few things. First, the normal case:

$ mkdir src src/src_dir dest && touch src/src_file src/.dotfile dest/dest_file
$ cp -r src dest
$ ls -A dest
dest_file  src

Then, with /. at the end:

$ mkdir src src/src_dir dest && touch src/src_file src/.dotfile dest/dest_file
$ cp -r src/. dest
$ ls -A dest
dest_file  .dotfile  src_dir  src_file

So, this behaves simlarly to *, but also copies hidden files.

$ mkdir src src/src_dir dest && touch src/src_file src/.dotfile dest/dest_file
$ cp -r src/* dest
$ ls -A dest
dest_file  src_dir  src_file

. and .. are proper hard-links as explained here, just like the directory entry itself.

Where does this behaviour come from, and where is it documented?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The behaviour is a logical result of the documented algorithm for cp -R. See POSIX, step 2f:

The files in the directory source_file shall be copied to the directory dest_file, taking the four steps (1 to 4) listed here with the files as source_files.

. and .. are directories, respectively the current directory, and the parent directory. Neither are special as far as the shell is concerned, so neither are concerned by expansion, and the directory will be copied including hidden files. *, on the other hand, will be expanded to a list of files, and this is where hidden files are filtered out.

src/. is the current directory inside src, which is src itself; src/src_dir/.. is src_dir’s parent directory, which is again src. So from outside src, if src is a directory, specifying src/. or src/src_dir/.. as the source file for cp are equivalent, and copy the contents of src, including hidden files.

The point of specifying src/. is that it will fail if src is not a directory (or symbolic link to a directory), whereas src wouldn’t. It will also copy the contents of src only, without copying src itself; this matches the documentation too:

If target exists and names an existing directory, the name of the corresponding destination
path for each file in the file hierarchy shall be the concatenation of target, a single slash
character if target did not end in a slash, and the pathname of the file relative to the
directory containing source_file.

So cp -R src/. dest copies the contents of src to dest/. (the source file is . in src), whereas cp -R src dest copies the contents of src to dest/src (the source file is src).

Another way to think of this is to compare copying src/src_dir and src/., rather than comparing src/. and src. . behaves just like src_dir in the former case.

Method 2

When you run cp -R src/foo dest, you’ll get dest/foo. So if directory dest/foo does not exist, cp will create it, and then copy the contents of src/foo to dest/foo.

When you run cp -R src/. dest, cp sees that dest/. exists, and then it’s just the matter of copying the contents of src/. to dest/..

When you think of it as copying a directory named . from src and merging its contents with the existing directory dest/., it will make sense.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x