6

How to delete all lines containing def AND jkl from the following file (lines 2 and 4)? I want the match to apply to sub-string matches of the fields, too. The fields in the file are space-separated.

$ cat test2.txt 
1. abc def ghi
2. def ghi jkl
3. jkl mno pqr
4. jkl def stu
5. vwx yza bcd

I managed to do it using a boolean OR (\|):

$ sed '/def.*jkl\|jkl.*def/d' test2.txt 
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

Isn't there a simpler syntax with a boolean AND, something like $ sed '/defANDjkl/d' ?

I tried sed '/def&jkl/d', sed '/def&&jkl/d', sed '/def\&jkl/d' and sed '/def\&\&jkl/d', but nothing works.

0
11

With sed specifically, you could do:

sed -e '/def/!b' -e /jkl/d

Where the first expression branches out (which prints the line as we didn't pass the -n option) if def is not (!) found, and the second deletes if jkl is found. So in the end the line is deleted only if both def and jkl are found.

To generalise to any number of regexps, you can do:

sed '
  /regexp1/!b
  /regexp2/!b
  /regexp3/!b
  d'

Note that \| is not a standard basic regular expression (BRE) operator. Few sed implementations support it. Standard BREs have neither OR nor AND operators. Standard EREs (extended regular expressions, as supported by sed -E with many sed implementations) do support OR (|) but not AND.

The ast-open implementation of sed does have a AND operator (&) in its augmented regexps enabled with -A or -X, but you'd need:

sed -A '/.*def.*&.*jkl.*/d'

as A&B matches on strings that are matched by both A and B.

With sed implementations that support perl-like regexps (like sed -P with ast-open's or sed -R with ssed), you can use look ahead operators:

sed -P '/^(?=.*def)(?=.*jkl)/d'

Which matches on the start of the line provided it is followed ((?=...)) by any number of characters (.*) followed by def and that it is followed by any number of characters followed by jkl.

There are more implementations of grep that support -P than sed though, so:

grep -vP '^(?=.*def)(?=.*jkl)'

would be more portable.

3
  • Great and thorough answer ! Yes, $ ssed -R '/^(?=.*def)(?=.*jkl)/d' works indeed too. That being said, AdminBee's awk solution is shorter and simpler. But I'll mark this answer as accepted, since my question was about sed. – ChennyStar Jul 21 at 11:04
  • Your first suggestion could be condensed into one expression with sed '/def/!b; /jkl/d' – David Husz Jul 23 at 15:12
  • @DavidHusz, that would not be standard sed syntax. In several seds including the original one, b; /jkl/d branches to the "; /jkl/d" label. – Stéphane Chazelas Jul 23 at 18:35
11

Since you state that substring-match would also constitute a deletion criterion, the following awk program should work:

~$ awk '!(/def/ && /jkl/)' test.txt
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

This will only print lines that do not fulfill the condition "line matches def and jkl".

If it has to be sed, you can adapt this answer by Stéphane Chazelas:

~$ sed -e '/def/!b' -e '/jkl/d' test.txt 
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd
0
9

You could also use perl instead. In this case, its syntax is far easier to understand than sed:

$ perl -ne 'print unless /def/ && /jkl/' test2.txt 
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

Or

$ perl -ne '/def/ && /jkl/ || print' test2.txt 
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

or even:

$ perl -ne '(/def/ && /jkl/) ? next : print' test2.txt 
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd
1
$ sed -ne '
  /def/G;/jkl/G
  /\n\{2\}/!P
' file
1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

$ perl -MList::Util=all -lne 'my $l.= $_;
    print if ! all { $l =~ /\Q$_/ } qw/def jkl/;
' file

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.