Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Recently I had a little mishap caused by a shell pattern that expanded in an unexpected way. I wanted to change the owner of a bunch of dot files in the /root directory, so I did

chown -R root .*

Naturally, the .* expanded to .. which was a bit of a disaster.

I know in bash this behaviour can be changed by tweaking some shell option, but with the default settings is there any pattern that would expand to every dot file in the directory but not to . and ..?

share|improve this question
add comment

2 Answers

up vote 17 down vote accepted

Bash, ksh and zsh have better solutions, but in this answer I assume a POSIX shell.

The pattern .[!.]* matches all files that begin with a dot followed by a non-dot character. (Note that [^.] is supported by some shells but not all, the portable syntax for character set complement in wildcard patterns is [!.].) It therefore excludes . and .., but also files that begin with two dots. The pattern ..?* handles files that begin with two dots and aren't just ...

chown -R root .[!.]* ..?*

This is the classical pattern set to match all files:

* .[!.]* ..?*

A limitation of this approach is that if one of the patterns matches nothing, it's passed to the command. In a script, when you want to match all files in a directory except . and .., there are several solutions, all of them cumbersome:

  • Use * .* to enumerate all entries, and exclude . and .. in a loop. One or both of the patterns may match nothing, so the loop needs to check for the existence of each file. You have the opportunity to filter on other criteria; for example, remove the -h test if you want to skip dangling symbolic links.

    for x in * .*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
    
  • A more complex variant where the command is run only once. Note that the positional parameters are clobbered (POSIX shells don't have arrays); put this in a separate function if this is an issue.

    set --
    for x in * .[!.]* ..?*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      set -- "$@" "$x"
    done
    somecommand "$@"
    
  • Use the * .[!.]* ..?* triptych. Again, one or more of the patterns may match nothing, so we need to check for existing files (including dangling symbolic links).

    for x in * .[!.]* ..?*; do
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
    
  • Use the * .[!.]* ..?* tryptich, and run the command once per pattern but only if it matched something. This runs the command only once. Note that the positional parameters are clobbered (POSIX shells don't have arrays), put this in a separate function if this is an issue.

    set -- *
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- .[!.]* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- ..?* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    somecommand "$@"
    
  • Use find. With GNU or BSD find, avoiding recursion is easy with the options -mindepth and -maxdepth. With POSIX find, it's a little trickier, but can be done. This form has the advantage of easily allowing to run the command a single time instead of once per file (but this is not guaranteed: if the resulting command is too long, the command will be run in several batches).

    find . -name . -o -exec somecommand {} + -o -type d -prune
    
share|improve this answer
    
+1: nice, clear and thorough [and quite clever]. –  Olivier Dulac Jan 2 at 11:56
    
There's a bug in your answer: The "A more complex variant" will pass *, .[!.]*, and/or ..?* to somecommand if those patterns don't match any file. I think you should replace case $x in .|..) continue;; esac with [ -e "$x" ] || continue (like your third bullet point). –  Richard Hansen Jan 8 at 19:07
    
@RichardHansen Right, thanks, I've fixed this. I also fixed another bug: test -e skips dangling symlinks. –  Gilles Jan 8 at 19:16
add comment

This works if all of the filenames contain at least three characters (including the dot):

chown -R root .??*

For a more robust solution, you can use find:

find . -maxdepth 1 -name '.*' -exec chown -R root {} \;
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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