22

Someone of our team wanted to recursively change the user permissions on all hidden directories in a users home directory. To do so he executed the following command:

cd /home/username
chown -R username:groupname .*

We were pretty surprised when we realized, that he actually recursively changed the permissions of all user directories in /home, because .* equals to .. as well. Would you have expected this behavior in Linux though?

marked as duplicate by Gilles bash Jul 12 '15 at 21:32

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 6
    ./.* will include ./. and ./.., which is why you get that evil recursivity covering your entire system in the first place. – John WH Smith Mar 26 '15 at 14:09
  • so ./.* would have been ending up in the same disaster, if I got you right? – g000ze Mar 26 '15 at 14:13
  • 1
    Have a look at ls -d ./.*. It includes .., the parent directory. And this directory would include its parent as well, and so on... Run ls --recursive ./.* and you'll see it... – John WH Smith Mar 26 '15 at 14:15
  • You are right John, in this case I am going to edit my initial question and remove the wrong conclusion we made. Thank you... – g000ze Mar 26 '15 at 14:21
  • 2
    Related: Does 'rm .*' ever delete the parent directory? rm specifically protects against this; commands like chmod and chown do not. – Scott Mar 26 '15 at 22:39
17

I always get burned when I try using .* for anything and long ago switched to using character classes:

chown -R username.groupname .[A-Za-z]*

is how I would have done this.

Edit: someone pointed out that this doesn't get, for example dot files such as ._Library. The catch all character class to use would be

chown -R username.groupname .[A-Za-z0-9_-]*
  • 13
    Another more common alternative: .??* This will include digits and other signs which may end up in file names :) – John WH Smith Mar 26 '15 at 14:03
  • 2
    .??* misses any single character name dot files; e.g. .a. Admittedly such would be unlikely to exist, but it's worth pointing out. – pgoetz Mar 26 '15 at 14:36
  • 20
    Why not just use .[^.]*? – Cole Johnson Mar 26 '15 at 18:08
  • 6
    This doesn't get files with names like ...... – immibis Mar 27 '15 at 2:45
  • 1
    @trlkly: 1. POSIX regexes are different from (say) Perl-style ones. 2. But [^.] does have the appropriate meaning in a POSIX regex. 3. But POSIX filename-expansion doesn't use the same rules as POSIX regex. (If it did, then .* would mean "zero or more characters" rather than "a dot, plus zero or more characters".) 4. And in POSIX filename-expansion, you have to write [! rather than [^ -- the meaning of the latter is unspecified. 5. But the question specifically calls out Bash, not a generic POSIX shell, and Bash filename-expansion does support [^ as equivalent to [!. – ruakh Mar 28 '15 at 2:02
10

Using the extended globbing (shopt -s extglob), you can use

.!(.|)

i.e. dot not followed by dot or nothing.

10

The character . is only excluded from wildcard matching when it's the first character of the file name and it would be matched by a wildcard. In the pattern .*, the * matches strings beginning with ., so .* includes .. (as well as ., with * matching the empty string). This is a straightforward consequence of the pattern matching rules, annoying though it may be.

It would make sense to make an exception and to systematically exclude . and .. from matches, but that's not how it was done historically, so many Bourne/POSIX shells (sh, dash, bash, AT&T ksh, yash …) include them, as do (t)csh and even fish 1.x. A few shells exclude . and .. from all wildcard matches: zsh, pdksh/posh/mksh (unlike AT&T ksh), fish ≥2.0.

