Home > OS >  Is there a globbing pattern to match by file extension, both PWD and recursively?
Is there a globbing pattern to match by file extension, both PWD and recursively?

Time:05-17

I need to match files only with one specific extension under all nested directories, including the PWD, with BASH using "globbing".

From this Answer (here), I believe there may not be a way to do this using globbing.

tl;dr

I need:

  • A glob expression
  • To match any command where simple globs can be used (ls, sed, cp, cat, chown, rm, et cetera)
  • Mainly in BASH, but other shells would be interesting
  • Both in the PWD and all subdirectories recursively
  • For files with a specific extension

I'm using grep & ls only as examples, but I need a glob expression that applies to other commands also.

  • grep -r --include=GLOB is not a glob expression for, say, cp; it is a workaround specific to grep and is not a solution.
  • find is not a glob, but it may be a workaround for non-grep commands if there is no such glob expression. It would need | or while do;, et cetera.

Examples

Suppose I have these files, all containing "find me":

./file1.js
./file2.php
./inc/file3.js
./inc/file4.php
./inc.php/file5.js
./inc.php/file6.php

I need to match only/all .php one time:

./file2.php
./inc/file4.php
./inc.php/file6.php

Duplicates returned: shopt -s globstar; ... **/*.php

This changes the problem; it does not solve it.

Dup: ls

Before entering shopt -s globstar as a single command...

ls **/*.php returns:

inc/file4.php
inc.php/file5.js
inc.php/file6.php
  • file2.php does not return.

After entering shopt -s globstar as a single command...

ls **/*.php returns:

file2.php
inc/file4.php
inc.php/file6.php

inc.php:
file5.js
file6.php
  • inc.php/file6.php returns twice.

Dup: grep

Before entering shopt -s globstar as a single command...

grep -R "find me" **/*.php returns:

inc/file4.php: find me
inc.php/file6.php: find me
  • file2.php does not return.

After entering shopt -s globstar as a single command...

grep -R "find me" **/*.php returns:

file2.php: find me
inc/file4.php: find me
inc.php/file5.js: find me
inc.php/file6.php: find me
inc.php/file6.php: find me
  • inc.php/file6.php returns twice.
    • After seeing the duplicate seen from the ls output, we know why.

Current solution: faulty misuse of && logic

grep -r "find me" *.php && grep -r "find me" */*.php
ls -l *.php && ls -l */*.php
  • Please no! I fail here && so I never happen

Desired solution: single command via globbing

grep -r "find me" [GLOB]
ls -l [GLOB]

Insight from grep

grep does have the --include flag, which achieves the same result but using a flag specific to grep. ls does not have an --include option. This leads me to believe that there is no such glob expression, which is why grep has this flag.

CodePudding user response:

With bash, you can first do a shopt -s globstar to enable recursive matching, and then the pattern **/*.php will expand to all the files in the current directory tree that have a .php extension.

zsh and ksh93 also support this syntax. Other commands that take a glob pattern as an argument and do their own expansion of it (like your grep --include) likely won't.

CodePudding user response:

With shell globing it is possible to only get directories by adding a / at the end of the glob, but there's no way to exclusively get files (zsh being an exception)

Illustration:

With the given tree:

file.php
inc.php/include.php
lib/lib.php

Supposing that the shell supports the non-standard ** glob:

  • **/*.php/ expands to inc.php/

  • **/*.php expands to file.php inc.php inc.php/include.php lib/lib.php

  • For getting file.php inc.php/include.php lib/lib.php, you cannot use a glob.
    => with zsh it would be **/*.php(.)

Standard work-around (any shell, any OS)

The POSIX way to recursively get the files that match a given standard glob and then apply a command to them is to use find -type f -name ... -exec ...:

  • ls -l <all .php files> would be:
find . -type f -name '*.php' -exec ls -l {}  
  • grep "finde me" <all .php files> would be:
find . -type f -name '*.php' -exec grep "finde me" {}  
  • cp <all .php files> ~/destination/ would be:
find . -type f -name '*.php' -type f -exec sh -c 'cp "$@" ~/destination/' _ {}  

remark: This one is a little more tricky because you need ~/destination/ to be after the file arguments, and find's syntax doesn't allow find -exec ... {} ~/destination/

CodePudding user response:

Suggesting different strategy:

Use explicit find command to build bash command(s) on the selected files using -printf option.

Inspect the command for correctness and run.

1. preparing bash commands on selected files

 find . -type f -name "*.php" -printf "cp %p ~/destination/ \n"

2. inspect the output, correct command, correct filter, test

cp ./file2.php ~/destination/
cp ./inc/file4.php ~/destination/
cp ./inc.php/file5.php ~/destination/

3. execute prepared find output

 bash <<< $(find . -type f -name "*.php" -printf "cp %f ~/destination/ \n")
  • Related