Suppose that I have a directory with thousands of files; how could I open all files whose name contains both the strings "red" and "green"?
-
File names, or contents? Why a regex when a glob (for filenames) would do? – Jeff Schaller Jun 02 '16 at 00:50
-
What's your criteria for 'best'? Shortest command? Simplest syntax ? Posix/portable? fastest? – Jeff Schaller Jun 02 '16 at 00:51
-
when you say "red and green" do you mean "red" files along with "green" files or "files that have red and green" ? – Jeff Schaller Jun 02 '16 at 01:02
-
Sorry, I should have clarified file names! – James Shapiro Jun 02 '16 at 03:31
-
1Please do not respond in comments; [edit] your question to make it clearer and more complete. – Scott - Слава Україні Jun 02 '16 at 04:54
4 Answers
find . -type f -name '*red*' -name '*green*' -exec echo {} +
Replace echo
with vi
, gvim
, less
or whatever command you want to open the files with.
Note, some programs can not handle multiple filenames on the command line. If your chosen program is one of those, replace the +
at the end of line with \;
. This will run the command once for each filename, rather than just once with a long list of filenames.
xdg-open
is one such command. It detects what kind of file it is being asked to open and then use either the system default program (or your preferred program if you've overridden the default) to open the file. If it doesn't recognise the file's type, it will ask you what to open it with.
Using it with find
like this is kind of useful and kind of annoying at the same time. Try it and you'll see why (you'll see lots of windows opening, one after another...one for each matching filename).
find . -type f -name '*red*' -name '*green*' -exec xdg-open {} \;
BTW, if you wanted to find files matching *red*
OR *green*
, you'd have to use parentheses to specify precedence for the "and" (-a
) and "or" (-o
) operators. BTW, when you run find
with multiple predicates as above, the default operator is a -a
, i.e. the predicates are logically AND-ed together.
Parentheses also have a special meaning to the shell, so have to be backslash-escaped so they are passed on to the find command rather than interpreted by the shell.
find . -type f \( -name '*red*' -o -name '*green*' \) -exec echo {} +
Read this as "a regular file AND (a filename matching either '*red*' OR '*green*')"

- 78,579
With zsh
:
set -o extendedglob # best in ~/.zshrc
ls -ld -- *green*~^*red*
The ~
glob operator is for except (and not). ^
is for not. So the combination is and not not, so and. IOW, that matches files with green
in their names, except those that do not contain red
.
An alternative using the ^
negation operator (also requires extendedglob
):
ls -ld -- ^(^*green*|^*red*)
everything but the files that either don't contain green, or don't contain red.
To also include hidden files, add the (D)
glob qualifier.
You can also use glob qualifiers to match file names against regexps, and with setopt rematchpcre
, you can make those regexps PCRE ones, where you can use look ahead operators to achieve and:
re() {
setopt localoptions rematchpcre
[[ $REPLY =~ $1 ]]
}
ls -ld -- *(e['re "^(?=.*green).*red"'])
Or:
re() {
setopt localoptions rematchpcre
[[ $REPLY =~ $RE ]]
}
RE='^(?=.*green).*red'; ls -ld -- *(+re)
(with the caveat that PCRE chokes on non-text strings, and does not support all multibyte text encodings).
With ksh
or bash -O extglob
or zsh -o kshglob
ls -ld -- !(!(*green*)|!(*red*))
!(pattern)
being the ksh
equivalent of zsh
's ^pattern
extended glob operator.
You could also do:
ls -ld -- @(*green*red*|*red*green*)
But that's not generalisable to any pattern. For instance for files containing abc
and bcd
, you'd need @(*abc*bcd*|*abcd*|*bcd*abc*)
, for file matching ???
and *x*
, @(x??|?x?|??x)
...
With ksh93
ksh93
has some and globbing operators:
ls -ld -- @(*green*&*red*)
Or using its augmented regexps (introduced with the ~(A:...)
glob operator):
ls -ld -- ~(A:.*green.*&.*red.*)
You can also use perl-like
look ahead operators in ksh93
:
ls -ld -- ~(P:(?=.*green).*red.*)

- 544,893
(On the assumption that you're looking for file names that contain both the string "red" and the string "green")
To pointlessly use bash to test filenames against 'red' and 'green' using the =~
regular expression match operator:
for f in *
do
[[ $f =~ red && $f =~ green ]] && echo Bash: yes: "$f" || echo Bash: no: "$f"
done
To use standard shell syntax using case
and globs to find the same files:
for f in *
do
case "$f" in
*green*red*|*red*green*) echo Case: yes: $f;;
*) echo Case: no: $f;;
esac
done
Even simpler, with your shell's globbing:
printf "Glob: %s\n" *green*red* *red*green*
Sample run:
$ touch a b aredgreenb agreenredb agreenbred aredbgreen red green redgreen greenred
$ for f in *
> do
> [[ $f =~ red && $f =~ green ]] && echo Bash: yes: "$f" || echo Bash: no: "$f"
> done
Bash: no: a
Bash: yes: agreenbred
Bash: yes: agreenredb
Bash: yes: aredbgreen
Bash: yes: aredgreenb
Bash: no: b
Bash: no: green
Bash: yes: greenred
Bash: no: red
Bash: yes: redgreen
$ for f in *
> do
> case "$f" in
> *green*red*|*red*green*) echo Case: yes: $f;;
> *) echo Case: no: $f;;
> esac
> done
Case: no: a
Case: yes: agreenbred
Case: yes: agreenredb
Case: yes: aredbgreen
Case: yes: aredgreenb
Case: no: b
Case: no: green
Case: yes: greenred
Case: no: red
Case: yes: redgreen
$ printf "Glob: %s\n" *green*red* *red*green*
Glob: agreenbred
Glob: agreenredb
Glob: greenred
Glob: aredbgreen
Glob: aredgreenb
Glob: redgreen

- 67,283
- 35
- 116
- 255
-
Thanks this is very helpful!
open *red*green* *green*red*
did the trick and was exactly what I was looking for!
– James Shapiro Jun 02 '16 at 03:40 -
1If there is a file called
brightly_colored_green,and_having_a_high_credit_rating
(or simplyred_green_red
), your*red*green* *green*red*
approach will process it twice. – Scott - Слава Україні Jun 02 '16 at 05:13 -
Scott's absolutely right; the glob solutions will also (by default, without the dotglob option being set) also omit files like
.greenred
. – Jeff Schaller Jun 02 '16 at 11:09
Simple, assuming that the open
command can handle multiple args:
open *red* *green*
Edit: sorry I was reading the question as opening (files with red in the name) and (files with green in the name); my mistake.
The following uses extglob and avoids the double-count in the case of files named redgreenred
:
$ shopt -s extglob # turn on extended globbing
$ printf "extglob: %s\n" @(*green*red*|*red*green*)
extglob: agreenbred
extglob: agreenredb
extglob: aredbgreen
extglob: aredgreenb
extglob: greenred
extglob: redgreen
extglob: redgreenred
$ shopt -u extglob # turn off extended globbing

- 1,501