Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I can run the following command in bash without any errors:

$ find /d/Code/Web/Development/Source/ \( -name '*.cs' -o -name '*.cshtml' \) -exec grep -IH UserProfileModel {} \;

I wrote a function in .bash_aliases to quickly run this command:

search() {
    local file_type file_types find_cmd opt OPTARG OPTIND or pattern usage

    usage="Usage: search [OPTION] ... PATTERN [FILE] ...
Search for PATTERN in each FILE.
Example: search -t c -t h 'hello world' /code/internal/dev/ /code/public/dev/

Output control:
  -t    limit results to files of type"

    if [[ $1 == --help ]]; then
        echo "$usage"
        return
    fi

    file_types=()
    while getopts ":t:" opt; do
        case $opt in
            t)
                file_types+=("$OPTARG")
                ;;
            ?)
                echo "$usage"
                return
                ;;
        esac
    done
    shift $((OPTIND-1))

    if (( $# == 0 )); then
        echo "$usage"
        return
    fi

    pattern=$1
    shift

    if (( $# == 0 )); then
        echo "$usage"
        return
    fi

    find_cmd=(find "$@" '\(')
    or=""
    for file_type in "${file_types[@]}"; do
        find_cmd+=($or -name \'*.$file_type\')
        or="-o"
    done
    find_cmd+=('\)' -exec grep -IH "$pattern" {} '\;')

    "${find_cmd[@]}"
}

However, the function throws an error:

find: paths must precede expression

If I change the last line to echo "${find_cmd[@]}", it prints the exact same command as above:

$ search -t cs -t cshtml UserProfileModel /d/Code/Web/Development/Source/
find /d/Code/Web/Development/Source/ \( -name '*.cs' -o -name '*.cshtml' \) -exec grep -IH UserProfileModel {} \;

I don't understand why it would work when run in the console but fail when run inside a function.

Also, if I simplify the function to just the command it works:

search() {
    find /d/Code/Web/Development/Source/ \( -name '*.cs' -o -name '*.cshtml' \) -exec grep -IH UserProfileModel {} \;
}

I am editing .bash_aliases in Notepad++, but I have made sure the line endings are Unix format.

Edit

Per F. Hauri's advice below, I enabled debugging. Apparently this is the command that's actually being executed:

find /d/Code/Web/Development/Source/ '\(' -name ''\''*.cs'\''' -o -name ''\''*.cshtml'\''' '\)' -exec grep -IH UserProfileModel '{}' '\;'

I'm not sure what to make of this information. Removing the escape character before the parens cause it to throw a different error:

find: missing argument to -exec
share|improve this question
1  
I don't know if it's related to your problem specifically, but you should quote the -name patterns e.g. -name '*.cs' and similar arguments when you run find. Otherwise the shell may expand the glob to a list of matching files from the current directory. Quoting ensures that *.cs is passed unexpanded to find. –  steeldriver May 7 at 22:44
    
I meant to add that. Does it matter if I use single quotes or double quotes? Either way it doesn't change the error. –  Koveras May 7 at 22:49
    
Stop playing with quotes, while using bash arrays. See my answer! –  F. Hauri May 8 at 0:53
    
@steeldriver I think the problem is reverse: There too much quotes. –  F. Hauri May 8 at 1:00
add comment

2 Answers

up vote 1 down vote accepted

Tip: run set -x to enable tracing mode. Bash prints each command before executing it. Run set +x to turn off tracing mode.

+ find . '\(' '\)' -exec grep -IH needle '{}' '\;'

Notice how the last argument to find is \; instead of ;. You have the same problem with the opening and closing parentheses. In your source, you've quoted the semicolon twice. Change

    find_cmd=(find "$@" '\(')
    …
    find_cmd+=('\)' -exec grep -IH "$pattern" {} '\;')

to

    find_cmd=(find "$@" '(')
    …
    find_cmd+=(')' -exec grep -IH "$pattern" {} ';')

or

    find_cmd=(find "$@" \()
    …
    find_cmd+=(\) -exec grep -IH "$pattern" {} \;)

Additionally, -name \'*.$file_type\' has bad quotes — you're looking for files whose name starts and ends with a single quote. Make this -name "*.$file_type" (the * needs to be quoted in case there are matching files in the current directory, and variable expansions should be in double quotes unless you know why you need to leave out the double quotes).

share|improve this answer
    
Very clear explanation that doesn't require rewrite. Thank you. –  Koveras May 8 at 15:08
add comment

Running command using arrays

Let try:

find /tmp \( -type f -o -type d \) -ls 

Wow, there is a lot of output...

Well, now:

cmd_list=(find /tmp \()
cmd_list+=(-type f)
cmd_list+=(-o -type d)
cmd_list+=(\) -ls)
"${cmd_list[@]}"

Hmm... There seem identical!

find /tmp \( -type f -o -type d \) -ls 2>/dev/null | md5sum
eb49dfe4f05a90797e444db119e0d9bd  -
"${cmd_list[@]}" 2>/dev/null| md5sum
eb49dfe4f05a90797e444db119e0d9bd  -

Ok, finally:

printf "%q " "${cmd_list[@]}";echo
find /tmp \( -type f -o -type d \) -ls 

while

printf "%s " "${cmd_list[@]}";echo
find /tmp ( -type f -o -type d ) -ls

My full running version:

Well, for fun, there is a quick-'n-dirty small version:

search() {
    local OPTIND=0 _o
    local -a _t
    while getopts 't:' _o ;do
        case $_o in
            t) _t+=(${_t+-o} -name \*.${OPTARG}) ;;
            *) echo "Usage: $0 [-t type[ -t type]] /path [/path.. /path] pattern";
               exit 1 ;;
        esac
    done
    find ${@:OPTIND:$#-$OPTIND} \( "${_t[@]}" \) -print0 |
        xargs -0 grep -lI ${@:$#}
}

In the hope this may help you.

share|improve this answer
    
Sorry, missreading... –  F. Hauri May 7 at 23:24
    
Please see my edit. –  F. Hauri May 7 at 23:49
    
What does this mean? I updated my question btw. –  Koveras May 8 at 0:02
1  
echo is not well for this, use declare -p VARNAME for looking into a variable. –  F. Hauri May 8 at 0:44
    
My running version work same as your, but permit the use of more than one path to browse into. –  F. Hauri May 8 at 1:01
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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