1

I know that I can print the "next" line of a match with grep, with -A1, but what about the inverse of that? I mean, "hide the next line after a filter match".

Example:

a
b
c
d
e

cat letters.txt | grep -A1 -v "c" <-- hide "c" and "d"

alexandernst
  • 14,352
  • 22
  • 97
  • 197

4 Answers4

4

grep by itself doesn't (usually) offer this functionality, but it's easy enough with Awk or sed.

awk '/c/ { skip=1; next }
    skip { skip=0; next } 1' letters.txt
tripleee
  • 175,061
  • 34
  • 275
  • 318
2

You could do this with sed like

sed -ne '/c/{n;d;}; p' letters.txt

This will read the file letters.txt. Any line that does not match a c will be printed, if a c is matched, do not print that, and also go to the next line and delete that from the output

With GNU sed to make it easier to get N lines it can be done like

sed -ne '/c/,+1d; p;' letters.txt

and you can change the 1 to whatever number you like. Also, you could add the -i flag to sed to make it modify the file in place if you wanted to do that

Eric Renouf
  • 13,950
  • 3
  • 45
  • 67
  • This is actually matching `d` instead of "the next line". I don't know the content of the line after `c`, so this is not really helpful. – alexandernst May 20 '15 at 16:43
  • 2
    No, it isn't. The `d` is the `sed` command to delete the following line, and not a regex at all. – tripleee May 20 '15 at 16:45
  • @alexandernst Why do you think it's matching d? the `d` in the sed command is the command to delete the line – Eric Renouf May 20 '15 at 16:45
  • @EricRenouf Oh, sorry, I got confused by the `d`. – alexandernst May 20 '15 at 16:47
  • @alexandernst yeah, I guess it was a pretty confusing coincidence if one doesn't know that about sed – Eric Renouf May 20 '15 at 16:47
  • @mklement0 good point, I put a version in that will go to N easily too – Eric Renouf May 20 '15 at 16:51
  • Cool, didn't know about the `+N` address form. I know this question is tagged `linux`, bout it would still be helpful to note in your answer that `+N` is a _GNU_ Sed extension. You could simplify to `sed '/c/,+1d' letters.txt`. If you don't want to change the 1st command to the simpler `sed '/c/{N;d;}' letters.txt` (note that `N` here is a Sed function that appends the next line to the current one), please add a `;` before `}` to make the command _portable_ (to make it work on BSD and OSX platforms too). – mklement0 May 20 '15 at 17:02
2

This answer looks at general solutions with variable skip count.


If you have GNU sed, Eric Renouf's elegant answer is the simplest solution.

It can be slightly simplified to sed '/c/,+1d', or, generalized:

regex='c' count=2
sed "/${regex}/,+${count}d" letters.txt  # skips c, d, e

The +<n> form of a context address to match the <n>-th line from the start of the range (and thus the start of the range through the following <n> lines) is a GNU sed extension.

Note that $count +1 lines are skipped: the line matching the regex + $count lines after.


For portability and for more flexibility in general (such as not skipping the matching line itself), use a generalized version of tripleee's helpful awk answer:

awk -v regex='c' -v count=2 \
  '$0 ~ regex { skip=count; next } --skip >= 0 { next } 1' letters.txt # skips c, d, e

Note that $count +1 lines are skipped: the line matching the regex + $count lines after.

  • -v regex='c' -v count=2 defines awk variables with the specified values.
  • $0 ~ regex matches the line of interest
    • { skip=count; next } initializes the skip count and proceeds to the next line, effectively skipping the matching line; in the 2nd solution, the print before next ensures that it is not skipped.
    • --skip >= 0 decrements the skip count and takes action if it is (still) >= 0, implying that the line at hand should be skipped.
    • { next } proceeds to the next line, effectively skipping the current line
  • 1 is a commonly used shorthand for { print }; that is, the current line is simply printed
    • Only non-matching and non-skipped lines reach this command.
    • The reason that 1 is equivalent to { print } is that 1 is interpreted as a Boolean pattern that by definition always evaluates to true, which means that its associated action (block) is unconditionally executed. Since there is no associated action in this case, awk defaults to printing the line.

As a POSIX-compliant shell function:

# SYNOPSIS
#   matchAndSkip regex skipCount [file]
matchAndSkip() {
  awk -v regex="$1" -v count="$2" \
    '$0 ~ regex { skip=count; next } --skip >= 0 { next } 1' "${3-/dev/stdin}"
}
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Using awk you can also do:

awk -v s='c' '$0 ~ s {getline;next} 1' letters.txt
a
b
e
mklement0
  • 382,024
  • 64
  • 607
  • 775
anubhava
  • 761,203
  • 64
  • 569
  • 643