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

5 Answers 5

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
    Commented Jul 21, 2021 at 11:04
  • Your first suggestion could be condensed into one expression with sed '/def/!b; /jkl/d'
    – David Husz
    Commented Jul 23, 2021 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. Commented Jul 23, 2021 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
11

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
2
$ 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
0

Using Raku (formerly known as Perl_6)

raku -ne '.put  unless .grep: all(/def/ , /jkl/);'

OR

raku -ne '.put if .grep: { !/def/ || !/jkl/ };'

OR (Raku equivalent of @terdon's Perl5 code):

raku -ne '.put unless /def/ && /jkl/;'

OR

raku -ne '/def/ && /jkl/ || .put;'

OR

raku -ne '(/def/ && /jkl/) ?? {next} !! .put'

Sample Input:

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

Sample Output:

1. abc def ghi
3. jkl mno pqr
5. vwx yza bcd

Briefly, the first two answers using the -ne(non-autoprinting) linewise flag along with Raku's grep routine. The next three answers are the Raku equivalent of @terdon's Perl(5) answers.

The first two (grep) lines of code are interesting in that they use two different mechanisms: the first uses an all junction (often called on Sets within Raku), while in the second example grep is run on a {}code block, which boolifies to a True/False.

A nice variation on the first two (grep) results is taking advantages of extra parameters (:k, :v, :kv, :p) available in the grep routine. For example, simply adding the :p pairs ('adverbial' parameter) instructs the code to output results as numbered pairs:

raku -ne '.put if .grep( { !/def/ || !/jkl/ }, :p );' 

Sample Output (with index):

0 => 1. abc def ghi
2 => 3. jkl mno pqr
4 => 5. vwx yza bcd

https://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .