1

I have read many sed examples on how to remove lines beginning with a specific character or character set which is not challenging but I cannot seem to crack this one. I want to remove all lines in a file that begin with the letter "i" and DO NOT end with ".conf".

The following is a list example:

include /usr/local/choops/users/2488.conf
include /usr/local/cho
include /usr/local/choops/users/2492.conf
i
incl

The 3 lines that need to be removed in the above example are:

include /usr/local/cho
i
incl

They are incomplete and cannot be in there.

I have tried a few variations of examples I have seen on StackExchange and StackOverflow as well as various other forums but I cannot seem to get it right. Here is what I've tried:

sed -i '/^i.*[^\.conf]$/d' /usr/local/choops/users/main.conf

sed -i '/^i.\*\[^\\.conf\]$/d' /usr/local/choops/users/main.conf

sed -i '/^i.*[^.conf]$/d' /usr/local/choops/users/main.conf

Where main.conf is the file that lists a set of .conf files as show above. I didn't think this one would be too complicated but none of these commands seem to work for me. I built the commands from various sources and understanding of sed. Any help would be appreciated, I'm stumped.

The following did the opposite of what I want but I guess that's an easy one:

sed -i 's/i.*.conf//' main.conf
1
  • You should have included some lines that do not start with i in your sample input/output so we could see (and test with) that aspect of your requirements. Since you didn't, you're getting some answers that would delete any line that doesn't start with i.
    – Ed Morton
    Mar 27, 2023 at 13:25

4 Answers 4

3

There are two clauses that must both be satisfied ("starts with i", "doesn't end with .conf"). For a line to be excluded from the output, each clause needs to be tested separately and then AND-ed together.

Sample input (with extra lines not beginning with "i" added):

$ cat input.txt 
include /usr/local/choops/users/2488.conf
this line doesn't begin with i
include /usr/local/cho
this line doesn't begin with i but does end in .conf
include /usr/local/choops/users/2492.conf
i
incl

with perl:

$ perl -ne 'print unless (/^i/ && ! /\.conf$/)' input.txt 
include /usr/local/choops/users/2488.conf
this line doesn't begin with i
this line doesn't begin with i but does end in .conf
include /usr/local/choops/users/2492.conf

with sed:

$ sed -ne '/^i/ {/\.conf$/!d;};p' input.txt 
include /usr/local/choops/users/2488.conf
this line doesn't begin with i
this line doesn't begin with i but does end in .conf
include /usr/local/choops/users/2492.conf

Alternatively (probably better), as @seshoumara suggests:

sed -e '/^i/ {/\.conf$/!d;}' input.txt

All three versions delete the unwanted lines from the output, printing everything else.

11
  • sed -i '/^i/ {/\.conf$/!d};' input.txt did it. I wanted to remove the lines not only show them. Thank you for showing me how.
    – robetus
    Mar 27, 2023 at 1:31
  • both the perl and sed versions do it. and both have a -i option for "in-place edit" of the original file.
    – cas
    Mar 27, 2023 at 1:33
  • It clears the data from the line but does not backspace/remove the line. Can sed doe this?
    – robetus
    Mar 27, 2023 at 1:45
  • Just remove -n and the explicit printing, it will do the printing automatically. The d command isn't just clear pattern space, it immediately starts next cycle, so no empty line will be printed.
    – seshoumara
    Mar 27, 2023 at 1:46
  • @robetus i don't understand your question. Both versions remove the unwanted lines from the output, - they don't "clear data from the line", they delete unwanted lines. As would sesoumara's suggested change.
    – cas
    Mar 27, 2023 at 1:48
2

It seems you have two conditionals, if line starts with 'i' and on true if line doesn't end with '.conf'. Instead of doing it in one search, do it in two.

sed -i '/^i/{/\.conf$/!d}' /usr/local/choops/users/main.conf

Alternatively, if you have something that does the opposite, just negate it.

sed -i '/^i.*\.conf$/!d' /usr/local/choops/users/main.conf

Notice that the dot needs escaped, otherwise it is a regex meaning any character.

The '[^abc]' regex you tried actually represents: a single character that is not 'a', not 'b', nor 'c'. Could be 'j' or 'z'. Whatever that regex will match, it is still a single character. Lastly, the characters in '[]' or '[^]' can't have any regex meaning, just a list of literal characters, so '.' will always mean dot, no need to escape it. But in // or s/// you want it escaped, for your case.

1

I want to remove all lines in a file that begin with the letter "i" and DO NOT end with ".conf"

  • Let A denote "begin with the letter i"
  • Let B denote "end with .conf"

Using Boolean logic we can say that you want to delete lines matching the condition "A and not B":

A . B'

Furthermore we can rewrite this as "the negation of { not A, or B }":

( A' + B'' )' = ( A' + B )'

Using grep we can invert this to keep (rather than removing) lines matching the expression, so:

( A' + B )

In code this then becomes,

grep -E '^[^i]|\.conf$'

Although actually we also need to account for lines that don't start with any character at all (ie they are blank),

grep -E '^[^i]|\.conf$|^$'

Using your example file you get to keep these lines:

include /usr/local/choops/users/2488.conf
include /usr/local/choops/users/2492.conf

The usual rules apply for in-place editing (it's smoke and mirrors), so we apply that ourselves

grep ... file >file.tmp &&
    mv -f -- file.tmp file
rm -f -- file.tmp
5
  • It's not possible with sed? I don't want remove and recreate a file.
    – robetus
    Mar 26, 2023 at 23:23
  • Worth a shot but didn't work: sed -i '/^[^i]|.conf$|^$/d'
    – robetus
    Mar 26, 2023 at 23:27
  • Empty lines don't start with 'i', so OP has no deletion condition for them. I understand the intention, but maybe the OP didn't want it.
    – seshoumara
    Mar 27, 2023 at 1:14
  • @robetus of course it's possible with sed. I chose to use grep. It achieves your requirement Mar 27, 2023 at 6:19
  • @seshoumara empty lines don't satisfy the removal conditions stated by the OP so we keep them Mar 27, 2023 at 6:19
0

Using any awk we can identify the lines you want to delete by simply translating your stated matching requirement, begin with the letter "i" and DO NOT end with ".conf", to code:

$ awk '/^i/ && !/\.conf$/' file
include /usr/local/cho
i
incl

then trivially negate that condition to get the lines you want to keep:

$ awk '!(/^i/ && !/\.conf$/)' file
include /usr/local/choops/users/2488.conf
include /usr/local/choops/users/2492.conf

which, if you prefer, can be abbreviated by DeMorgan's laws to:

$ awk '!/^i/ || /\.conf$/' file
include /usr/local/choops/users/2488.conf
include /usr/local/choops/users/2492.conf

You must log in to answer this question.

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