2

I wanted to copy all dotfiles from my ~ folder to a git repo to back them up, and I'm using ZSH.

I came across this command that seems to work:

cp -a ~/.[^.]* . - where the final . is the git dir.

I don't understand how this works. Can anyone give me a guide, or tell me what to google to learn more?

I tried ZSH + [^] + globbing

2 Answers 2

7

That syntax is for some shells other than zsh and even there, it would be wrong.

.[^.]* matches on file names that start with . followed by a character other than ., followed by 0 or more characters.

That's the kind of syntax you'd need in shells that include . and .. in the expansion of .*.

. and .. are navigating tools used to refer to the current and parent directories respectively. They have no place in glob expansions as globs are tools to generate lists of actual files¹. Still, historically shells have been including them in their glob expansions as they were being reported by readdir().

zsh, like the Forsyth shell and its descendants (pdksh, mksh, OpenBSD sh...) or the fish shell have fixed that and never include . nor .. in the result of filename generation², even in globs like:

$ echo (.|..)
zsh: no matches found: (.|..)

It's also wrong in the general case, as it misses files like ..foobar.

Also note that [^.], though supported by many shell, is not standard POSIX syntax.

In POSIX sh syntax, you'd need:

cp -a ~/.[!.]* ~/..?* .

(where we add ..?* which matches on .. followed by one or more characters, to cover for the ..foobar type of file names mentioned above).

In zsh (and those other shells mentioned above), you only need:

cp -a ~/.* .

Hopefully, that will eventually be allowed/recommended for sh by POSIX and we'll see more other shells follow suit.


¹ On a history note, and according to legend, the concept of files whose name starts with . being hidden originates in a bug in an early version of the ls utility in the 70s which ended up causing all filenames starting with . to be hidden when the intent was only to hide . and ... That bug became a feature when people started to rely on it to hide files

² Bash added a globskipdots option for that in the 5.2 version.

0
1

Let's split it up in parts

  • cp -a: copy everything in ~/.[^.]* and the dirs below it to .
    With -a you are telling 'Also copy the subdirectories and try to keep all properties of the files and dirs the same' . See man cp for more info
  • ~/.[^.]*: All hidden files and dirs in your home dir because:
    • ~ is your homedir
    • * means 'this can be anything' so ~/* becomes: all files/dirs in your homedir
    • ~/.* becomes: all files in your homedir starting with dot. On first sight this is the same as saying 'all hidden files and dirs' but this is not the case because you also have the dirs . (current dir) and .. (parent dir) and you don't want them
    • [^.] Means 'every possible character except the . so this fixes the problem with . and ..

So in total this command says : Copy all hidden files and dirs in your home directory to the current directory

1
  • But as I said in my answer, ~/.[^.]* is wrong as it's omitting other files beside . and .. and also in zsh specifically which the OP is using, it's unnecessary. Commented Jul 29, 2020 at 8:55

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.