Delete a set of lines from a file in Perl - perl

I have a Verilog file that looks like this:
Line1
Line2
Line3
module1
Line4
Line5
Line6
endmodule
Line7
Line8
module2
Line9
Line11
Line12
Line13
endmodule
Line15
Line16
Here I want to delete whole modules and the module names will be specified by me. Ex: I want to delete module1 so I want lines from module1 to endmodule to be deleted(module1, Line4, Line5, Line6, endmodule). And keep the other remaining modules intact.
My expected output when I delete module1:
Line1
Line2
Line3
Line7
Line8
module2
Line9
Line11
Line12
Line13
endmodule
Line15
Line16
How do I go about it?

I'd use sed for this, not perl:
sed -e '/module1/,/endmodule/d' input.txt
X,Y specifies a range of lines to do something on, starting with the one matching X and ending with the one matching Y, and the d command basically says to delete the current line instead of printing it like normal.
If you're set on perl, the scalar form of the range operator (..) allows for the same sort of thing:
perl -ne 'print unless /module1/ .. /endmodule/' data.txt

Related

Using sed, extract text between first occurrence of a word1 and last occurrence of a word2

I need to extract text between the first occurrence of a word called "BEGIN" and the last occurrence of a word called "END" using sed.
Input:
line1
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
line9
line10
Expected Output:
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
My approach:
It extracts text between BEGIN and END. Here there are two BEGIN & END statement, and my solution extracts text between these words.
My solution fails to extract text between first occurence of word1 (BEGIN) and last occurence of word2 (END).
dsonachalam$ sed -n -e '/^BEGIN$/,/^END$/p' logs.txt
BEGIN
line2
line3
END
BEGIN
line6
line7
ENDED
END
start=$(grep -n "BEGIN" $FILE_NAME |cut -f1 -d:|head -n 1)
end=$(grep -n "END" $FILE_NAME |cut -f1 -d:|tail -n 1)
sed -n $start,"$end"p $FILE_NAME
If the file is small enough to fit memory:
$ perl -0777 -ne 'print /(^BEGIN\n.*^END\n)/ms' ip.txt
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
With a 2-pass approach to avoid having to store any text in memory so it'll work for any size input file and with 1 call to 1 standard UNIX tool to avoid spawning multiple subshells, the following will work using any awk in any shell on every UNIX box:
$ awk '
NR==FNR{ if (!beg && /BEGIN/) beg=NR; if (/END/) end=NR; next}
(beg <= FNR) && (FNR <= end)
' file file
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
A one-liner sed command would suffice (using GNU sed):
sed -E '/^BEGIN$/,$!d; :a; /(^|\n).*END$/{p;d}; $d; N; ba'
/^BEGIN$/,$!d; deletes lines above the first BEGIN. :a; /(^|\n).*END$/{p;d}; $d; N; ba accumulates ("slurps") lines into pattern space. Whenever an END line is read then the accumulated lines are printed out and pattern space is deleted starting a new cycle. Note that this "slurping" approach may be slow, or even may crash the sed process if the input is too large.
Content of input file:
line1
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
line9
line10
and using GNU sed 4.8
sed -E '/^BEGIN$/,$!d; :a; /(^|\n).*END$/{p;d}; $d; N; ba' inputfile
prints
BEGIN
line2
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
Another approach would be:
lastend=$(sed -n '/^END$/=' inputfile | tail -1)
[[ -n $lastend ]] && sed -n "/^BEGIN\$/,${lastend}p" inputfile
This two-pass approach doesn't suffer from "slurping" lines.
This might work for you (GNU sed):
sed -n '/\<BEGIN\>/{x;:a;n;/\<END\>/{x;p;ba};H;$!ba;x;//P}' file
Set automatic printing off by using the -n option and then focus on lines following one that contains the word BEGIN.
Swap to the hold space (HS) and initiate a loop that fetches the next line and if that line contains the word END swap to the HS, print its contents and repeat.
If the current line does not contain the word END, append the current line to the HS and unless it is the end of file repeat.
At the end of file, print the first line of the HS if it begins END and whatever the condition allow the file processing to terminate.
Thus processing of lines only occurs once the word BEGIN has been seen and printing of those lines every time the word END occurs.

