How to make a script to make multiple grep's o

2019-07-10 04:48发布

I want to make a script that can do the following automatically:

 grep 'string1' file.txt | grep 'string2' | grep 'string3' ... | grep 'stringN'

The idea is that the script can be run like this:

myScript.sh file.txt string1 string2 string3 ... stringN

and the script has to return all the lines of file.txt that contain all the strings.

For instance, if file.txt looks like this:

hello world 
hello world run 
hello planet world 

And I can make a grep like this:

grep hello file.txt | grep world

and I get:

hello world 
hello world run 
hello planet world

I want to make a script that makes this automatically, with an undefined number of strings as parameters.

I found that it is hard to achieve this, since the number of strings can be variable. First, I tried to create an array called args like this in myScript.sh:

 #!/bin/bash
 args=("$@")

with the purpose of storing the arguments. I know that the ${args[0]} is going to be my file.txt and the rest are the strings that I need to use in the distinct greps, but I don't know how to proceed and if this is the best approach to solve the problem. I would appreciate any suggestion about how to program this.

标签: bash
5条回答
放我归山
2楼-- · 2019-07-10 05:18

You can create the command in a loop and then use eval to evaluate it. This is using cat so you can group all the grep.

#! /bin/bash

file="$1"
shift
args=( "$@" )


cmd="cat '$file'"
for a in "${args[@]}"
do
    cmd+=' | '
    cmd+="grep '$a'"
done

eval $cmd
查看更多
\"骚年 ilove
3楼-- · 2019-07-10 05:18

An eval-free alternative:

#!/bin/bash

temp1="$(mktemp)"
temp2="$(mktemp)"

grep "$2" "$1" > temp1
for arg in "${@:3}"; do
    grep "$arg" temp1 > temp2
    mv temp2 temp1
done

cat temp1
rm temp1

mktemp generates a temporary file with a unique name and returns its name; it should be widely available.

The loop then executes grep for each argument and renames the second temp file for the next loop.

查看更多
看我几分像从前
4楼-- · 2019-07-10 05:20

This is the optimization of Diego Torres Milano's code and the answer to my original question:

#! /bin/bash
file=$1
shift
cmd="cat '$file'"
for 'a' in "$@"
do
   cmd+=" | grep '$a'"
done
eval $cmd
查看更多
▲ chillily
5楼-- · 2019-07-10 05:22

You can generate the pattern of operation and save it in a variable:

pattern="$(printf 'grep %s file.txt' "$1"; printf ' | grep %s' "${@:2}" ; printf '\n')"

and then

eval "$pattern"

Example:

% cat file.txt                                                          
foo bar
bar spam
egg

% grep_gen () { pattern="$(printf 'grep %s file.txt' "$1"; printf ' | grep %s' "${@:2}" ; printf '\n')"; eval "$pattern" ;}

% grep_gen foo bar                           
foo bar
查看更多
Explosion°爆炸
6楼-- · 2019-07-10 05:30

sed is capable of doing this perfectly with a single process, and avoids these eval shenanigans. The resulting script is actually quite simple.

#!/bin/sh
file=$1
shift
printf '\\?%s?!d\n' "$@" |
sed -f - "$file"

We generate a line of sed script for each expression; if the expression is not (!) found, we delete (d) this input line, and start over with the next one.

This assumes your sed accepts - as the argument to -f to read the script from standard input. This is not completely portable; you would perhaps need to store the generated script in a temporary file instead if this is a problem.

This uses ? as the internal regex separator. If you need a literal ? in one of the patterns, you will need to backslash-escape it. In the general case, creating a script which finds an alternative separator which is in none of the search expressions would perhaps be possible, but at that point, I'd move to a proper scripting language (Python would be my preference) instead.

查看更多
登录 后发表回答