10

Here is my script

eval "find \\( -type f -a \\( -name '*.h' \\) \\) -print0" | xargs -0 -n100 grep  -f <(echo "stdio")
echo $?

Nothing is found and the exit code is 123.

If I modify it a little as follows

echo "stdio" >.P
eval "find \\( -type f -a \\( -name '*.h' \\) \\) -print0" | xargs -0 -n100 grep <.P
echo $?

Something is found but the exit code is still 123.

Actually I just want to write a small script to make find+xargs+grep easier. For example,

xgrep -e PATTERN1 -e PATTERN2 ... *.c *.h

is to execute

find -name *.c -o -name *.h | xargs grep -f <(echo "$PATTEN1
$PATTERN2")

The use of the -f option instead of -e is to avoid troubles in escaping single or double quotes within the patterns.

#!/bin/bash
#set -e -o pipefail

eval ARGV=($(getopt -l '' -o 'e:li' -- "$@")) || exit 1
for((i=0;i<${#ARGV[@]};i++)) {
    o="${ARGV[$i]}"
    case $o in
    -e)
        i=$((i+1));
        a="${ARGV[$i]}"
        if [ -n "$grep_patterns" ]; then
            grep_patterns="$grep_patterns"$'\n'
        fi
        grep_patterns="$grep_patterns$a"
        ;;
    -i)
        grep_options="$grep_options -i"
        ;;
    -l)
        grep_options="$grep_options -l"
        ;;
    --)
        i=$((i+1));
        break;;
    esac
}

for((;i<${#ARGV[@]};i++)) {
    if [ -n "$find_options" ]; then
        find_options="$find_options -o "
    fi
    find_options="${find_options}-name '${ARGV[$i]}'"
}

cmd="find \\( -type f -a \\( $find_options \\) \\) -print0"
eval "$cmd" | xargs -0 grep $grep_options -f <(echo "$grep_patterns")
tripleee
  • 175,061
  • 34
  • 275
  • 318
Pan Ruochen
  • 1,990
  • 6
  • 23
  • 34

2 Answers2

10

123 means "any invocation exited with a non-zero status". So xargs ran grep at least twice (because you fed it so many files that they would exceed the maximum command line length, which you limited to 100 files) and at least one of the invocations was on a set of files which contained no matches, which caused the exit code from grep to be nonzero (failure).

Perhaps you should explain what you are trying to accomplish. The eval looks superfluous and the double redirection is probably not accomplishing what you want (grep's standard input cannot simultaneously be connected to the pipe from eval and to .P).

If you want to parametrize the first argument to grep, maybe do something like

#!/bin/sh
find -type f -name '*.h' -print0 |
xargs -0 -n100 grep "$1"

... where you invoke this with e.g. stdio as the first argument.

(Notice also the much simplified parameters to find. You only have two predicates so there is no need to parenthesize anything, and then the -a can be dropped, too.)

The exit code will still be 123 if there are grep invocations which return zero matches. You can reduce the chances by omitting the -n 100 (which hardly seems to serve any useful pupose anyway) but if you want to absolutely prevent it, you can feed the entire pipeline to | grep . which will report success if there was any output. (Or you could run xargs on a wrapper which always returns success if the exit code from grep is either 0 or 1, but that is more complex, and you will then see "success" even in the case of zero matches.)

tripleee
  • 175,061
  • 34
  • 275
  • 318
2

I'm posting this as a separate answer in response to your recent edit. I don't particularly want to change my existing answer, which already addresses the fundamental question.

Your script is unfortunately a classic example of the problem described in http://mywiki.wooledge.org/BashFAQ/050: "I am trying to put my command in a variable, but"...

The short version is "don't do that". The long version is, try to use arrays, and avoid variables where they are not absolutely necessary. Here is an attempt at refactoring your tool along those lines.

#!/bin/bash
#set -e -o pipefail

grep_patterns=( )
grep_options=( )

eval ARGV=($(getopt -l '' -o 'e:li' -- "$@")) || exit 1
for((i=0;i<${#ARGV[@]};i++)) {
    case ${ARGV[$i]} in
    -e) i=$((i+1))
        grep_patterns+=("-e" "${ARGV[$i]}") ;;
    -i | -l)
        grep_options+=("${ARGV[$i]}") ;;
    --) i=$((i+1));
        break;;
    esac
}

find_options=("${ARGV[@]:$i}")

find -type f -a \( "${find_options[@]}" \) -print0 |
xargs -0 grep "${grep_options[@]}" "${grep_patterns[@]}"

I'm not sure passing multiple -e options to grep is supported everywhere, but it works fine with GNU grep and simplifies things to my mind.

tripleee
  • 175,061
  • 34
  • 275
  • 318