7

I created two files:

echo -e "1\n2\n3\n4\n5" > 123.txt
echo -e "a\nb\nc\nd\ne" > abc.txt

I want to get the file 123.txt with the following contents:

1
2
3
b
c
d
4
5

So in other words insert the rows with numbers from 2 to 4 of the file abc.txt into the file 123.txt after the third line.

I have reviewed the many similar questions here, but did not find a suitable answer. Nevertheless I got the lines:

sed -n '2,4p' abc.txt

and put some text into the file after the third line:

sed -i '3a\mytext' 123.txt

How to do this using the previous command stdout or/and a sed/awk single command?

2
  • Not an elegant solution. But still can be done as a single command. head -n +3 123.txt > tmp && cat abc.txt >> tmp && tail -n +2 123.txt >> tmp && mv tmp 123.txt
    – Ramesh
    Jul 8, 2014 at 16:12
  • @Ramesh I agree with you. Only corrected your command because it gives the wrong result. head -3 123.txt > tmp && tail -4 abc.txt | head -3 >> tmp && tail -2 123.txt >> tmp && mv tmp 123.txt
    – Apostle
    Jul 9, 2014 at 8:25

4 Answers 4

6

If your system has the GNU version of sed, you can use the GNU extension r command:

r filename
    As a GNU extension, this command accepts two addresses.

    Queue the contents of filename to be read and inserted into the
    output stream at the end of the current cycle, or when the next input 
    line is read. Note that if filename cannot be read, it is treated as 
    if it were an empty file, without any error indication.

    As a GNU sed extension, the special value /dev/stdin is supported for 
    the file name, which reads the contents of the standard input. 

For example,

$ sed '3r /dev/stdin' 123.txt < <(sed -n '2,4p' abc.txt)
1
2
3
b
c
d
4
5
2
  • 2
    The r command is in POSIX. The only thing that isn't POSIX (but is common even on non-GNU systems) in the sed part is /dev/stdin. If you use a pipe instead of a command substitution, your shell snippet is POSIX apart from the requirement that /dev/stdin exist. Jul 8, 2014 at 22:04
  • The answer command gives the desired result on FreeBSD (a non-GNU system) as well.
    – Apostle
    Jul 9, 2014 at 7:55
3

By using awk and sed:

$ awk 'FNR==3{print;system("sed -n '2,4p' abc.txt");next};1' 123.txt 
1
2
3
b
c
d
4
5
2

With sed, you can use the r command to insert a whole file. To insert a part of a file, extract that part and pipe it as input to sed. You can use sed to do the extraction as well.

sed -n '2,4p' abc.txt | sed -i '3r /dev/stdin' 123.txt

With awk, you can read one file and switch to another file in the middle.

awk <123.txt >123.txt.new '
    1                                    # print the input line
    NR==3 {                              # after line 3 of 123.txt, …
        while (getline < "abc.txt") {
            ++inner_NR;
            if (inner_NR >= 2) print;    # start printing at line 2 from abc.txt
            if (inner_NR == 4) break;    # stop after line 4
        }
    }
' &&
mv 123.txt.new 123.txt

You can also use head and tail to extract the parts of the files that you want to combine.

head -n 3 <123.txt >123.txt.new &&
<abc.txt tail -n +2 | head -n 3 >>123.txt.new &&
tail -n +4 123.txt >>123.txt.new &&
mv 123.txt.new 123.txt

You can combine the head+tail approach with sed. For huge files, this is likely to be the fastest.

<abc.txt tail -n +2 | head -n 3 | sed -i '3r /dev/stdin' 123.txt

Note that all these approaches write to a temporary file which is moved to 123.txt (even the approaches using sed -i, because that's what sed -i does under the hood).

2

In-place editing usinged and sed (on the bash prompt):

ed -s 123.txt<<<$'3r !sed -n "2,4p" abc.txt\nw'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .