This question already has an answer here:

I am trying to write some bash script to replace a command I quite often use. Here is the code from my file test.sh

echo -e "\n"

for arg in $@ ; do

what="-type f"

while getopts "d:f:F:ih" opt ; do
  case $opt in
      echo -e "This is the help of this search function."
      echo -e "\t $0 [-d <dir>] [-f|-F <pattern>] [-i] string"
      echo -e "This will output the result of"
      echo -e "\t find dir -[i]name pattern -exec grep --color -Hn[i] string {} \;"
      echo -e "Default is"
      echo -e "\t find . -type f -exec grep --color -Hn string {} \;\n"
      exit 0
      while [[ $OPTIND -lt $# ]] && [[ "${tmp:0:1}" != "-" ]] ; do
        where="$where"" $tmp"
      what="-iname "
      what="-name "
      echo "Invalide option, use option -h for help." >&2
      exit 0


command="find $where $what -exec grep --color -Hn $gcase \"$str\" {} \;"
echo "$command"

Now, from my terminal, I do ./test.sh -d auto-avoid -F "TEST*" "main" and I get

find  auto-avoid -iname "TEST*" -exec grep --color -Hn  "main" {} \;
find: missing argument to `-exec'

(auto-avoid is a directory with a small c++ program I wrote for fun.)

Then, in my terminal I copy-paste the command find auto-avoid -iname "TEST*" -exec grep --color -Hn "main" {} \; and I get

auto-avoid/test.cpp:26:int main(int argc, char **argv)

which is the expected result.

The question is: what did I miss?

For now I wrote it as an independent script to test it, but the goal is to have it as a function in my .bash_aliases.

I have found some similar topic but nothing that could help me. If you find that this is a duplicated question, I will gladly take the solution.

I am pretty sure some people will tel me to use grep -r, but I would at least want to understand why my script does not work. This is a minimal "not"-working example, I will exclude some directory from my find later.

marked as duplicate by Gilles bash Feb 8 '17 at 23:39

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Because you tried to store a list of words into a string, and use the result as a command. That doesn't work. – Gilles Feb 8 '17 at 23:40

Use set -x to see what the shell really tries to run:

$ command='find foo -iname "TEST*" -exec grep --color -F -Hn "main" {} \;'
$ echo "$command"
find foo -iname TEST* -exec grep --color -F -Hn main {} \;
$ set -x
$ $command
+ find foo -iname 'TEST*' -exec grep --color -F -Hn main '{}' '\;'
find: missing argument to `-exec'

Note the '\;': you're giving find a literal backslash, which isn't what it expects.

The double quotes accomplish the same function as the backslash would, escaping the semicolon so that it's taken as a character, and not a command separator.

These should be equivalent:

$ foo="something ;"
$ foo=something\ \;

Also, note that running a command line with $command is a bit hairy: if you have spaces in any of the arguments going to the resulting command (e.g. in the pathname you have in $where), they will get split. Shell arrays give a more robust way to do that.

  • Either set -x or #!/bin/bash -x works fine for this. – a CVn Feb 8 '17 at 14:45
  • When I do set -x options are not parsed any more. – Tsathoggua Feb 9 '17 at 7:16
  • Replacing \; with ; solves the problem on missing argument but the function still display nothing. Actually, the problem now comes from interpreting *, but that is another problem. Thanks for the array advice, I will look later. – Tsathoggua Feb 9 '17 at 7:28
  • @Éric, Mostly the same problem, quotes inside an expanded variable don't count, so you either get the splitting (which you want), and expanding the pattern by the shell (which you don't), or you get neither. Arrays are the solution again. But that's all in the linked answer anyway. – ilkkachu Feb 11 '17 at 11:02

Not the answer you're looking for? Browse other questions tagged or ask your own question.