92

How can I introduce a conditional OR into grep? Something like, grepping a file's type for (JPEG OR JPG), and then sending only those files into the photos folder. For example. I know how to send the file where I want it, and get the file type, I just need some help with the grep part.

I'm on OS X, which IMO seems to have modified/customized *nix utilities than what I'm used to in a *nix environment. So hopefully the answers can be as generic/portable as possible.

4
  • 4
    One might argue that OSX has the more *nix binaries since it is mostly BSD based. I suspect you are far more used the gnu tools.
    – Zoredache
    Dec 1, 2011 at 0:42
  • @Zoredache Uhh .. sure! What you said! I am most familiar with Debian, though I have dabbled with Slackware, CentOS, Gentoo, Ubuntu, and a few others. Debian is my favourite, I almost can't stand Gentoo and Ubuntu, and Slackware I find to be a bit too unfriendly for me. I tend to break stuff. Great way to learn, though.
    – Harv
    Dec 1, 2011 at 1:41
  • You don't need regexes if you just want to grep for different fixed patterns. Just use the -e parameter for each pattern you want to match, or one -F if you want to supply them as a newline separated list, or -f if you want to read them from a file (see man grep). May 25, 2014 at 21:54
  • @Jeff Schaller my post was flagged as a supplicate, but the other one's a different question.
    – Harv
    Apr 12, 2018 at 1:47

5 Answers 5

123

If you want to introduce a OR in grep, introducing it with REGEXP is the wrong way. Try the -e option to grep instead:

grep -e PATTERN1 -e PATTERN2 your_file

will match lines with PATTERN1 or PATTERN2.

1
  • 1
    grep -E "ATTN|MASS|BOND|ANGLE|DIHE|IMPROPER" temp.dat and grep -e ATTN -e MASS -e BOND -e ANGLE -e DIHE -e IMPROPER temp.dat are the same.
    – BARIS KURT
    May 15 at 23:49
44

I'm also fairly new to regex, but since noone else has answered I'll give it a shot. The pipe-operator "|" is used for an OR operator.

The following REGEX should get you going somewhere.

.+((JPG)$|(JPEG)$)

(Match anything one or more times followed by either "JPG" or "JPEG" at the end)

Extended answer (editted after learning a bit about (e)grep): Assuming you have a folder with following files in them:

test.jpeg, test.JpEg, test.JPEG, test.jpg, test.JPG, test.notimagefile, test.gif

(Not that creative with names...)

First we start by defining what we know about our pattern: We know that we are looking for the end of the name. Ergo we use the "$" operand to define that each line has to end with the defined pattern. We know that the pattern needs to be either JPEG or JPG. To this we use the pipeline "|" as an or operand. Our pattern is now:

((JPEG)|(JPG))$

(Match any line ending with EITHER "JPEG" or "JPG")

However we see that in this example, the only difference is the optional "E". To this we can use the "?" operand (meaning optional). We write:

(JP(E)?G)$

(Mach any file ending with a pattern like: "J", followed by "P", followed by an optional "E", followed by a "G").

However we might also like to match files with lowercase letters in file name. To this we introduce the character-class "[...]". meaning match either of the following. We write:

([jJ][pP]([eE])?[gG])$

(Match any file ending with at pattern like: "j" or "J", followed by "p" or "P", followed by an optional "e" or "E", followed by "g" or "G") (This could also be done using the "-i" option in grep, but I took this as an exercise in REGEX)

Finally, since we (hopefully) start to see a pattern, we can omit the unnecessary parentheses. Since there is only one optional letter ("E"), we can omit this one. Also, since the file only has this pattern to end on, we can omit the starting and ending parenthesis. Thus we simply get:

[jJ][pP][eE]?[gG]$

Finally; lets assume you also want to find files with ".gif"-filetype, we can add this as a second parameter:

 ([jJ][pP][eE]?[gG])|([gG][iI][fF])$

