Search for string in sed after ocurrence of another string - sed

The input file I have looks like this
Topic1
Some text
Topic2
Some text
Topic3
Some text
I need to go to Topic2 and then replace "Some text" with "Actual input"
The following script works
/Topic2/,/Topic/ {
/Some text/c
Actual input
}
Is there a simpler way to do this. I was hoping the following will work but it does not.
/Topic2/ {
/Some text/c
Actual input
}
Update : Should have made it clearer. Am only looking for sed based solutions.

This might work for you (GNU sed):
sed -e '/Topic2/,/Topic/{//!d;/Topic2/a\bla\nbla\nbla' -e '}' file
or:
sed -e '/Topic2/,/Topic/{//!d;/Topic2/r fileb' -e '}' filea
or using Bash:
sed $'/Topic2/,/Topic/{//!d;/Topic2/a\\bla\\nbla\\nbla\n}' file
EDIT:
sed $'/Topic2/{:a;n;/Some text/!ba;c\\Actual input\n}' file

how about with awk?
awk 'p&&/Topic/{p=0}/Topic2/{p=1}p{gsub(/Some text/,"foo\nbar\nbla\nline")}7' file
above I used a multiple line replacement as example, the output with your input would be:
Topic1
Some text
Topic2
foo
bar
bla
line
Topic3
you can pass the replacement string to awk as shell variable, so that the awk one-liner would be flexible. like:
kent$ r="a
b
c"
kent$ awk -v rep="$r" 'p&&/Topic/{p=0}/Topic2/{p=1}p{gsub(/Some text/,rep)}7' f
Topic1
Some text
Topic2
a
b
c
Topic3
Some text

Here is a gnu awk solution:
awk -v RS="Topic[0-9]*" 'f=="Topic2" {$0="\nActual input\n"} NR>1{print f,$0} {f=RT}' file
Topic1
Some text
Topic2
Actual input
Topic3
Some text

Related

Replace line with file content using sed

I'm trying to replace a single line in a file with the content of another file, and print the result.
#!/bin/sh
set -e
echo "a
b
c" > template.txt
echo "Hello, World!" > foo.txt
sed -e '/b/ {
d
r /dev/stdin
}' "template.txt" < "foo.txt"
Unfortunately this results in
a
c
and when I remove the d line in the sed script, I can get
a
b
Hello, World
c
How can I get rid of the b and preserve Hello, World?
Thanks, ToxicFrog, for the answer. Apparently, d means "clear pattern space and proceed immediately to next line", so I need these commands in the opposite order. r appends the contents of the file to the output immediately rather than reading it into the pattern space.
sed -e '/b/ {
r /dev/stdin
d
}' "template.txt" < "foo.txt"
This might work for you (GNU sed):
sed '/b/cHello World!' file
Change (c) any line containing b to Hello World!.

entering a text from a text file before the first line of another file using sed

I have a text file named: "header" and I wish to insert it as the header of "anotherfile" which already contains some text.
The final output should be:
"header" text content
right above
"otherfile" text content
On ubuntu linux system I tried the following, which got me close but not quite:
cat otherfile | sed '1 r header' - > myoutputfile
the problem with this solution is that it enters all the content of "header" one line AFTER the first line of "otherfile", instead of BEFORE the first line of "otherfile".
Is there any neat way to make the content of my "header" file appear as the header of "otherfile" ?
i should mention that the following attempt
cat otherfile | sed '0 r header' - > myoutputfile
fails with the following error message:
sed: -e expression #1, char 3: invalid usage of line address 0
The answer to the question stated in your question is simply:
cat header otherfile > myoutputfile
But to answer the question in your comments: Assuming that cat otherfile is some pipeline, not actually a file, all you need is:
cat otherfile | cat header - > myoutputfile
e.g.:
$ cat file1
a
b
c
$ cat file2
foo
bar
$ cat file1 | cat file2 -
foo
bar
a
b
c
This might work for you (GNU sed):
sed -e '1h;1r header' -e '1d;2H;2g' file
or:
sed '1e cat header' file
ok, this is the solution that works for me:
cat otherfile | sed -e '2{x;G};1{h;rheader' -e 'd}' - > myoutputfile

Replacing several lines in a script with a single line using sed

Say I have a script where I want to change several lines for a single line.
For example, I got a new function that can summarize several commands, so that I can replace in my script as follows:
Original
some_code
command1
command2
command3
some_more_code
Edited
some_code
foo()
some_more_code
How would you do that using sed?
sed '/some_code/,/command3/ !b
/some_code/ b
/command3/ a\
foo()
d' YourFile
be carrefull about meta character ( like &\\^$[]{}().) in any of the pattern (except your foo() line)
I am answering my own question here.
I couldn't figure out a way to do it in one go, so I split the problem into two parts.
Part 1: replace the first line
sed -e 's/command1/foo()/g' file1 > file2
Part 2: remove the rest of the lines
sed -e '/command2/,+1d/' file2 > file3
I'd prefer a more elegant way though, where I can be flexible in the number of lines that I am replacing, possibly matching the last command in the block. Any ideas?
Just use awk:
$ awk -v RS='^$' -v ORS= '{sub(/command1\ncommand2\ncommand3/,"foo()")}1' file
some_code
foo()
some_more_code
The above uses GNU awk for multi-char RS.
This might work for you (GNU sed):
sed '/command1/,/command3/c\foo()' file

