15

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 ..?

2 Answers 2

21

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
    
0
6

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 {} \;

You must log in to answer this question.

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