7

I want to do something like the following:

#!/bin/bash

cmd="find . -name '*.sh'"
echo $($cmd)

What I expect is that it will show all the shell script files in the current directory, but nothing happened.

I know that I can solve the problem with eval according to this post

#!/bin/bash

cmd="find . -name '*.sh'"
eval $cmd

So my question is why command substitution doesn't work here and what's the difference between $(...) and eval in terms of the question?

Community
  • 1
  • 1
Wei Li
  • 471
  • 6
  • 14

3 Answers3

2

Command substitution works here. Just you have wrong quoting. Your script find only one file name! This one with single quotes and asteriks in it:

'*.sh'

You can create such not usual file by this command and test it:

touch "'*.sh'"

Quoting in bash is different than in other programming languages. Check out details in this answer.

What you need is this quoting:

cmd="find . -name *.sh"
echo $($cmd)
jidicula
  • 3,454
  • 1
  • 17
  • 38
Tomek Wyderka
  • 1,425
  • 1
  • 15
  • 21
  • This is what I originally posted. The problem is that the pattern expands before the command is executed, so you only find the files that the pattern matches (i.e., you don't get any files from subdirectories). – chepner Nov 08 '12 at 15:10
  • chepner: I see now that your answer contain part of mine. I was mislead by arrays you suggested and didn't read your post carefully. – Tomek Wyderka Nov 08 '12 at 16:08
1

Since you are already including the patter *.sh inside double quotes, there's no need for the single quotes to protect the pattern, and as a result the single quotes are part of the pattern.

You can try using an array to keep *.sh quoted until it is passed to the command substitution:

cmd=(find . -name '*.sh')
echo $("${cmd[@]}")

I don't know if there is a simple way to convert your original string to an array without the pattern being expanded.

Update: This isn't too bad, but it's probably better to just create the array directly if you can.

cmd="find . -name *.sh"
set -f
cmd=($cmd)
set +f
echo $("${cmd[@]}")
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    I like the array trick actually! Remember to add `-maxdepth 1` to `find` if you want to limit the search to the current working directory only. – platforms Nov 08 '12 at 15:11
  • Unless you use other predicates to refine the search, the given find command with `-maxdepth 1` would be (I think) equivalent to simple pattern matching. – chepner Nov 08 '12 at 15:14
  • Well, I ran this without `-maxdepth 1` and it traversed directories finding more shell scripts below. With it, it limited the scope of the search to the current working directory. – platforms Nov 08 '12 at 15:18
  • That's what I meant about other predicates. `*.sh` would also match all shell scripts in the current directory, but doesn't let you filter based on file type, access time, file size, or other attributes that `find` gives you. – chepner Nov 08 '12 at 15:20
0

When you use the echo $($cmd) syntax, it's basically equivalent to just putting $cmd on it's own line. The problem is the way bash wants to interpolate the wildcard before the command runs. The way to protect against that is to put the variable containing the * char in quotes AGAIN when you dereference them in the script.

But if you put the whole command find . -name "*.sh" in a variable, then quote it with `echo $("$cmd"), the shell will interpret that to mean that the entire line is a file to execute, and you get a file not found error.

So it really depends on what you really need in the variable and what can be pulled out of it. If you need the program in the variable, this will work:

#!/bin/bash

cmd='/usr/bin/find'
$cmd . -name "*.sh" -maxdepth 1

This will find all the files in the current working directory that end in .sh without having the shell interpolate the wildcard.

If you need the pattern to be in a variable, you can use:

#!/bin/bash

pattern="*.sh"
/usr/bin/find . -name "$pattern" -maxdepth 1

But if you put the whole thing in a variable, you won't get what you expect. Hope this helps. If a bash guru knows something I'm missing I'd love to hear it.

platforms
  • 2,666
  • 1
  • 18
  • 23