1

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

#!/bin/bash
echo -e "\n"

i=0
args[i++]=$0
for arg in $@ ; do
  args[i++]=$arg
done

where="."
what="-type f"
gcase=
str=

while getopts "d:f:F:ih" opt ; do
  case $opt in
    h)
      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
      ;;
    d)
      OPTIND=$(($OPTIND-1))
      where=
      tmp=${args[$OPTIND]}
      while [[ $OPTIND -lt $# ]] && [[ "${tmp:0:1}" != "-" ]] ; do
        where="$where"" $tmp"
        OPTIND=$(($OPTIND+1))
        tmp=${args[$OPTIND]}
      done
      ;;
    F)
      what="-iname "
      what="$what""\"$OPTARG\""
      ;;
    f)
      what="-name "
      what="$what""\"$OPTARG\""
      ;;
    i)
      gcase="-i"
      ;;
    \?)
      echo "Invalide option, use option -h for help." >&2
      exit 0
      ;;
  esac
done

str=${args[$OPTIND]}

command="find $where $what -exec grep --color -Hn $gcase \"$str\" {} \;"
echo "$command"
$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.

1 Answers1

4

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.

ilkkachu
  • 138,973
  • Either set -x or #!/bin/bash -x works fine for this. – user Feb 08 '17 at 14:45
  • When I do set -x options are not parsed any more. – Tsathoggua Feb 09 '17 at 07: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 09 '17 at 07: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