15

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.

5 Answers5

23

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.

  • 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 - Слава Україні Oct 27 '14 at 22:27
  • 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 Oct 28 '14 at 13:08
5

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
  • 19,261
  • 4
  • 49
  • 52
5

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.

Giacomo1968
  • 55,001
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.

Jan Doggen
  • 4,218
malarres
  • 208
  • This breaks if files have whitespace or special characters in their name or path. – slhck Oct 23 '14 at 16:30
  • 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 Oct 24 '14 at 10:39
  • 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 Oct 24 '14 at 10:45
  • 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 Oct 24 '14 at 10:46
  • Thanks for the link. Let's leave the find parsing then. – malarres Oct 27 '14 at 07:22
  • 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 - Слава Україні Oct 27 '14 at 22:29
  • (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 Oct 28 '14 at 09:38
  • (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 - Слава Україні Oct 28 '14 at 23:23
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
  • 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 - Слава Україні Oct 27 '14 at 22:29
  • That is why I point that it works if the second character is alphanumeric. ;) – gaboroncancio Oct 27 '14 at 22:41