2

I try to put find inside function and catch an argument passed to this function with the following minimal work example:

function DO
{
    ls $(find . -type f -name "$@" -exec grep -IHl "TODO" {} \;)
}

But, when I execute DO *.tex, I get “find: paths must precede expression:”. But when I do directly:

ls $(find . -type f -name "*.tex" -exec grep -IHl "TODO" {} \;)

then I get all TeX files witch contain "TODO".

I try many thing in the DO function, such as \"$@\", '$@', I change the quotes marks, but the behavior still the same.

So, what to do to force find work inside function?

1
  • If you have two files ending in .tex, your command would be expanded by the calling shell to, say, DO f1.tex f2.tex, and the find command would see ... -name f1.tx f2.tex ..., which is invalid. Jun 20, 2018 at 7:54

2 Answers 2

5

There are a few issues in your code:

  1. The *.tex pattern will be expanded when calling the function DO, if it matches any filenames in the current directory. You will have to quote the pattern as either '*.tex', "*.tex" or \*.tex when calling the function.

  2. The ls is not needed. You already have both find and grep that are able to report the pathnames of the found files.

  3. -name "$@" only works properly if "$@" contains a single item. It would be better to use -name "$1". For a solution that allows for multiple patterns, see below.

The function may be written

DO () {
   # Allow for multiple patterns to be passed,
   # construct the appropriate find expression from all passed patterns

   for pattern do
       set -- "$@" '-o' '-name' "$pattern"
       shift
   done

   # There's now a -o too many at the start of "$@", remove it
   shift

   find . -type f '(' "$@" ')' -exec grep -qF 'TODO' {} ';' -print
}

Calling this function like

DO '*.tex' '*.txt' '*.c'

will make it execute

find . -type f '(' -name '*.tex' -o -name '*.txt' -o -name '*.c' ')' -exec grep -qF TODO {} ';' -print

This would generate a list of pathnames of files with those filename suffixes, if the files contained the string TODO.

To use grep rather than find to print the found pathnames, change the -exec ... -print bit to -exec grep -lF 'TODO' {} +. This will be more efficient, especially if you have a large number of filenames matching the given expression(s). In either case, you definitely do not need to use ls.


To allow the user to use

DO tex txt c

your function could be changed into

DO () {
   # Allow for multiple patterns to be passed,
   # construct the appropriate find expression from all passed patterns

   for suffix do
       set -- "$@" '-o' '-name' "*.$suffix"   # only this line (and the previous) changed
       shift
   done

   # There's now a -o too many at the start of "$@", remove it
   shift

   find . -type f '(' "$@" ')' -exec grep -qF 'TODO' {} ';' -print
}
0

You need to quote the parameter to the function, otherwise shell expands it and the function gets a list of files, not a wildcard mask:

DO "*.tex"

You can then use just "$1" instead of "$@", as there will only be one parameter.

5
  • This solution work but it break the expected usage of commands :/
    – fauve
    Jun 20, 2018 at 7:24
  • 2
    So the expectation was wrong. There's a reason you need to quote the mask for find.
    – choroba
    Jun 20, 2018 at 7:27
  • What about "$*" instead of "$@"? Might work if the filenames don't contain spaces.
    – choroba
    Jun 20, 2018 at 7:27
  • No, $* expands all the arguments but as a single string. The only way to get *.tex into the find -name parameter from the command line is to quote it. Jun 20, 2018 at 7:30
  • @fauve if you're looking for files that end with .tex could you change your tool to expect just tex on the command line? You could then do something like find ... -name "*.$1" Jun 20, 2018 at 7:32

You must log in to answer this question.

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