Using sed to delete all lines between two matching patterns
RegexSedRegex Problem Overview
I have a file something like:
# ID 1
blah blah
blah blah
$ description 1
blah blah
# ID 2
blah
$ description 2
blah blah
blah blah
How can I use a sed command to delete all lines between the #
and $
line? So the result will become:
# ID 1
$ description 1
blah blah
# ID 2
$ description 2
blah blah
blah blah
Can you please kindly give an explanation as well?
Regex Solutions
Solution 1 - Regex
Use this sed command to achieve that:
sed '/^#/,/^\$/{/^#/!{/^\$/!d}}' file.txt
Mac users (to prevent extra characters at the end of d command
error) need to add semicolons before the closing brackets
sed '/^#/,/^\$/{/^#/!{/^\$/!d;};}' file.txt
###OUTPUT
# ID 1
$ description 1
blah blah
# ID 2
$ description 2
blah blah
blah blah
###Explanation:
/^#/,/^\$/
will match all the text between lines starting with#
to lines starting with$
.^
is used for start of line character.$
is a special character so needs to be escaped./^#/!
means do following if start of line is not#
/^$/!
means do following if start of line is not$
d
means delete
So overall it is first matching all the lines from ^#
to ^\$
then from those matched lines finding lines that don't match ^#
and don't match ^\$
and deleting them using d
.
Solution 2 - Regex
$ cat test
1
start
2
end
3
$ sed -n '1,/start/p;/end/,$p' test
1
start
end
3
$ sed '/start/,/end/d' test
1
3
Solution 3 - Regex
In general form, if you have a file with contents of form abcde, where section a precedes pattern b, then section c precedes pattern d, then section e follows, and you apply the following sed
commands, you get the following results.
In this demonstration, the output is represented by => abcde
, where the letters show which sections would be in the output. Thus, ae
shows an output of only sections a and e, ace
would be sections a, c, and e, etc.
Note that if b
or d
appear in the output, those are the patterns appearing (i.e., they're treated as if they're sections in the output).
Also don't confuse the /d/
pattern with the command d
. The command is always at the end in these demonstrations. The pattern is always between the //
.
sed -n -e '/b/,/d/!p' abcde
=> aesed -n -e '/b/,/d/p' abcde
=> bcdsed -n -e '/b/,/d/{//!p}' abcde
=> csed -n -e '/b/,/d/{//p}' abcde
=> bdsed -e '/b/,/d/!d' abcde
=> bcdsed -e '/b/,/d/d' abcde
=> aesed -e '/b/,/d/{//!d}' abcde
=> abdesed -e '/b/,/d/{//d}' abcde
=> ace
Solution 4 - Regex
Another approach with sed:
sed '/^#/,/^\$/{//!d;};' file
/^#/,/^\$/
: from line starting with#
up to next line starting with$
//!d
: delete all lines except those matching the address patterns
Solution 5 - Regex
I did something like this long time ago and it was something like:
sed -n -e "1,/# ID 1/ p" -e "/\$ description 1/,$ p"
Which is something like:
-n
suppress all output-e "1,/# ID 1/ p"
execute from the first line until your pattern and p (print)-e "/\$ description 1/,$ p"
execute from the second pattern until the end and p (print).
I might be wrong with some of the escaping on the strings, so please double check.
Solution 6 - Regex
The example below removes lines between "if" and "end if".
All files are scanned, and lines between the two matching patterns are removed ( including them ).
IFS='
'
PATTERN_1="^if"
PATTERN_2="end if"
# Search for the 1st pattern in all files under the current directory.
GREP_RESULTS=(`grep -nRi "$PATTERN_1" .`)
# Go through each result
for line in "${GREP_RESULTS[@]}"; do
# Save the file and line number where the match was found.
FILE=${line%%:*}
START_LINE=`echo "$line" | cut -f2 -d:`
# Search on the same file for a match of the 2nd pattern. The search
# starts from the line where the 1st pattern was matched.
GREP_RESULT=(`tail -n +${START_LINE} $FILE | grep -in "$PATTERN_2" | head -n1`)
END_LINE="$(( $START_LINE + `echo "$GREP_RESULT" | cut -f1 -d:` - 1 ))"
# Remove lines between first and second match from file
sed -e "${START_LINE},${END_LINE}d;" $FILE > $FILE
done