Sed combine only certain lines within directory

I am using sed to combine lines of text files in a directory.
The command cd dir && sed -e 'N;s/\n//' *.txt works fine to do that but is there any way it can be tweaked to only combine the line sentences that start with ** with following sentence ending in **. So
This is Line1
**This is Line2
This is Line3**
This is Line4
This is Line5
Becomes
This is Line1
** This is Line2 This is Line3**
This is Line4
This is Line5
etc
sed is for simple subsitutions on individual lines, that is all. For anything else you should be using awk. This will do what you show with your sample input/output:
$ awk '{ORS=(/^\*\*/?FS:RS)}1' file
This is Line1
**This is Line2 This is Line3**
This is Line4
This is Line5
but of course it doesn't address any of the requirements you haven't shared with us yet (e.g. what to do when a line starts with ** but the next line doesn't end with ** or vice-versa or a line starts and ends with ** or a line starting with ** is at the end of the input file or....).
Sed is your friend
$ sed '/^\*\*/{:l1;/\*\*$/!{N;bl1};s/\n/ /g;}' file
This is Line1
**This is Line2 This is Line3**
This is Line4
This is Line5
You can use this sed:
sed '/^\*\*/{:loop; N; /\*\*$/{s/\n/ /g;p;d;}; b loop}' file
Test:
$ cat file
This is Line1
**This is Line2
in between
This is Line3**
This is Line4
**This is Line5
This is Line6**
$ sed '/^\*\*/{:loop; N; /\*\*$/{s/\n/ /g;p;d;}; b loop;}' file
This is Line1
**This is Line2 in between This is Line3**
This is Line4
**This is Line5 This is Line6**
$ cat ip.txt
This is Line1
**This is Line2
This is Line3**
This is Line4
This is Line5
$ # this slurps entire file
$ perl -0777 -pe 's/^(\*\*.*)\n(.*\*\*)$/$1 $2/mg' ip.txt
This is Line1
**This is Line2 This is Line3**
This is Line4
This is Line5
$ # can use this if testing start of line for ** is enough
$ perl -pe 's/\n/ / if /^\Q**/' ip.txt
Reference: How do I search and replace across multiple lines with Perl?

sed remove first pattern match only

I would like to match a set of data between two patterns and remove this data and the start/end patterns but only for the first occurrence of the pattern.
So if this is the test data:
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
TESTLINE1
TESTLINE2
TESTLINE3
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
This will quite happy remove all the pattern matches and the lines in between but I only want to remove the first pattern match and the lines in between:
sed '/PATTERNSTART/,/PATTERNEND/d' testsed.txt
Output:
TESTLINE1
TESTLINE2
TESTLINE3
Required output:
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
TESTLINE1
TESTLINE2
TESTLINE3
PATTERNSTART
LINE1
LINE2
LINE3
PATTERNEND
Any sed ideas?
It's a bit incredible-machiney, but this works:
sed '/PATTERNSTART/,/PATTERNEND/ { // { x; s/$/./; x; }; x; /.../! { x; d; }; x; }' filename
as follows:
/PATTERNSTART/,/PATTERNEND/ { # in the pattern range
// { # in the first and last line:
x
s/$/./ # increment a counter in the hold buffer by
# appending a character to it. The counter is
# the number of characters in the hold buffer.
x
}
x # for all lines in the range: inspect the
# counter
/.../! { # if it is not three or more (the counter
# becomes three with the start line of the
# second matching range)
x
d # delete the line
}
x
}
The xs in that code are largely to ensure that the counter ends up back in the hold buffer when the whole thing is over. The // bit works because // repeats the last attempted regex, which is the start pattern of the range for its first line and the end pattern for the others.
Just use awk (the cat -n is just so you can see which line numbers are being printed):
$ cat -n file | awk '/PATTERNSTART/{f=1;++c} !(f && c==1); /PATTERNEND/{f=0}'
6 PATTERNSTART
7 LINE1
8 LINE2
9 LINE3
10 PATTERNEND
11 TESTLINE1
12 TESTLINE2
13 TESTLINE3
14 PATTERNSTART
15 LINE1
16 LINE2
17 LINE3
18 PATTERNEND
Set the test on c to be the occurrence number of whatever block you want to skip:
$ cat -n file | awk '/PATTERNSTART/{f=1;++c} !(f && c==2); /PATTERNEND/{f=0}'
1 PATTERNSTART
2 LINE1
3 LINE2
4 LINE3
5 PATTERNEND
11 TESTLINE1
12 TESTLINE2
13 TESTLINE3
14 PATTERNSTART
15 LINE1
16 LINE2
17 LINE3
18 PATTERNEND
sed '/PATTERNSTART/,/PATTERNEND/{0,/PATTERNEND/d}' file
You can do this with that (quite ugly I admit) sed code:
sed -e '/PATTERNSTART/,/PATTERNEND/{ /PATTERNEND/b after; d; :after; N; s/^.*\n//; :loop; n; b loop; }' testsed.txt
Let's look at it more closely:
sed -e '/PATTERNSTART/,/PATTERNEND/{
/PATTERNEND/b after; # if we're at the end of match, go to the hack
d; # if not, delete the line and start a new cycle
:after; # Begin "end of part to delete"
N; # get the next line...
s/^.*\n//; # ...and forget about this one
# We now only have to print everything:
:loop; n; b loop;
# And you sir, have your code!
}' testsed.txt
This might work for you (GNU sed):
sed '/PATTERNSTART/,/PATTERNEND/{x;/./{x;b};x;/PATTERNEND/h;d}' file
This uses the hold space as a switch. Check the file for the desired range of lines. If encountered and the hold space is not empty, the first range has already been deleted so bail out and print as normal. If not, set the switch on the last pattern match and delete all lines within the range.
Use
sed -e '/PATTERNSTART/,/PATTERNEND/d' -e '/PATTERNEND/q' some_file.txt
The q command causes sed to quit.

