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?

g000ze
  • 343

7 Answers7

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_-]*
pgoetz
  • 732
  • 9
  • 18
  • 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 Tobin Mar 26 '15 at 18:08
  • 6
    This doesn't get files with names like ...... – user253751 Mar 27 '15 at 02:45
  • @ColeJohnson Does POSIX let you get away with that? I remember POSIX regex is different from regular regex. – trlkly Mar 27 '15 at 03:48
  • 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 02:02
  • @Izkata: Not exactly. It's complicated. See my comment. (Or alternatively, see http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01: "the exclamation mark character ('!') shall replace the circumflex character ('^') in its role in a 'non-matching list'".) – ruakh Mar 28 '15 at 02:04
  • @ruakh: The syntax used in pathname expansion is usually called glob as it is significantly different from most things called regex. – Nate Eldredge Mar 28 '15 at 07:32
  • @NateEldredge: Yup, I'm aware. (Hence extglob, globstar, etc.) But the specs don't seem to use that term; they just say "filename expansion", so that's what I stuck with in my comment. (By the end of #5, I was really nearing the character limit -- no room for any unnecessary facts. :-P ) – ruakh Mar 28 '15 at 15:30
  • This is the practical answer for interactive use, but doesn't handle the general case with unusual names (see @gilles answer below for that). The edit to add 0-9, etc was unnecessary: to use this technique you always need to make sure (visually) that you're handling all the actual file names that exist in your particular situation. This method shouldn't be used for a programmatic solution. – jrw32982 Apr 01 '15 at 13:55
10

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

.!(.|)

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

choroba
  • 47,233
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. – 0.. 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 'SO- stop being evil' 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 01 '15 at 13:48
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 {} +
Chris Down
  • 125,559
  • 25
  • 270
  • 266
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
John WH Smith
  • 15,880
  • 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
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.

dimo414
  • 1,797
  • .* would work with files with funky names. – Gilles 'SO- stop being evil' Mar 27 '15 at 00: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 00: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 'SO- stop being evil' Mar 27 '15 at 01:01
  • Withdrawn, clearly I'm confused :) That's what I get for answering questions on my phone. – dimo414 Mar 27 '15 at 01: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
  • @FranklinPiat good advice, thanks. The $(ls) syntax is easy to remember and usually works, so I'll leave my answer here, but caveats definitely apply. – dimo414 Mar 29 '15 at 21:42
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 {} +
fd0
  • 1,449