ChatGPT解决这个技术问题 Extra ChatGPT

Have sed ignore non-matching lines

sed

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?

The accepted answer should be the one by mouviciel: stackoverflow.com/a/1665574/869951

A
Asclepius

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'

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 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, it won't work since the last p flag will work on each matched line by the last substitution expression.
l
liori

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)


On OS X 10.8.2 I had to separate tx and d with a newline rather than a semicolon as I was getting undefined label 'x;d;:x'.
Even better: sed -e 's/.../.../' -e 'tx' -e 'd' -e ':x' (suggested in a comment on a similar question).
't' will transfer to the end of the script if no label is supplied, so simpler: sed -e 's/.../.../' -e 't' -e 'd'.
People dont know the meaning of -e option so dont mention about it generally.
undefined label 'd'
G
Greg Hewgill

Use Perl:

... |& perl -ne 'print "$1\n" if /^\[wrote (.*\.class)\]$/'

A
Amessihel

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}' <

Result: keke


O
Obviously
sed -n '/.../!p'

There is no need for a substitution.


Or sed '/.../ d' for deleting lines that do match