1

I've seen various topics on how to iterate through multiple file extensions but in most case, the list is defined.

example:

for file in ${arg}/**/*.{txt,h,py}
do
    ....
done

As can be seen, .TXT files would be ignored. And sadly, the guy who answered it says it only works for bash4. but my setup uses bash3.x.

Any ideas on how it can be done?

  • 1
    Use the find command to select the files on any depth, then its -exec option to do something on them. No need to use bash. – Anthon Jul 23 '14 at 5:28
  • 2
    @Tejas, that is not the same question at all. – Stéphane Chazelas Jul 23 '14 at 6:58
  • 1
    Could you please edit and clarify your question? It does indeed seem very similar to your previous one. What are you actually trying to achieve? What would ${arg} be here? Do you need recursion? What kind of directory structure would you have? What guy answered it? What did this guy answer? You say that in most cases the list is defined, is it defined in your case? – terdon Jul 23 '14 at 11:21
6

In bash you can set nocaseglob:

shopt -s nocaseglob

for file in "$arg"/**/*.{txt,h,py}
do
  ....
done

shopt -u nocaseglob

noclaseglob is fine to use in any bash since 2.01, however ** requires bash 4.0 or later (and it follows symlinks up to bash 4.3 where it was fixed). Note the correction to quote $arg since there will be problems if this contains spaces or glob characters.

Without using ** you can do something like:

find "$arg" \( -iname '*.txt' -o -iname '*.h' -o -iname '*.py' \) -exec bash -c '
  for file; do
    ...
  done' bash {} +

This will find all the files you are looking for and pass them as separate arguments to a new bash instance, which will then loop through them. If your per file operation is a single command, you can probably skip the new bash instance and use the command directly.

Update

As per Stephane's comments below, the POSIX compatible solution is:

find "$arg" \( -name '*.[tT][xX][tT]' -o -name '*.[hH]' -o -name '*.[pP][yY]' \) \
  -exec sh -c '
  for file do
    ...
  done' sh {} +
| improve this answer | |
  • 2
    ** appeared in bash 4 (borrowed from ksh93, itself borrowing it with differences from zsh). The following of symlinks was fixed in 4.3. You need shopt -s globstar for it. – Stéphane Chazelas Jul 23 '14 at 6:12
  • Note that -iname is not standard. You also need braces as -a has precedence over -o and so only .py files will be processed. Also note that find will include hidden files (and descend into hidden dirs) while **/ will not by default. – Stéphane Chazelas Jul 23 '14 at 12:45
5

Use zsh instead:

setopt extendedglob
for f ($arg/**/*.(#i)(txt|h|py)(N.)) {
  ...
}
  • **/: any level of subdirectories
  • (#i) is to turn case insensitive matching on for the rest of the glob (like ~(i) in ksh93).
  • (N.) is the glob qualifier. N is to not return an error if there's no matching file and . to select regular files only (the equivalent of find's -type f)). You could add a D in there to also match dot (hidden) files and descend into hidden dirs (like find would be default).
| improve this answer | |
  • While your answer looks factually correct, it would be good to elaborate it a little more on how it works. I know it's an old answer, but this question got bumped to the front page because of another answer. – Centimane Aug 14 '17 at 21:42
  • @Centimane, see if the edit makes it any clearer. – Stéphane Chazelas Aug 15 '17 at 10:41
  • Looks more clear to me, have an upvote! – Centimane Aug 15 '17 at 21:44
1

What about a simple:

for file in "$(ls -1 | grep -iE '.txt|.py|.h')";
do
  ....
done

Another simple option is

find . -maxdepth 1 -iname '*.txt'; find . -maxdepth 1 -iname '*.py'; find . -maxdepth 1 -iname '*.h'; 

You can wrap find in () parenteses in bash to have the list.

| improve this answer | |
  • Care to comment downvotes? It could a way to learn something. – Francesco Pasa Nov 7 '17 at 19:14

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.