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?

5
  • 6
    ./.* will include ./. and ./.., which is why you get that evil recursivity covering your entire system in the first place. Commented Mar 26, 2015 at 14:09
  • so ./.* would have been ending up in the same disaster, if I got you right?
    – g000ze
    Commented Mar 26, 2015 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... Commented Mar 26, 2015 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
    Commented Mar 26, 2015 at 14:21
  • 2
    Related: Does 'rm .*' ever delete the parent directory? rm specifically protects against this; commands like chmod and chown do not. Commented Mar 26, 2015 at 22:39

7 Answers 7

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_-]*
11
  • 13
    Another more common alternative: .??* This will include digits and other signs which may end up in file names :) Commented Mar 26, 2015 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
    Commented Mar 26, 2015 at 14:36
  • 20
    Why not just use .[^.]*?
    – Cole Tobin
    Commented Mar 26, 2015 at 18:08
  • 6
    This doesn't get files with names like ......
    – user20574
    Commented Mar 27, 2015 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
    Commented Mar 28, 2015 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 {} +
3
  • 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.
    – null
    Commented Mar 27, 2015 at 15:22
  • @xfix I'd checked with fish 1.23.1. I wasn't aware that the behavior had changed, thanks. Commented Mar 27, 2015 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
    Commented Apr 1, 2015 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
2
  • Unless I'm misunderstanding, the question specifies that only hidden directories are desired to be chowned, whereas this chowns everything.
    – Chris Down
    Commented Mar 26, 2015 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. Commented Mar 26, 2015 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 {} +
2
  • Yeah, this seems to be the cleanest solution, although not the shortest.
    – Ruslan
    Commented Mar 28, 2015 at 8:32
  • If for some reason you don't want to modify the current directory, use -mindepth 1before -maxdepth 1. Commented Mar 28, 2015 at 15:17
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.

6
  • .* would work with files with funky names. Commented Mar 27, 2015 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
    Commented Mar 27, 2015 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). Commented Mar 27, 2015 at 1:01
  • Withdrawn, clearly I'm confused :) That's what I get for answering questions on my phone.
    – dimo414
    Commented Mar 27, 2015 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. Commented Mar 28, 2015 at 15:14
0

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 {} +

Not the answer you're looking for? Browse other questions tagged .