Issue
I am having trouble doing an in-place text substitution with sed that involves multi-line patterns. I specifically need to cut a pattern from one line and append it to the end of the first occurrence of another pattern on a separate line. When the text is appended to the line containing the second pattern, there also needs to be some additional text added on both sides of the pattern being pasted.
In my case, the search range is as follows:
/^find.* | while/,/^done/
All text before | while
needs to be cut, and then pasted it to the end of ^done
.
In doing this, the pasted text should also be surrounded by characters as follows:
< <(
PASTED TEXT)
Finally, the pipe at the beginning of line cut from should be removed: /^| while/s/^| //
For example, if the file contains the following:
find . -mindepth 1 -maxdepth 1 -printf "%P\n\0" | sort -z | while read -d $'\0' a
do
cnt=$(( "${cnt}"+1 ))
declare select"${cnt}"="${a}"
name="${a}"
echo "Type ${cnt} for ${name::-5}"
done
The resulting text should be:
while read -d $'\0' a
do
cnt=$(( "${cnt}"+1 ))
declare select"${cnt}"="${a}"
name="${a}"
echo "Type ${cnt} for ${name::-5}"
done < <(find . -mindepth 1 -maxdepth 1 -printf "%P\n\0" | sort -z)
Note that the files being changed have multiple occurrences of these kinds of situations. These substitutions would be ran from a bash script. sed -i -e '<expression>' "$changeme"
Is this possible to do this in sed? Any help is greatly appreciated!
Solution
For the specific case of groups of lines where:
- first line starts with
find
and then contains| while
- final line starts with
done
you could try:
sed -i.old '
/^find.* | while/,/^done/ {
/^\(find.*\) | \(while.*\)/ {
# create and store new final line
h
s//done < <(\1)/
x
# edit start line
s//\2/
b
}
# load new final line
/^done/ x
}
# print (possibly-edited) line
' file
It may be safe to rearrange the code to avoid having to specify the start/finish regex twice:
sed -i.old '
1 {
h
s/.*/done/
x
}
/^(find.*\) | (while.*\)/ {
h
s//done < <(\1)/
x
s//\2/
b
}
/^done$/ x
' file
Preload hold space with the default final line.
After a (potential) final line matches, it resets the stored line so x
becomes a no-op until a new start line has been processed.
So unrelated done
lines are not affected as long as they are not nested (with inappropriate indentation) inside the find | while
loop.
Answered By - jhnc Answer Checked By - David Marino (WPSolving Volunteer)