5

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.

  • A globbing pattern will match any name, it can not be made to distinguish between files and directories by itself. – Kusalananda Jun 20 '16 at 19:22
6
find . -maxdepth 1 ! -type d

Details:

  • -maxdepth 1 restricts the search to the current directory

  • ! -type d eliminates directories

  • another variant that i use frequently is find . -maxdepth 1 -type f this only matches regular files – hildred Jul 14 '14 at 12:42
6

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.

  • What does the -mindepth 1 do in your first example? You are already excluding directories with the ! -type d, is there any point to using -mindepth as well? Also, your -prune example won't work, did you mean -type f? – terdon Jul 14 '14 at 12:00
  • @terdon I included -mindepth 1 as a force of habit for a non-recursive use of find. I guess it doesn't hurt and if I left it out one could arguably wonder why. The second find command was a typo (braino?) of mine: -name . has to go first, otherwise . would end up pruned. – Gilles Jul 14 '14 at 14:23
3

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
  • Correct answer and the link provided was really nice. Thanks. – York Jul 13 '14 at 22:03
2

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

2

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 "/$"  

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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