2

So I need to find all files which have d, o, and g in the filename. The letters do not need to be side by side (aadaaoaagaa would be a correct filename, aaoaadaag would not be a correct filename). It does not matter if the letters are uppercase or lowercase (aadaaOaag is a correct filename, as is aaDoGa.

How would I use filename substitution on the terminal command line to list all of the files like this? Preferably without using loops or anything too advanced. All I have been introduced to so far is filename substitution characters (*,?,[])

1
  • 2
    Just ls *[dD]*[oO]*[gG]* is not good enough?
    – Nykakin
    Sep 28, 2013 at 4:15

4 Answers 4

2

To list them when you are in the directory where those files are in, you can use, as Nykakin said in this comment the following command:

ls *[dD]*[oO]*[gG]*

or, if you want each file on its own line, you can use:

printf '%s\n' *[dD]*[oO]*[gG]*

or, using grep:

ls | grep [dD].*[oO].*[gG]
2
  • ls [dD]*[oO]*[gG] The problem with this is, I think it makes it possible for the letters to get mixed up since there is the * between everything, meaning those could also referance a d, D, o, O, g or G. They have to be in order, it cannot be like oDog because the o has to come after the d Sep 28, 2013 at 11:03
  • @JohnStacen Well, you didn't said nothing about the cases where an "o" or "O" is before these letters in your question. You can eliminate these occurrences using grep -v. For example: ls *[dD]*[oO]*[gG]* | grep -v [oO].*[dD] Sep 28, 2013 at 14:19
2

If I understand your problem correctly, you want all those filenames with a d, an o and a g, in any case (upper or lower). Radu Rădeanu gave you an answer that works for the letters occuring in the order d-o-g. For example aaGaaaoaaDaa will not be found. You could alter his solution using the six possible permutations as, e.g.,

ls *[dD]*[oO]*[gG]* *[oO]*[gG]*[dD]* *[gG]*[dD]*[oO]* *[oO]*[dD]*[gG]* *[dD]*[gG]*[oO]* *[gG]*[oO]*[dD]*

(setting shopt -s nullglob before is a good idea).

This becomes a bit messy, so you might want to also use shopt -s nocaseglob so that the match is performed without regard to the case of alphabetic characters (quoted from the Bash Reference Manual) as:

$ shopt -s nullglob nocaseglob
$ ls *d*o*g* *o*g*d* *g*d*o* *o*d*g* *d*g*o* *g*o*d*

Another option is to use the find command as, e.g.,

$ find . -maxdepth 1 -iname '*d*' -iname '*o*' -iname '*g*' -type f

The -maxdepth 1 option is to limit to the current directory and not its subdirectories and -type f to limit the search to regular files. Change these options to fit your needs. -iname is for a case-insensitive search on the name (using glob patterns). Don't forget the quotes!

One advantage of find is that you'll be able to -exec stuff if you need to perform any operation on these files, e.g., renaming them, or appending stuff to them, etc. Another advantage is that if you have a huge number of such files, the globbing will take ages, and might even overflow the maximum number of arguments allowed. find will be alright, whatever the number of files is.

Hope this helps!

Edit. It seems I missed the in this order part in your title. Hahaha. Well, then the find command would simply be:

$ find . -maxdepth 1 -iname '*d*o*g*' -type f

and the ls/printf solution would be:

$ shopt -s nullglob nocaseglob
$ ls *d*o*g*
$ printf '%s\n' *d*o*g*
1

I would use Perl for this:

perl -le 'print for grep {/\A[a-cefh-np-z]*d[a-fh-z]*o[a-z]*g[a-z.]*\Z/i} <*>'

Explanation

  • <*> is magical: it returns the result of globbing the pattern * from the current directory.
  • grep{} only returns those items of the glob matching the criteria inside it.
  • print outputs the result of grep one item per line due to the -l option.
  • The single criterion used for grep is that the file name consist exactly of:

    • any number of letters in the set [a-cefh-np-z] (the alphabet minus o and g)
    • the letter d
    • any number of letters in the set [a-fh-z] (the alphabet minus g)
    • the letter o
    • any number of any letter whatsoever
    • the letter g.
    • any number of letters or a period (for extensions).
  • The i modifier is applied to the regex criterion to ensure the match is case-insensitive as required in the question.

I know this isn't exactly what you asked for, but I believe Bash wildcard patterns are too simple to handle this task.

0

Another way to look at the problem would be to keep only filenames for which once eliminated every character outside of D,O or G, the result would be exactly DOG, case insensitive.

This could be done in shell with search-replace-test instead of wildcards:

#!/bin/bash
shopt -s nocasematch 
shopt -s nullglob
for i in *
do
  if [[ ${i//[^dogDOG]/} == dog ]]; then 
    echo $i;
  fi;
done
4
  • for i in `ls` is just plain wrong and broken. Use for i in * instead. Sep 29, 2013 at 8:26
  • @gniourf_gniourf: I've changed to for i in * for spaces in filenames but one could argue that since * expands as * when the dir is empty, it's also incorrect strictly speaking to get the directory contents. It doesn't matter much here, though. Sep 29, 2013 at 11:53
  • To avoid that, use shopt -s nullglob or, to have an explicit error use shopt -s failglob. Globbings should always be used with either of these options. Since you're already shopting, you might as well replace the shopt line with shopt -s nocasematch nullglob. Sep 29, 2013 at 12:46
  • Also, avoid using backticks! use $(...) instead. Moreover, to remove all the letters dogDOG from a variable, you can use parameter expansions: ${i//[^dogDOG]/}. It's much nicer and doesn't require the external tool tr. I'd hence write if [[ ${i//[^dogDOG]/} == dog ]]; then Sep 29, 2013 at 12:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .