Bash pattern to match all files but directories

In bash, how do I specify a pattern that matches everything but subdirectories in current directory. Given that the pattern */ matches all the subdirectories, I tried (with extglob turned on):

$ echo !(*/)

But it didn’t work.

Asked By: York

||

Sort of long winded, but:

for f in *; do if [ ! -d "$f" ]; then echo "$f"; fi; done

-d is a file test operator to check if the argument is a directory.

The above could also be shortened to

for f in *; do [ ! -d "$f" ] && echo "$f"; done
Answered By: goldilocks
find . -maxdepth 1 ! -type d

Details:

  • -maxdepth 1 restricts the search to the current directory

  • ! -type d eliminates directories

Answered By: John1024

The reason */ matches directories is that the final / restricts matches to directories. This effect is only triggered when the / is after a pattern, you can’t use / inside parentheses in !(*/). There’s no feature built into bash to do what you want.

You can make a loop over all files and build an array.

non_directories=()
for x in *; do
  [ -d "$x" ] || non_directories+=("$x")
done
somecommand "${non_directories[@]}"

You can also use find, if you want to execute a command over all files. If your find implementation supports it (GNU, BSD, BusyBox), use -mindepth and -maxdepth to list only entries in the current directories (with ./ prepended). Use ! -name '.*' to omit dot files (if you indeed want to omit them).

find . -mindepth 1 -maxdepth 1 ! -type d -exec somecommand {} +

If you can only assume a POSIX find, use -prune to avoid recursing.

find . -name . -o -type d -prune -o -exec somecommand {} +

If you want to stuff the output of find into an array variable, beware that parsing the output of find requires additional assumptions on the file name. You’re better off with a loop.

In zsh, you can use glob qualifiers: *(^/), or *(.) to match only regular files.

I’m not sure which of the 2 options to you want to achieve – bash globing that excludes based on filesystem attributes, or a way to display only files in a directory?

If the first, I’m not sure that’s possible. Globing just expands, and filenames are a base in any directory – bash doesn’t distinguish between a file name or a dirname on it’s own.

If the second – you already got 2 answers, another one using ls:

ls -dF * | grep -v "/$"  
Answered By: Dani_l

My suggestion:

GLOBIGNORE=$(echo */)            # create list of directories with globbing
GLOBIGNORE=${GLOBIGNORE//:/\:}  # escape possible ":" with "" to allow
                                 # the separator ":" in directory names
GLOBIGNORE=${GLOBIGNORE/// /:}  # replace "/ " with separator ":" 
GLOBIGNORE=${GLOBIGNORE%/}       # remove trailing "/"
ls -ld *

Result: no directories

Back to default with unset GLOBIGNORE

Answered By: Cyrus
Categories: Answers Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.