How to copy hidden (starting with a dot) files and subdirectories in linux?

10

2

How to copy hidden files and hidden subdirectories (the ones starting with a dot) in folder A to folder B? For example if I have this structure:

A/a
A/b
A/.a
A/.b/
A/.b/somefile
A/.b/.c

I would like to copy to B just the hidden files and hidden subdirectories in A:

B/.a
B/.b/
B/.b/somefile
B/.b/.c

I have already tried this command: cp A/.* B from this other superuser question. However, it does not copy the subdirectories. Also tried cp -r A/.* B, but it copies . so I end with an exact copy of A (including the normal files). Any help is appreciated.

gaboroncancio

Posted 2014-10-23T13:15:50.310

Reputation: 275

Answers

16

As long as you're only looking for hidden files and folders at the level of A and don't want, for example

A/b/.hidden

to be copied, you should be able to use this:

cp -r A/.[^.]* B

It basically means copy anything that starts with a . and then any character other than a . That filters out . and ..

Edit: Removed the -p from the cp command since Asker hasn't indicated he wants to preserve any ownerships, dates, etc.

Omnipresence

Posted 2014-10-23T13:15:50.310

Reputation: 569

This works for the example file and directory names given in the question, but the text of the question says “hidden files and hidden subdirectories (the ones starting with a dot)”, and this answer will not find files and directories whose names begin with two dots; e.g., ..c. – Scott – 2014-10-27T22:27:34.170

That's quite an edge case, but a legitimate concern none-the-less. I hadn't considered that. You could account for that by switching to .*[^.] but then you'd miss files that end with a .. I think you would indeed need extended globbing to truly account for all cases. – Omnipresence – 2014-10-28T13:08:34.390

4

The problem with A/.* is that there is the directory . in A which also matches the pattern.

You can turn on extended glob patterns and use the following:

shopt -s extglob
cp -r A/.!(?(.)) B    

It matches files whose name starts with a dot and whose second character is neither a dot nor nothing ( ?(.) matches nothing or a dot, !(...) negates it, i.e. !(?(.)) matches everything else than nothing or a dot).

choroba

Posted 2014-10-23T13:15:50.310

Reputation: 14 741

+1 for a correct answer. Note that .!(@(|.)) is (AFAICT) equivalent to the above, (IMNSHO) a little clearer, and only one character longer. – Scott – 2014-10-27T22:28:26.757

3

For cases like this would recommend using find instead of cp like this:

find A/ -type f -maxdepth 1 -name '.*' -exec cp -p {} B/ \;

The basic syntax breaks down like this:

  • find A/ -type f: find items in the directory A/ whose type is a file (instead of a directory)…
  • -maxdepth 1 -name '.*': To this for a maxdepth of 1 directories and whose name begins with ..
  • -exec cp -p {} B/ \;: And once these files are found, exec the cp command with a -p flag to preserve dates/times from the source ({}) to the destination of B/.

I like using maxdepth to add a layer of control so I am not accidentally copying a whole filesystem. But feel free to remove that.

JakeGould

Posted 2014-10-23T13:15:50.310

Reputation: 38 217

0

 for item in `find A -type d | grep -E "\."` ; do cp -r $item B ; done
  • find A -type d provides a recursive list within A with only directories
  • grep -E "\." filters directories with a dot (i.e.: hidden directories)
  • the -E option was needed here because without it it means "current directory" as well
  • the backslash is to avoid the meaning, under regexp, of "any character"
  • cp -r to copy recursively

I have created the files and folders structure for A and executed the command in Git Bash (I'm not with a linux just right now) and it worked.

malarres

Posted 2014-10-23T13:15:50.310

Reputation: 188

This breaks if files have whitespace or special characters in their name or path. – slhck – 2014-10-23T16:30:47.310

Thanks for noticing :) Actually I limitied to the "test case" by @gaboroncancio. If you can give me other test battery I may try to improve that (of course if you want, improve it by yourself either editing this response or creating a new answer) – malarres – 2014-10-24T10:39:23.497

You could simply put the dotfiles in a folder called A B, and then it'd act unexpectedly because it'd expand to cp -r A B/.dotfile B. The general advice is not to parse find or ls output at all. If you use find you should also use its own options for filtering rather than grep, and if you pipe find output somewhere else, use -print0, or directly call the command you want. See the find manual.

– slhck – 2014-10-24T10:45:26.653

Even more generally, when working with files it's safest to use shell globs as explained in other answers (although they often require extglob to be set). – slhck – 2014-10-24T10:46:22.023

Thanks for the link. Let's leave the find parsing then. – malarres – 2014-10-27T07:22:01.283

Even aside from the badness of parsing the output of find, (1) This works for the example file and directory names given in the question, but this answer will include any name that *contains* a dot (e.g., somefile.c), i.e., treating it as a hidden file. (2) Speaking of files, the question says “hidden files and hidden subdirectories” – so why are you saying -type d? (3) Why are you giving grep the -E option? I tried it and got the same results with and without it. – Scott – 2014-10-27T22:29:03.320

(1) yes, you're right. maybe using ^..* for grep would work better (2) As the desired output included B/.b/somefile I supposed that @gaboroncancio wanted every file (hidden or not) that was inside a hidden directory.(3) without the -E option my grep includes A directory itself (ymmv, i worked with git bash) – malarres – 2014-10-28T09:38:29.493

(1) You rarely need .* in regexs for grep unless it’s between two things, as in foo.*bar. Your suggestion of ^\. is a good start, but consider: find’s output will look like A, A/a, A/b, A/.a, A/.b, … – so you might want to use /\.. (2) My point was that the question shows an example file of A/.a, and that your command would miss that (because the -type d would filter out the plain file A/.a, and the grep would filter out the A). But it occurs to me that, if you deleted the -type flag, you could end up copying the A/.b directory *and* the A/.b/.c file. – Scott – 2014-10-28T23:23:21.500

0

As an alternative you can use this other command if the second character is alphanumeric (source):

cp -r A/.[a-zA-Z0-9]* B

gaboroncancio

Posted 2014-10-23T13:15:50.310

Reputation: 275

This works for the example file and directory names given in the question, but the text of the question says “hidden files and hidden subdirectories (the ones starting with a dot)”, and this answer will not find files and directories whose names begin with a dot and a special character; e.g., .@foo or ..c. – Scott – 2014-10-27T22:29:52.097

That is why I point that it works if the second character is alphanumeric. ;) – gaboroncancio – 2014-10-27T22:41:18.277