0

I've got a script that uses find to look for files that match user input. Basically, the user enters something like test* and it's supposed to find all matching files.

The problem I'm having is that if there is a file in the current directory that matches the filename pattern the user entered, it only looks for that file. That is, if the user enters fred* and there is a file in the current directory called frederica, the script only ever finds files named frederica. Here's an example of the problem:

>ls
test1  test2  test3  test4  test5  words
>tmp()
> {
>    my_file="$1"
>    find . -iname "$my_file" 
> }
>tmp test*
./test1
>

If I enter tmp test*, I would expect it to return the five files test1 through test5. Instead it only returns the first one. Further, if I searched on, say, /, it would still only ever return files named test1 from every directory.

The actual script is more complex, of course, and I've developed a workaround involving a "wildcard option" (e.g., -w 1 means leading and trailing asterisks, etc.) but I'd really just like to be able to let the user enter a filename with a wildcard.

Thanks in advance

Roger Sinasohn
  • 483
  • 4
  • 11
  • Why don't you try `'$my_file'` or even just `$my_file` – M. Becerra Apr 07 '17 at 18:34
  • That doesn't seem to help -- putting single quotes around `$my_file` in the find command results in not finding anything while no quotes gives the same results as double quotes. – Roger Sinasohn Apr 07 '17 at 18:55
  • This is very closely related to [Stop shell wildcard character expansion?](http://stackoverflow.com/questions/11456403/stop-shell-wildcard-character-expansion). – Charles Duffy Apr 07 '17 at 19:49

2 Answers2

2

When you call your function the way you do, the shell that is interpreting the call tries to find matching files (this is done before entering the function), and different things can happen :

  • If the glob fails and the failglob shell option is set, then the function will not be executed at all. You have probably not seen this because of default shell options.

  • Else if the glob fails and the nullglob shell option is set, then the result of the glob is nothing at all, and no argument will be passed to your function. You have probably not experienced that either.

  • Else if the glob fails and the failglob shell option is unset, then result of the glob is whatever the glob expression was, and this is used as the argument value. Then, this glob expression is passed as is to find, and find does what you want because it implements globbing internally. You are lucky that it works, but it works.

  • The glob succeeds and results in a list of files that is used as a list of arguments for your function. Your call find using $1, so only the first of these arguments is taken into account, resulting in the result you do not want.

Double quoting or single quoting disable globbing. If using from a script, you can therefore do:

tmp "test*"

If you want to use this command from the command line, your only option would be to entirely disable globbing, with set -f, which might very well have drawbacks.

Fred
  • 6,590
  • 9
  • 20
  • Your description matches my experience. I think failglob is not set so when there's no matching file(s) locally, the parm is passed as is (with the wildcard(s)) and it works as it should. – Roger Sinasohn Apr 07 '17 at 19:12
  • Unfortunately, disabling globbing is not an option, so it sounds like I'm stuck with what I've got -- relying on an option to have the script set the wildcards internally. Thanks! – Roger Sinasohn Apr 07 '17 at 19:12
  • 2
    @RogerSinasohn In all cases it works as it should, because "as it should" depends on the shell specs, not on what you want or expect... – Fred Apr 07 '17 at 19:14
  • Yes, you are quite right -- it works as it should, just not as I want it to (in this case). – Roger Sinasohn Apr 07 '17 at 19:42
  • Of course, that does not mean the spec is "right" (in the sense of the best it could be). You are certainly entitled to think the spec should be different, or that a different behavior would be more intuitive. I meant "as it should" in the sense that the "software behaving as its documentation says it should", which is certainly something anyone wants, irrespective of any opinion about what the documentation states. – Fred Apr 07 '17 at 20:06
1

test* expands to test1 test2 test3 test4 test5. You're only looking at $1, namely test1.

Try calling it as tmp 'test*'.

Josh Lee
  • 171,072
  • 38
  • 269
  • 275
  • That works, but it means training the users to put parameters inside single quotes which I don't think will work. But I think perhaps you're on to something -- is the expansion of `test*` happening within the script (when it is assigned to _my_file_) or in the actual command before the script starts? That is, is the problem that typing `tmp test*` is effectively the same as typing `tmp test1 test1 test3 test4 test5` so the script never even sees the wildcard? It's a shell problem? – Roger Sinasohn Apr 07 '17 at 18:51
  • Yes, if you want to pass a literal `*` to find you have to quote it from the shell. – Josh Lee Apr 07 '17 at 19:14
  • 1
    @RogerSinasohn, to answer your question directly: The glob is expanded *before* the script starts. The script is passed an argument vector with a list of individual strings, one per file, and has no way of knowing what the original command line was. – Charles Duffy Apr 07 '17 at 19:48