Let M=3 and N=4. I want to replace line M in file1 with line N in file2. I can replace with a string using sed:
sed -i '3s/.*/stringToReplace/' file1
And I can use awk to get line N from file2
awk 'NR==4' "file2"
How can I combine these two? If I try
sed -i '3s/.*/{awk 'NR==4' "file2"}/' file1
then I replace line M with literally {awk 'NR==4' "file2"}.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
There are several ways to do this properly so as to handle arbitrary input.
With GNU sed and a system that supports /dev/stdin:
sed -n "${n}{p;q;}" file2 | sed -e "$m{r/dev/stdin" -e 'd;p;}' file1
or, slightly shorter1:
sed $n'!d;q' file2 | sed -e $m'{r /dev/stdin' -e 'd;p;}' file1
With any sed and a shell that supports process substitution
sed '1h;1d;'$((m+1))'x' <(sed ${n}'!d;q' file2) file1
which could also be written as
sed ${n}'!d;q' file2 | sed '1h;1d;'$((m+1))'x' - file1
Basically, one sed invocation extracts line n from file2 which is then read by the other sed as 1st operand: it saves it into the hold buffer, deletes it and then reads the content of the 2nd operand, namely file1, exchanging buffers when on line m+1 (of the combined input).
With any sed that supports reading a script-file via -f from stdin one could run:
sed ${n}'!d;i
'${m}'c\
s/\/&&/g
q' file2 | sed -f - file1
here, the 1st sed turns line n from file2 into a script-file like
${m}c
line_n_content_here_with_any_backslash_escaped
which is then used by the 2nd sed to process file1 (namely replace line m with the following text…). Any backslashes present in the original text (along with any embedded newlines – but here there’s just one line) have to be escaped because when using any of a, i or c to add text
<backslash> characters in text shall be removed, and the following character shall be treated literally.
With any sed, you can use the always-popular substitute command making sure that the string interpolated into sed substitution escapes all reserved characters – in this particular case it’s just one line so e.g.
line=$(sed ${m}'!d;s|[/&]|\&|g;q' file2)
then substitute:
sed ${m}'s/.*/'"$line"'/' file1
With huge input files you could run:
{ head -n $((m-1)); { head -n $((n-1)) >/dev/null; head -n 1; } <file2; head -n 1 >/dev/null; cat; } <file1
which does something like this:
print (m-1) lines from file1
discard (n-1) lines from file2
print n-th line from file2
discard m-th line from file1
print the remaining lines from file1
though some heads are dumb and won’t comply with the standards so this won’t work on all setups… but where it does, it trumps sed, awk and the likes in terms of speed.
1: with some shells you might need to disable history expansion for that ! to work…
also, $n and $m don’t really need quoting here as they’re supposed to be positive integers though it doesn’t hurt either
Method 2
Try this:
$ cat f1
foo
bar
xyz
baz
temp
good
$ cat f2
1
2
3
4
5
6
$ awk -v m=3 -v n=4 'NR==FNR{if(FNR==n) s=$0; next} FNR==m{$0=s} 1' f2 f1
foo
bar
4
baz
temp
good
NR==FNRwill be true only when first file is being processedif(FNR==n) s=$0if it is nth line, save to variablenextso that rest of code is not executed as long as first file is processedFNR==m{$0=s}if it is mth line for second file argument, replace it1print input record, including any modifications- Note the order of file input arguments
Can use if(FNR==n){s=$0;nextfile} to avoid processing lines after nth line
From GNU awk manual – thanks @iruvar
NOTE: For many years, nextfile was a common extension. In September 2012, it was accepted for inclusion into the POSIX standard. See the Austin Group website.
Method 3
Short sed approach:
Sample file f1:
f1 line1 f1 line2 f1 line3 f1 line4 f1 line5
Sample file f2:
ID1,value12,value13 ID1,value22,value23 ID1,value32,value33 ID2,/value42/,~value43~ ID3,value52,value53
The job:
sed '3 s/.*/'"$(sed -n '4{ s///\//g;p;}' f2)"'/;' f1
The output:
f1 line1 f1 line2 ID2,/value42/,~value43~ f1 line4 f1 line5
Method 4
Don’t mix awk with sed. You can use a complete awk solution
file1:
file1 1 file1 2 file1 3 file1 4
file2:
file2 1
file2 2
file2 3
file2 4
awk -v m=3 -v n=4 'NR == FNR { filea[FNR]=$0 } FNR != NR { fileb[FNR]=$0 } END { for (i=1;i<=FNR;i++) { if ( i == m ) { print fileb[n] } else { print filea[i] } } } ' file1 file2
Broken down:
NR == FNR {
filea[FNR]=$0
}
FNR != NR {
fileb[FNR]=$0
}
END {
for (i=1;i<=FNR;i++) {
if ( i == m ) {
print fileb[n]
}
else {
print filea[i]
}
}
}
We compare NR and FNR to determine records in file1 and file2 (when NR=FNR, we know that we are in the first file) We set up array filea with the records in file1 and fileb with the records in file2. We then loop through all the records in the array filea printing the contents only when the passed parameter m is not equal to 3. If it is, we instead print the subscript of array fileb determined by the passed parameter n
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0