If you set GLOBIGNORE to any non-empty value, bash switches to the convenient but non-standard behavior of excluding . and .. from matches. Setting GLOBIGNORE also turns off the behavior of excluding dot files; with GLOBIGNORE='.*', you get the usual behavior of ./* excluding dot files, but ./.* matches only dot files and not . or ... Set GLOBIGNORE=.:.. (or GLOBIGNORE=.) to have ./* match all files, including dot files, but excluding . and ...

In ksh93, set FIGNORE='@(.|..)' to exclude . and .. from matches but include dot files. Thus .* will expand to dot files but not include . or ...

Without resorting to shell-specific features, you can match dot files with the following two globs:

.[!.]* ..?*

and all files (excluding . and ..) with the following three globs:

..?* .[!.]* *

But you need to take care because one or several of the globs might not match any file, which would cause the corresponding pattern to remain unexpanded.

To avoid surprises, it might be easier to use find. find never recurses to the parent directory (unless told to follow symbolic links).

find /home/username/. -name . -o -prune -name '.*' -exec chown -R username:groupname {} +
  • I'm not sure if I follow. Fish shell since 2.0 doesn't include . and .. in .*. If it does include them, it's a bug, and should be reported. – Konrad Borowski Mar 27 '15 at 15:22
  • @xfix I'd checked with fish 1.23.1. I wasn't aware that the behavior had changed, thanks. – Gilles Mar 27 '15 at 15:42
  • FWIW, the shorter and (IMO) slightly easier-to-understand .[!.] .??* can be used instead of .[!.]* ..?*. Analysis of these glob patterns (all of them start with a single dot and do not match . or ..): .[!.] = exactly 2 chars, 2nd is non-dot; .??* = 3+ chars; .[!.]* = 2+ chars, 2nd char is non-dot; ..?* = 3+ chars, 2nd char is dot. – jrw32982 Apr 1 '15 at 13:48
6

If the directory itself shares the same ownership as its files (hidden or not), then you can chown it recursively instead. The -R option will include hidden files when recursing inside the current directory.

$ chown user:group . -R # Will include all hidden files
  • Unless I'm misunderstanding, the question specifies that only hidden directories are desired to be chowned, whereas this chowns everything. – Chris Down Mar 26 '15 at 16:22
  • @ChrisDown Yes, but since the question and most answers provide code including both files and directories, I thought it'd still be of interest. – John WH Smith Mar 26 '15 at 17:06
6

Consider using find (-maxdepth is a non-POSIX extension, but it should be readily available on Linux):

find . -maxdepth 1 -type d -name '.*' -exec chown -R user:group {} +
  • Yeah, this seems to be the cleanest solution, although not the shortest. – Ruslan Mar 28 '15 at 8:32
  • If for some reason you don't want to modify the current directory, use -mindepth 1before -maxdepth 1. – Franklin Piat Mar 28 '15 at 15:17
1

A variation of Chris Down solution that filter just hidden directories and removes the -R options. Your original requirement was to change ownership and group classification of hidden directories, not their content.

find /home/username -maxdepth 1 -type d -name '.*' -exec chown user:group {} +
1

I'd think you could use ls -A instead, specifically:

chown -R username:groupname $(ls -A | grep '^\.')

This does what you'd expect .* to do, match all files in the current directory that begin with a ., excluding . and ... But note this won't behave identically to a bash glob if you need it to match funky file names, like files with spaces in them.

  • .* would work with files with funky names. – Gilles Mar 27 '15 at 0:23
  • @Gilles not unless you did ".*" or some other escaping. As is, the command in the question would also fail to properly handle filenames with spaces and the like, my answer just does the same. – dimo414 Mar 27 '15 at 0:48
  • No, ".*" would pass the two-character string .* to the command. .* (unquoted) expands to the list of file names starting with a ., no matter what characters the file names contain. I'm not sure what your misapprehension here is; keep in mind that a command line is not a string but a list of strings, and a wildcard pattern expands to the list of matches, not to their concatenation with spaces in between or something (it's commands like echo that do a concatenation with spaces as separator). – Gilles Mar 27 '15 at 1:01
  • Withdrawn, clearly I'm confused :) That's what I get for answering questions on my phone. – dimo414 Mar 27 '15 at 1:18
  • Anything which involves mycommand $(ls) is a bad idea. This syntax leads to unexpected results for lots of characters like: space, semi-column and many more. find /path/to/dir -print0 | xargs -0 mycommand or mycommand * (where * is a glob expression) are you friends. – Franklin Piat Mar 28 '15 at 15:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.