How to delete the records that has '?' in a file using sed

How to delete the records that has '?' in the file ?
Input file data
12345 Line1
?
34567 Line2
?
89101 Line3
Expected Output
12345 Line1
34567 Line2
89101 Line3
sed '/?/d' yourfile
or
grep -v '?' yourfile
if you only wanted just a '?' and nothing else, do '^?$' instead of just the ?.
Seems like you want to replace a blankline followed by a ? symbol again followed blank line with a single blank line. If so then try the below perl command.
perl -0777 -pe 's/\n\?\n//g' file
Example:
$ perl -0777 -pe 's/\n\?\n//g' file
12345 Line1
34567 Line2
89101 Line3

sed - search in one file the contents of another

So there are other similar questions, but here's in particular what I want to do -
I have one really long file. long.txt that looks like
line1
line2
line3
line4
line1
line1
line2
line8
line1
line2
now, I have another file, pattern.txt that looks like
line1
line2
Finally, replace.txt that looks like
newline1
newline2
Is there a way to call sed such that after running it on the above, I end up with
newline1
newline2
line3
line4
line1
newline1
newline2
line8
newline1
newline2
This might work for you (GNU sed):
cat <<\! >cat.sed
> :a;$!{N;ba};s/\n/\\n/
> !
sed ':a;$!'"{N;ba};s/$(sed -f cat.sed pattern.txt)/$(sed -f cat.sed replace.txt)/g" long.txt
newline1
newline2
line3
line4
line1
newline1
newline2
line8
newline1
newline2
Explanation:
Build the LHS (pattern) and RHS (replace) of a sed substitution using a generic sed script - cat.sed
Plug the above substitution into another sed script that processes the long.txt file.
$ paste -d'/' pattern.txt replace.txt | sed 's#.*#s/&/#' >script.sed
$ sed -f script.sed long.txt