sed: remember capture in second expression - sed

I am trying to find occurence of captured pattern and pre-pend next line with captured pattern.
For example:
...
[line 10] #---------- SOLID: tank_phys.0
[line 11] Shape {
...
[line 22] #---------- SOLID: head_phys.0
[line 23] Shape {
...
expected output:
...
[line 10] #---------- SOLID: tank_phys.0
[line 11] DEF tank Shape {
...
[line 22] #---------- SOLID: head_phys.0
[line 23] DEF head Shape {
...
Here is what I have:
sed -rn '/#---------- SOLID: (.*)_phys.0/{n ; s/Shape/DEF <PreviousCapture> Shape/p;}' g4_00.wrl
How can I substitute Shape { with DEF tank Shape { ?
Thanks
GT

With a pure sed solution:
INPUT:
$ cat file
#---------- SOLID: tank_phys.0
Shape {
abcdef
1234
#---------- SOLID: head_phys.0
Shape {
12345
gdfg
Command:
$ sed -rn '/#---------- SOLID: (.*)_phys.0/{p;s/#---------- SOLID: (.*)_phys.0/DEF \1/;N;s/\n//;s/ {2,}/ /;s/^/ /p;b};/#---------- SOLID: (.*)_phys.0/!p' file
OUTPUT:
#---------- SOLID: tank_phys.0
DEF tank Shape {
abcdef
1234
#---------- SOLID: head_phys.0
DEF head Shape {
12345
gdfg
EXPLANATIONS:
/#---------- SOLID: (.*)_phys.0/{ #this block will be executed on each line respecting the regex /#---------- SOLID: (.*)_phys.0/
p; #print the line
s/#---------- SOLID: (.*)_phys.0/DEF \1/; #replace the line content using backreference to form DEF ...
N;#append next line Shape { to the pattern buffer
s/\n//;s/ {2,}/ /;s/^/ /p; #remove the new line, add/remove some spaces
b}; #jump to the end of the statements
/#---------- SOLID: (.*)_phys.0/!p #lines that does not respect the regex will just be printed

Following simple awk may help you on same.
awk '/#---------- SOLID/{print;sub(/_.*/,"",$NF);val=$NF;getline;sub("Shape {","DEF " val " &")} 1' Input_file
Output will be as follows.
[line 10] #---------- SOLID: tank_phys.0
[line 11] DEF tank Shape {
...
[line 22] #---------- SOLID: head_phys.0
[line 23] DEF head Shape {

You can try this sed
sed -E '
/SOLID/!b
N
s/(^.*SOLID: )([^_]*)(.*\n)([[:blank:]]*)(.*$)/\1\2\3\4DEF \2 \5/
' infile

Related

sed read entire lines from one file and replace lines in another file

I have two files: file1, file with content as follows:
file1:
f1 line1
f1 line2
f1 line3
file2:
f2 line1
f2 line2
f2 line3
f2 line4
Wonder how I can use sed to read line 1 to line 3 from file1 and use these lines to replace line2 to line 3 in file 2.
The output should be like this:
f2 line1
f1 line1
f1 line2
f1 line3
f2 line4
Can any one help? Thanks,
If you have GNU sed which supports s///e, you can use the e to call head -n3 file1
I think (w/o yet testing):
sed '2d;3s/.*/head -n3 file1/e' file2
I'll go verify...
sed is the best tool for doing s/old/new on individual strings. That's not what you're trying to do so you shouldn't be considering trying to use sed for it. Using any awk in any shell on every UNIX box:
$ cat tst.awk
NR==FNR {
if ( FNR <= num ) {
new = new $0 ORS
}
next
}
(beg <= FNR) && (FNR <= end) {
if ( !done++ ) {
printf "%s", new
}
next
}
{ print }
.
$ awk -v num=3 -v beg=2 -v end=3 -f tst.awk file1 file2
f2 line1
f1 line1
f1 line2
f1 line3
f2 line4
To read a different number of lines from file1 just change the value of num, to use the replacement text for a different range of lines in file2 just change the values of beg and end. So, for example, if you wanted to use 10 lines of data from a pipe (e.g. seq 15 |) instead of file1 and wanted to replace between lines 3 and 17 of a file2 like you have but with 20 lines instead of 4 then you'd leave the awk script as-is and just tweak how you call it:
$ seq 15 | awk -v num=10 -v beg=3 -v end=17 -f tst.awk - file2
f2 line1
f2 line2
1
2
3
4
5
6
7
8
9
10
f2 line18
f2 line19
f2 line20
Try doing the same with sed for such a minor change in requirements and note how you're changing the script and it's not portable to other sed versions.

Perl command not giving expected output

Command:
perl -lpe '1 while (s/(^|\s)(0\d*)(\s|$)/$1"$2"$3/)' test5
Input:
1234 012345 0
0.000 01234 0
01/02/03 5467 0abc
01234 0123
0000 000054
0asdf 0we23-1
Current Output:
perl -lpe '1 while (s/(^|\s)(0\d*)(\s|$)/$1"$2"$3/)' test5
1234 "012345" "0"
0.000 "01234" "0"
01/02/03 5467 "0abc"
"01234" "0123"
"0000" "000054"
0asdf 0we23-1
Excepted Output:
1234 "012345" 0
0.000 "01234" 0
01/02/03 5467 "0abc"
"01234" "0123"
"0000" "000054"
"0asdf" "0we23-1"
Conditions to follow in output:
All strings starting with 0 having alphanumeric character except / and . should be double quoted.
if string starting with 0 have only 0 character should not be quoted.
Spacing between strings should be preserved.
This appears to do what you want:
#!/usr/bin/env perl
use strict;
use warnings;
while ( <DATA> ) {
my #fields = split;
s/^(0[^\.\/]+)$/"$1"/ for #fields;
print join " ", #fields, "\n";
}
__DATA__
1234 012345 0
0.000 01234 0
01/02/03 5467 0abc
01234 0123
0000 000054
0asdf 0we23-1
Note - it doesn't strictly preserve whitespace like you asked though - it just removes it and reinserts a single space. That seems to meet your spec, but you could instead:
my #fields = split /(\s+)/;
as this would capture the spaces too.
join "", #fields;
This is reducible to a one liner using -a for autosplitting:
perl -lane 's/^(0[^\.\/]+)$/"$1"/ for #F; print join " ", #F'
If you wanted to do the second bit (preserving whitespace strictly) then you'd need to drop the -a and use split yourself.

Replace the "pattern" on second-to-last line of a file

I have to replace a "pattern" with a "string" on the second-to-last line of the file - file.txt.
The below three sed commands are able to print the second-to-last line. But I need to replace a "pattern" with a "string". Any help??
sed -e '$!{h;d;}' -e x file.txt
sed -n 'x;$p' file.txt
sed 'x;$!d' file.txt
$ cat file.txt
cabbage
spinach
collard greens
corn salad
Sweet pepper
kale
How to replace the second-to-last line of a file (Sweet pepper):
a. replace "Sweet" with "green" if second-to-last line contains "Sweet pepper"
b. replace the whole line with "carrots", no matter what it contains
To change Sweet to Green on the second to last line but only if that line contains Sweet pepper:
$ sed 'x; ${/Sweet pepper/s/Sweet/Green/;p;x}; 1d' file.txt
cabbage
spinach
collard greens
corn salad
Green pepper
kale
To replace the whole of the second to last line, regardless of what it contains, to carrots:
$ sed 'x; ${s/.*/carrots/;p;x}; 1d' file.txt
cabbage
spinach
collard greens
corn salad
carrots
kale
How it works
Let's take this command and examine it one step at a time:
sed 'x; ${s/.*/carrots/;p;x}; 1d'
x
This exchanges the pattern space (which holds the most recently read line) and the hold space.
When this is done, the hold space will contain the most recently read line and the pattern space will contain the previous line.
(The exception is when we have just read the first line. In that case, the hold space will have the first line and the pattern space will be empty.)
${s/.*/carrots/;p;x}
When we are on the last line, indicated by the $, the pattern space holds the second to last line and we can perform whatever substitutions or other commands that we like. When we are done, we print the second to last line with p. Lastly, we swap pattern and hold space again with x so that the pattern space will again contain the last line. sed will print this because, by default, at the end of the commands, sed prints whatever is in the pattern space.
1d
When we are on the first line, indicated by the 1, the patten space is empty (because there was no previous line) and we delete it (d).
A still simpler method
This method is easy to understand at the cost of slower execution speed:
$ tac file.txt | sed '2 {/Sweet pepper/s/Sweet/Green/}' | tac
cabbage
spinach
collard greens
corn salad
Green pepper
kale
And, for carrots:
$ tac file.txt | sed '2 s/.*/carrots/' | tac
cabbage
spinach
collard greens
corn salad
carrots
kale
How it works: Here, we use tac to reverse the order of the lines. Observe:
$ tac file.txt
kale
Sweet pepper
corn salad
collard greens
spinach
cabbage
In this way, the second-to-last line becomes line number 2. Thus, we just simply tell sed to operate on line number 2. Afterward, we use tac again to put the lines but in correct order.
You might find an awk script easier to understand, maintain, port, etc.:
$ awk 'NR==FNR{tgt=NR-1;next} (FNR==tgt) && /Sweet pepper/ { $1="green" } 1' file file
cabbage
spinach
collard greens
corn salad
green pepper
kale
$ awk 'NR==FNR{tgt=NR-1;next} (FNR==tgt) { $0="carrots" } 1' file file
cabbage
spinach
collard greens
corn salad
carrots
kale
Want to change the line 3 before the end instead of the line 1 before the end? That's just the simple, obvious tweak to change -1 to -3:
$ awk 'NR==FNR{tgt=NR-3;next} (FNR==tgt) { $0="carrots" } 1' file file
cabbage
spinach
carrots
corn salad
Sweet pepper
kale
awk solution
$ cat 38649053
cabbage
spinach
collard greens
corn salad
Sweet pepper
kale
$tac 38649053 | awk 'NR==2{if($0=="Sweet pepper")#is record sweet pepper?
{
$1="green"} #changing sweet to green , note $1 is the first field
else{
$0="carrot" # $0 is the whole record which replaced by carrot
}}
{record[NR]=$0} #adding each record to an record number-indexed array
END{ #printing the records in reverse at the end
i=NR;for(;i>=1;i--)print record[i]}
'
cabbage
spinach
collard greens
corn salad
green pepper
kale
Sed solution
$ cat 38649053
cabbage
spinach
collard greens
corn salad
Sweet pepper
kale
$ lines=$(wc -l <38649053) # lines contains the total # of lines
$ ((s2l=--lines)) # storing the second to last line number to s2l
$ sed -E "${s2l}"'{/^Sweet pepper$/!s/.*/carrot/;/^Sweet pepper$/s/Sweet/green/}' 38649053
# applying the sed to the required line
cabbage
spinach
collard greens
corn salad
green pepper
kale

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.

optimize sed multiple expressions including white spaces and square brackets

I have following command working fine, but just for learning purpose, I want to know how can I put following three expressions of sed into one:
bash
[user#localhost]$ echo '[lib:Library10] [idx:10] [Fragment] [75] [color]'| sed -e 's/\]//g' -e 's/\[//g' -e 's/\s\+/\t/g' -e 's/\:/\t/'
lib Library10 idx:10 Fragment 75 color
sed 's/[][]//g; s/:\|\s\+/\t/g'
Demonstrating:
$ echo '[lib:Library10] [idx:10] [Fragment] [75] [color]'| sed 's/[][]//g; s/:\|\s\+/\t/g'
lib Library10 idx 10 Fragment 75 color
$ echo '[lib:Library10] [idx:10] [Fragment] [75] [color]'| sed 's/[][]//g; s/:\|\s\+/\t/g' | od -c
0000000 l i b \t L i b r a r y 1 0 \t i d
0000020 x \t 1 0 \t F r a g m e n t \t 7 5
0000040 \t c o l o r \n
0000047
If you want to put a right bracket in a character class, it must be the first character, so [][] will match either a left or a right bracket.
You can group it in two blocks:
$ sed -re 's/(\]|\[)//g' -e 's/(\s+|\:)/\t/g' <<< "[lib:Library10] [idx:10] [Fragment] [75] [color]"
lib Library10 idx 10 Fragment 75 color
That is,
sed -e 's/\]//g' -e 's/\[//g' -e 's/\s\+/\t/g' -e 's/\:/\t/'
-------------------------- ------------------------------
| delete ] and [ | | replace \s+ and : with tab |
-------------------------- ------------------------------
-re 's/(\]|\[)//g' -e 's/(\s+|\:)/\t/g'
By pieces:
sed -e 's/\]//g' -e 's/\[//g'
can be compacted as:
sed -re 's/(\]|\[)//g'
by joining the conditions with a (condition1|condition2) statement together with -r for sed.
And the same with the other expression.
As a side note, tr can be better to delete the [, ] chars:
$ echo '[lib:Library10] [idx:10] [Fragment] [75] [color]' | tr -d '[]'
lib:Library10 idx:10 Fragment 75 color
And to replace : with \t you can also use tr:
$ echo '[lib:Library10] [idx:10] [Fragment] [75] [color]' | tr ':' '\t'
[lib Library10] [idx 10] [Fragment] [75] [color]