3

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

James Shapiro
  • 213
  • 3
  • 6

4 Answers4

5
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*')"

cas
  • 78,579
5

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.*)
1

(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
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
0

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
thomas_d_j
  • 1,501