4

I have exported some MS Word documents to plain text and use this function to parse the contents of the .txt files in the current directory:

mo1 () {
for i in *.txt; do
    echo "File: $i"
    grep -n -HC 1 "$@" "$i"
done
}

If I had more than one pattern to look for, I could do mo1 | grep pattern2. But what if I want to do something the result of which would be like grep -E 'pattern1.*pattern2[.*...]...' depending on how many patterns are supplied to the function at runtime i.e. mo1 pattern1 pattern2 [...] etc.? I can see the @ array could provide the number of items and I could construct over a loop a variable (finalpattern='$1.*$2.*$3') that would end up being the expression used for grep. But I can't think of how to abstract that bit where you craft the expression in the function? Or is there a better/simpler way to do something like this?

5
+100

You can leverage the printf builtin.

mo1 () {
  for file in *.txt; do
    grep -n -C1 "$(printf "%s.*" "$@")" "$file"
  done
}

This simple version inserts .* after the last element. It doesn't matter for this specific use case, but in other cases (e.g. grep -o) you may need to strip off the extra .* at the end.

mo1 () {
  pattern=$(printf "%s.*" "$@")
  pattern=${pattern%??}
  for file in *.txt; do
    grep -n -C1 "$pattern" "$file"
  done
}

In bash, you can put the printf output directly in a variable, which is slightly faster than using a command substitution (but this is unlikely to ever matter, even on Cygwin where subshells are slow).

mo1 () {
  printf -v pattern "%s.*" "$@"
  pattern=${pattern%??}
  for file in *.txt; do
    grep -n -C1 "$pattern" "$file"
  done
}

If you wanted to insert a single character between the positional parameters, you could set IFS to that character and use "$@". But that doesn't work if the separator is more than one character. In ksh and bash, if there's a character that doesn't appear in the pattern, you can use that for joining and then perform a replacement. For example, here, it wouldn't make sense for patterns to contain newlines, so:

mo1 () {
  typeset IFS=$'\n'
  typeset pattern="$*"
  pattern=${pattern//$'\n'/.*}
  for file in *.txt; do
    grep -n -C1 "$pattern" "$file"
  done
}

In zsh, of course, there's a direct way.

mo1 () {
  for file in *.txt; do
    grep -n -C1 ${(j:.*:)@} $file
  done
}
1
  • Thank you for all the insight!! I think with the first one, I get some interaction because of C1, as if the output lines are context lines of the lines that contained the patterns, whereas they don't themselves contain it - at least I had that with the 3 terms I used for trying out the solutions. Otherwise this works flawlessly and will be very useful, thanks again! – user44370 Jul 18 '14 at 3:30
0

Alternatively, you can use the --file option to grep:

-f FILE, --file=FILE
    Obtain patterns from FILE, one per line.  The empty file contains
    zero patterns, and therefore matches nothing.  (-f is specified by POSIX.)
1
  • Thanks! The idea is interesting but when you create such a pattern file it's really doing OR i.e. it will find lines with either patterns from that pattern file... – user44370 Jul 18 '14 at 9:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy