SED: copy lines from a file to specific line in another file
Asked Answered
M

4

12

I can do this using the following example. The 1st command will output the lines 16...80 from file1 to patch, while the 2nd will insert the contents of patch after line 18 to file2:

sed -n 16,80p file1>patch
sed -i 18rpatch file2

However, I would like to copy directly from one file to another without using a temporary file in-between, in one command using sed (not awk, etc.). I'm pretty sure this is possible, just don't know how.

Menhaden answered 26/8, 2015 at 10:2 Comment(3)
@User112638726: Why not?!Menhaden
Well it might be if you know the exact number of lines in each file, and don't want to use -i but it's way more effort than what you currently have. Alternatively you could just use awk.Youlandayoulton
Well, if sed isn't an option then maybe awk would be (depending on the complexity of the command). Please add an answer (using awk) below so I can take a look.Menhaden
S
7

Doing this with sed requires some additional shell trickery. Assuming bash, you could use

sed -i 18r<(sed '16,80!d' file1) file2

Where <(sed '16,80!d' file1) is substituted with the name of a pipe from which the output of sed '16,80!d' file1 can be read.

Generally, I feel that it is nicer to do this with awk (if a little longer), because awk is better equipped to handle multiple input files. For example:

awk 'NR == FNR { if(FNR >= 16 && FNR <= 80) { patch = patch $0 ORS }; next } FNR == 18 { $0 = patch $0 } 1' file1 file2

This works as follows:

NR == FNR {                       # While processing the first file
  if(FNR >= 16 && FNR <= 80) {    # remember the patch lines
    patch = patch $0 ORS
  }
  next                            # and do nothing else
}
FNR == 18 {                       # after that, while processing the first file:
  $0 = patch $0                   # prepend the patch to line 18
}
1                                 # and print regardless of whether the current
                                  # line was patched.

However, this approach does not lend itself to in-place editing of files. This is not usually a problem; I'd simply use

cp file2 file2~
awk ... file1 file2~ > file2

with the added advantage of having a backup in case things go pear-shaped, but in the end it's up to you.

Samirasamisen answered 26/8, 2015 at 10:28 Comment(6)
Both sed and awk just outputs everything to stdout rather than saving the changes to file. Please edit your answer to reflect that. Btw., are you the same person as @User112638726 I was talking to before?Menhaden
Ah, I read right over the in-place editing part. Answer is updated. It'd technically be possible, with GNU awk 4.1 or later, to adapt the awk approach to in-place editing, but since there are multiple input files of which only one should be changed, I don't think it's a very good idea; you'd have to overwrite one file with its own contents, and that leaves a possibility for things to go more horribly wrong if something were overlooked. And no, I'm not User112638726.Samirasamisen
This is a side question but what's the problem with in-place editing with awk that's no problem with sed? According to this question, gawk can do that (i.e. it has the same -i option as sed). Also, thanks for the clarification about your identity. I was a bit confused that you answered the question right after he/she proposed it.Menhaden
gawk can edit in-place, but if you give it several input files, it will edit all of them in-place. You could overwrite the first input file with the data you pulled from it in the first place, and unless something goes wrong that will leave the file unchanged except for timestamps, but in my mind doing so is ugly because it is more error-prone than never touching the file in the first place.Samirasamisen
Just to make things clear, I'm gonna use these commands as "set of patches" that I can publish instead of working directly from Terminal to make changes when I can use a GUI. So I only care about to make sure they're short and simple, and also easy to understand.Menhaden
Thanks for the help, I ended up using sed -i 18r<(sed -n 16,80p file1) file2. While it's not exactly what I hoped for (it uses two sed commands), it's half-way between the ideal and the working solution.Menhaden
P
3

I have done something similar using:

    head -80 file | tail -16 > patch

Check the documentation for your local versions of head and tail, and change the two integers to suit your requirements.

Paulina answered 26/2, 2016 at 0:19 Comment(1)
tail -20 counts 20 lines from end of file, tail -n -20 counts 20 lines from beginning of file.Paulina
N
0
sed -i '1,15 d
        34 r patch
        81,$ d' YourFile

# oneliner version
sed -i -e '1,15 d' -e '34 r patch' -e '81,$ d' YourFile
  • order of line is not important.
  • You can adapt a bit or batch it with variable like this

sed -i "1,16 d $(( 16 + 18 )) r patch 81,$ d" YourFile

but add some security about line count in this case.

  • if the r line is more than 1 line, following line are still counted from original place and final file is bigger than 80 - 16 lines

i dont exactly test for line taken,excluded or modified (like 34 is the 18th line of cropped file),but principe is the same

Explaination for Lines index references used in this sample:

  • 1,15 are the heading line to remove, so file take care lines from 16 in this case
  • 34 is the line to change the content and is the result of 18th line AFTER the first new content (line 16 in our case) so 16 + 18 = 34
  • 81,$ are trailing lines to remove, $ mean last line and 81 is the first line (after 80 that is taken) of the unwanted trailing lines.
Nucleus answered 26/8, 2015 at 14:0 Comment(7)
This solution doesn't work (it just mess up the output file). Can you fix it, please? Also, use -e instead of new lines so it will take only one line instead of three.Menhaden
wich OS, work fine on my linux and on AIX (not the -i) ? you maybe need an argument after -i like on MAC. I just adapt the line number (16 -> 15, you ask to take from line 16 included)Nucleus
Hi again. I tried again using the change you proposed with the same results. My distro is Trisquel 7.0 (Ubuntu 14.04 based). This is the exact command I used: sed -i -e $((1847+1663))r../VirtualBox-4.1.40/Config.kmk -e 1,1662d -e '1681,$d' Config.kmk. The files can be obtained here: download.virtualbox.org/virtualbox/5.0.2/…. What am I doing wrong?Menhaden
Here's the link to the other file I forgot: download.virtualbox.org/virtualbox/4.1.40/…. The command should be executed from within VirtualBox-5.0.2 folder.Menhaden
sorry, cannot download files from here for testing. reading the line number from your command, it seems there is a problem. you delete lines from 1681 until the end but before, you change the line 3510 (your r). This will never include your file where it shouldNucleus
Weird. Both links are working just fine for me. You can also download the sources for the specific versions at virtualbox.org. As for the command, I'm not really sure how it's supposed to work so I just replaced the line numbers following your example. Basically, I would like to know if there's a way to achieve the results of sed -i 1847r<(sed -n 1663,1680p ../VirtualBox-4.1.40/Config.kmk) Config.kmk while using only one sed command.Menhaden
for download, it's a problem from here, not from source :-(. For th lines, i will add some explaination in reply directlyNucleus
P
0

i had this problem, i did it in 2 steps(1-tail 2-head), for example in a text file with 20 lines(test.txt), we want to copy lines from 13 to 17 to another file(final.txt),

tail -8 test.txt > temp.txt
head -5 temp.txt > final.txt

Protecting answered 21/11, 2018 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.