(Here I've again added extra parenthesis for readability/grouping. Feel free to remove them if they seem obfuscating.)

Finally, I used ls and a pipeline to send all file names to (e)grep:

ls | egrep '([jJ][pP][eE]?[gG])|([gG][iI][fF])$' 

Result:

test.gif
test.JPG
test.JpEg
test.JPEG
test.jpg
test.JPG

Second edit: Using the -i option and omitting parenthesis we can shorten it down to only:

ls | egrep -i 'jpe?g|gif$'
4
  • Damn. Good answer. Thanks for giving me a mini-less on regex!
    – Harv
    Dec 1, 2011 at 1:44
  • I would like to point out that your first regex seems to have way more parenthesis than required. With egrep you don't need any
    – TheLQ
    Dec 1, 2011 at 2:27
  • Good.. Just one thing though. Where you wrote, for .*, "(Match anything one or more times" should be "zero to many" times... OR, for "one or more", it would be .+
    – Peter.O
    Dec 1, 2011 at 2:37
  • To TheLQ and ferer: yeah, I ought to have deleted that first statement all together, was written as a starting point since no one else had answered at that time. @ferer, you are right, a sloppy error, is fixed now... Dec 1, 2011 at 2:50
16

Here is another way to do this:

grep "foo\|bar" /path/to/file

This find text foo or bar in file

2
  • 1
    Thank you! The other answers are interesting, but this is the one I was looking for -- backslash pipe. Thank you.
    – m_mlvx
    Feb 13 at 8:39
  • This agrees with the way my brain thinks too. Thank you for this one. Worked for my situation perfectly.
    – wetjosh
    Mar 31 at 23:59
13

If you want to match files by their names, grep is the wrong tool. The grep utility looks for patterns inside files; it's irrelevant if what you care about is the file's name.

Shell wildcard patterns are the way to match files by their names. In modern shells, wildcard patterns have the same expressive power as regular expressions (i.e. what you can do with one, you can do with the other), but they have a different syntax for historical reasons.

In bash, you need to enable extended wildcard patterns first, by typing this line or putting it into your ~/.bashrc:

shopt -s extglob

Then you can move all the .jpg or .jpeg files from the current to a photo directory like this:

mv *.@(jpg|jpeg) /path/to/photo/directory

or even

mv *.jp?(e)g /path/to/photo/directory

In zsh, you can use the syntax above if you put setopt ksh_glob in your ~/.zshrc (or type it on the command line), or you can write

mv *.(jpg|jpeg) /path/to/photo/directory
mv *.jp(e|)g /path/to/photo/directory

If you want to copy files from the current directory and its subdirectories recursively, then in zsh you can write

mv **/*.(jpg|jpeg) /path/to/photo/directory

(Note that this copies foo/bar.jpg to /path/to/photo/directory/bar.jpg.) In bash version 4, run shopt -s globstar and you can write

mv **/*.@(jpg|jpeg) /path/to/photo/directory
4
  • I should have been more clear. It's not based on the filename.
    – Harv
    Dec 1, 2011 at 5:14
  • Is there a way to enable regular expressions per command? I usually don't want the full power of regexp (And the special treatment of various characters).
    – nimrodm
    Dec 10, 2011 at 14:05
  • 2
    @nimrodm What do you mean? In zsh, you can disable globbing on the arguments of specific commands by prefixing them with noglob, e.g. noglob lynx http://google.com/search?q=foo. Apart from that, the shell parses the command line before it can see what the command name is, e.g. it first finds where the ; or newline is that ends the command and only then looks at the command name, so if you want to pass some special characters in arguments, you need to quote them. Dec 10, 2011 at 17:36
  • why not use brace expansions here? Dec 18, 2011 at 15:22
10

are you getting the file type by running file $file and looking at the output?

tk-mbp:~ tkennedy$ file share/rally.jpg 
share/rally.jpg: JPEG image data, JFIF standard 1.01

or, are you just looking at the file extension?

If you're looking at the actual file type from the output of the file command:

for file in $(find $dir -type f -exec file {} \; | grep JPEG | cut -d: -f1); do mv $file $photo_dir/ ; done

If you're just looking at the file extension, you can do it all within find:

find $dir -type f -name \*jpg -o -name \*jpeg -o -name \*JPG -o -name \*JPEG -exec mv {} $photo_dir/ \;

If you just want to know how to pass multiple arguments to grep, you can do that by using egrep or grep -E on Mac OS X.

find $dir | grep -E 'jpg|jpeg|JPG|JPEG'

etc.

1
  • fantastic answer, thank you. It may be more concise than Gunnar's, but I really enjoyed that he went into depth about how regex works. I will upvote yours because I think it gets to the point nicely, and is accurate.
    – Harv
    Dec 1, 2011 at 1:45

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