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 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?

share|improve this question
add comment

2 Answers 2

up vote 4 down vote accepted

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
}
share|improve this answer
    
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! –  illuminÉ Jul 18 at 3:30
add comment

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.)
share|improve this answer
    
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... –  illuminÉ Jul 18 at 9:52
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.