Sed or awk: how to call line addresses from separate file?

I have 'file1' with (say) 100 lines. I want to use sed or awk to print lines 23, 71 and 84 (for example) to 'file2'. Those 3 line numbers are in a separate file, 'list', with each number on a separate line.
When I use either of these commands, only line 84 gets printed:
for i in $(cat list); do sed -n "${i}p" file1 > file2; done
for i in $(cat list); do awk 'NR==x {print}' x=$i file1 > file2; done
Can a for loop be used in this way to supply line addresses to sed or awk?
This might work for you (GNU sed):
sed 's/.*/&p/' list | sed -nf - file1 >file2
Use list to build a sed script.
You need to do > after the loop in order to capture everything. Since you are using it inside the loop, the file gets overwritten. Inside the loop you need to do >>.
Good practice is to or use > outside the loop so the file is not open for writing during every loop iteration.
However, you can do everything in awk without for loop.
awk 'NR==FNR{a[$1]++;next}FNR in a' list file1 > file2
You have to >>(append to the file) . But you are overwriting the file. That is why, You are always getting 84 line only in the file2.
Try use,
for i in $(cat list); do sed -n "${i}p" file1 >> file2; done
With sed:
sed -n $(sed -e 's/^/-e /' -e 's/$/p/' list) input
given the example input, the inner command create a string like this: `
-e 23p
-e 71p
-e 84p
so the outer sed then prints out given lines
You can avoid running sed/awk in a for/while loop altgether:
# store all lines numbers in a variable using pipe
lines=$(echo $(<list) | sed 's/ /|/g')
# print lines of specified line numbers and store output
awk -v lineS="^($lines)$" 'NR ~ lineS' file1 > out

Sed replace pattern with line number

I need to replace the pattern ### with the current line number.
I managed to Print in the next line with both AWK and SED.
sed -n "/###/{p;=;}" file prints to the next line, without the p;, it replaces the whole line.
sed -e "s/###/{=;}/g" file used to make sense in my head, since the =; returns the line number of the matched pattern, but it will return me the the text {=;}
What am i Missing? I know this is a silly question. I couldn't find the answer to this question in the sed manual, it's not quite clear.
If possible, point me what was i missing, and what to make it work. Thank you
Simple awk oneliner:
awk '{gsub("###",NR,$0);print}'
Given the limitations of the = command, I think it's easier to divide the job in two (actually, three) parts. With GNU sed you can do:
$ sed -n '/###/=' test > lineno
and then something like
$ sed -e '/###/R lineno' test | sed '/###/{:r;N;s/###\([^\n]*\n\)\([^\n]*\)/\2\1/;tr;:c;s/\n\n/\n/;tc}'
I'm afraid there's no simple way with sed because, as well as the = command, the r and GNU extension R commands don't read files into the pattern space, but rather directly append the lines to the output, so the contents of the file cannot be modified in any way. Hence piping to another sed command.
If the contents of test are
fooo
bar ### aa
test
zz ### bar
the above will produce
fooo
bar 2 aa
test
zz 4 bar
This might work for you (GNU sed):
sed = file | sed 'N;:a;s/\(\(.*\)\n.*\)###/\1\2/;ta;s/.*\n//'
An alternative using cat:
cat -n file | sed -E ':a;s/^(\s*(\S*)\t.*)###/\1\2/;ta;s/.*\t//'
As noted by Lev Levitsky this isn't possible with one invocation of sed, because the line number is sent directly to standard out.
You could have sed write a sed-script for you, and do the replacement in two passes:
infile
a
b
c
d
e
###
###
###
a
b
###
c
d
e
###
Find the lines that contain the pattern:
sed -n '/###/=' infile
Output:
6
7
8
11
15
Pipe that into a sed-script writing a new sed-script:
sed 's:.*:&s/###/&/:'
Output:
6s/###/6/
7s/###/7/
8s/###/8/
11s/###/11/
15s/###/15/
Execute:
sed -n '/###/=' infile | sed 's:.*:&s/^/& \&/:' | sed -f - infile
Output:
a
b
c
d
e
6
7
8
a
b
11
c
d
e
15
is this ok ?
kent$ echo "a
b
c
d
e"|awk '/d/{$0=$0" "NR}1'
a
b
c
d 4
e
if match pattern "d", append line number at the end of the line.
edit
oh, you want to replace the pattern not append the line number... take a look the new cmd:
kent$ echo "a
b
c
d
e"|awk '/d/{gsub(/d/,NR)}1'
a
b
c
4
e
and the line could be written like this as well: awk '1+gsub(/d/,NR)' file
one-liner to modify the FILE in place, replacing LINE with the corresponding line number:
seq 1 `wc -l FILE | awk '{print $1}'` | xargs -IX sed -i 'X s/LINE/X/' FILE
Following on from https://stackoverflow.com/a/53519367/29924
If you try this on osx the version of sed is different and you need to do:
seq 1 `wc -l FILE | awk '{print $1}'` | xargs --verbose -IX sed -i bak "X s/__line__/X/" FILE
see https://markhneedham.com/blog/2011/01/14/sed-sed-1-invalid-command-code-r-on-mac-os-x/