122

How can I make sed filter matching lines according to some expression, but ignore non-matching lines, instead of letting them print?

As a real example, I want to run scalac (the Scala compiler) on a set of files, and read from its -verbose output the .class files created. scalac -verbose outputs a bunch of messages, but we're only interested in those of the form [wrote some-class-name.class]. What I'm currently doing is this (|& is bash 4.0's way to pipe stderr to the next program):

$ scalac -verbose some-file.scala ... |& sed 's/^\[wrote \(.*\.class\)\]$/\1/'

This will extract the file names from the messages we're interested in, but will also let all other messages pass through unchanged! Of course we could do instead this:

$ scalac -verbose some-file.scala ... |& grep '^\[wrote .*\.class\]$' |
  sed 's/^\[wrote \(.*\.class\)\]$/\1/'

which works but looks very much like going around the real problem, which is how to instruct sed to ignore non-matching lines from the input. So how do we do that?

Asclepius
  • 57,944
  • 17
  • 167
  • 143
Paggas
  • 1,851
  • 3
  • 15
  • 15
  • 2
    The accepted answer should be the one by mouviciel: https://stackoverflow.com/a/1665574/869951 – Oliver Feb 28 '19 at 00:45

4 Answers4

290

If you don't want to print lines that don't match, you can use the combination of

  • -n option which tells sed not to print
  • p flag which tells sed to print what is matched

This gives:

sed -n 's/.../.../p'

Additionally, you can use a preceding matching pattern /match only these lines/ to only apply the replacement command to lines matching this pattern.

This gives:

sed -n '/.../ s/.../.../p'

e.g.:

# replace all occurrences of `bar` with `baz`
sed -n 's/bar/baz/p'

# replace `bar` with `baz` only on lines matching `foo`
sed -n '/foo/ s/bar/baz/p'

See also this other answer addressing Rapsey's comment below on multiple replacements

Thomas BDX
  • 2,632
  • 2
  • 27
  • 31
mouviciel
  • 66,855
  • 13
  • 106
  • 140
  • 5
    One downside to this approach is if you have multiple expressions that match, the result will also be printed multiple times. For example: `echo foo | sed -n -e 's/foo/bar/p' -e 's/bar/oof/p'` will output both `bar` and `oof` on separate lines. Although the goto-label variety cannot handle multiple patterns either since it will delete the line if the first pattern does not match. – Rapsey Jul 14 '16 at 16:01
  • @Rapsey that's because you are telling it to print twice. In the single sed command you've told it to print twice, each instance is printed in-situ (or maybe buffered). You'd either have to pipe instead of -e or only put 'p' on the last -e. – microbial May 22 '18 at 20:55
  • @microbial, it won't work since the last p flag will work _on each matched line_ by the last substitution expression. – Amessihel Jul 25 '20 at 21:02
107

Another way with plain sed:

sed -e 's/.../.../;t;d'

s/// is a substituion, t without any label conditionally skips all following commands, d deletes line.

No need for perl or grep.

(edited after Nicholas Riley's suggestion)

liori
  • 40,917
  • 13
  • 78
  • 105
4

Rapsey raised a relevant point about multiple substitutions expressions.

  • First, quoting an Unix SE answer, you can "prefix most sed commands with an address to limit the lines to which they apply".
  • Second, you can group commands within curly braces {} (separated with a semi-colon ; or a new line)
  • Third, add the print flag p on the last substitution

Syntax:

sed -n -e '/^given_regexp/ {s/regexp1/replacement1/flags1;[...];s/regexp1/replacement1/flagsnp}'

Example (see Here document for more details):

  • Code:

    sed -n -e '/^ha/ {s/h/k/g;s/a/e/gp}' <<SAMPLE
    haha
    hihi
    SAMPLE
    
  • Result:

    keke
    
Amessihel
  • 5,891
  • 3
  • 16
  • 40
0
sed -n '/.../!p'

There is no need for a substitution.