14

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
  • 355
  • 2
  • 3
  • 10

5 Answers5

22

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
  • 659
  • 3
  • 6
  • 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).

gaboroncancio
  • 355
  • 2
  • 3
  • 10
choroba
  • 18,638
  • 4
  • 48
  • 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
  • 53,069
  • 19
  • 162
  • 212
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,108
  • 10
  • 36
  • 51
malarres
  • 208
  • 1
  • 3
  • 12
  • 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](http://mywiki.wooledge.org/ParsingLs) 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](http://www.gnu.org/software/findutils/manual/html_mono/find.html#Multiple-Files). – 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
gaboroncancio
  • 355
  • 2
  • 3
  • 10
  • 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