59

File1 contents:

line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 

File2 contents:

line1-file2     "25"  
line2-file2     "24"  
Pointer-file2   "23"  
line4-file2     "22" 
line5-file2     "21"

After the execution of perl/shell script, File2 content should become:

line1-file2     "25"  
line2-file2     "24" 
line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 
Pointer-file2   "23" 
line4-file2     "22" 
line5-file2     "21"

i.e paste the contents of File1 in File2 before the line that contains "Pointer".

3

11 Answers 11

68

The sed utility has a function for that and can do the modification in-place:

sed -i -e '/Pointer/r file1' file2

But this puts your Pointer line above the file1 contents. To put it below, delay line output:

sed -n -i -e '/Pointer/r file1' -e 1x -e '2,${x;p}' -e '${x;p}' file2 

With GNU sed:

sed '/Pointer/e cat file1' file2

As per the manual for the e [command]:

Note that, unlike the r command, the output of the command will be printed immediately; the r command instead delays the output to the end of the current cycle.

3
  • 13
    Could you please explain what -e 1x -e '2,${x;p}' -e '${x;p}' do ? I understand that you exchange stuff in the pattern buffer and then print it but I don't know what nor why you added the quiet option -n at the beginning.
    – hdl
    Sep 24, 2015 at 14:47
  • 1
    @jfg956 Is it possible to just substitute and delete the 'Pointer' part from the original file. This I can figure out with a second sweep of sed, but is it possible to do it in one run? Feb 20, 2019 at 12:08
  • to delete the matching line, use sed -e '/Pointer/{r file1' -e 'd}' file2
    – Sundeep
    Apr 4, 2020 at 8:29
22

Without using sed or awk.

First, find the line on which your pattern is:

line=$(grep -n 'Pointer' file2 | cut -d ":" -f 1)

Then, use 3 commands to output the wanted result:

{ head -n $(($line-1)) file2; cat file1; tail -n +$line file2; } > new_file

This has the drawback of accessing 3 times file2, but might be clearer than a sed of awk solution.

14

awk makes this fairly easy.
Insert the line before the file:

awk '/Pointer/{while(getline line<"innerfile"){print line}} //' outerfile >tmp
mv tmp outerfile

To make the inner file print after the Pointer line, just switch the order of the patterns (you need to add a semicolon to get the default action), and you can drop the line variable:

awk '//; /Pointer/{while(getline<"innerfile"){print}}' outerfile >tmp
mv tmp outerfile

And just because no one has used perl yet,

# insert file before line
perl -e 'while(<>){if($_=~/Pointer/){system("cat innerfile")};print}' outerfile

# after line
perl -e 'while(<>){print;if($_=~/Pointer/){system("cat innerfile")}}' outerfile
6
  • its working, but its getting removed the line containing pointer Feb 28, 2012 at 18:18
  • likewise, how to Paste the contents of file 1 in file 2 after that "Pointer" containing line using awk Feb 28, 2012 at 18:21
  • @user1228191 Fixed the first, added the second.
    – Kevin
    Feb 28, 2012 at 18:42
  • The 'perl' version doesn't seem to be working. system("cat innerfile") outputs the innerfile to the console. Am I missing something?
    – kaartic
    Jul 9, 2017 at 10:30
  • awk command [gawk '/<body>/{while(getline line<"$HOME/bin/SrunScenario.style"){print line}} //' index.html > new_index.html] just loops and prints millions of lines. gawk V4.2.0 What am I missing here?
    – JESii
    Feb 13, 2018 at 23:15
9

Use a loop to read the lines in file2. If you find a line starting with Pointer, then print out file1. This is shown below:

#!/bin/bash
while IFS= read -r line
do
    if [[ "$line" =~ ^Pointer.*$ ]]
    then
        cat file1
    fi
    echo "$line"
done < file2
9

An easy job for ed:

ed -s file1 <<IN
/Pointer/-r file2
,p
q
IN

-r file1 reads in the specified file to after the addressed line, which in this case is the line previous to the first line matching Pointer. So this will insert the content of file2 just once even if Pointer occurs on multiple lines. If you want to insert it before each matching line add global flag:

ed -s file1 <<IN
g/Pointer/-r file2
,p
q
IN

Replace ,p with w if you want to edit the file in-place.


The accepted sed answer does work for most cases but if the marker is on the last line, the command won't work as expected: it will insert the content of File1 after the marker.
I have initially tried with:

sed '/Pointer/{r file1
N}' file2

which also works fine (as r will do its magic at the end of the cycle) but has the same problem if the marker is on the last line (there's no Next line after the last line). To work around that, you could add a newline to your input:

sed '/Pointer/{              # like the first one, but this time even if the
r file1                      # marker is on the last line in File2 it
N                            # will be on the second to last line in
}                            # the combined input so N will always work;
${                           # on the last line of input: if the line is
/^$/!{                       # not empty, it means the marker was on the last
s/\n$//                      # line in File2 so the final empty line in the
}                            # input was pulled i\n: remove the latter;
//d                          # if the line is empty, delete it
}' file2 <(printf %s\\n)

This will insert file2 content before each matching line. To insert it only before the first matching line you could use a loop and just pull in the next line until you get to end of file:

sed '/Pointer/{
r file2
N
:l
$!n
$!bl
}
${
/^$/!{
s/\n$//
}
//d
}' file1 <(printf %s\\n)

With these sed solutions you lose the ability to edit in-place (but you can redirect to another file).

8

[Insert file contents into another file BEFORE pattern]

sed -i '/PATTERN/r file1' -e //N file2

[After Pattern]

sed -i '/PATTERN/r file1' file2
1
  • N works nicely, but not if the PATTERN matches last line of input
    – Sundeep
    Aug 29, 2019 at 7:49
5

There are a few ways to go about this w/ sed. One way is a delayed read as is recommended in the accepted answer. It could also be written like:

sed -e '$!N;P;/\nPointer/r file1' -e D file2

...with a little explicit look-ahead instead of the look-behind implemented elsewhere with the hold buffer. That will inevitably have the same problem with the last line that @don_crissti notes, though, because N does increment the line cycle and the read command is applied by line number.

You can get around it:

echo | sed -e '$d;N;P;/\nPointer/r file1' -e D file2 -

Not all seds will interpret the - to mean standard input, but many do. (POSIX says sed should support - to mean standard-in if the implementer wants - to mean standard-in???)

Another way is to handle the appended content in order. There is another command that schedules output in the same way read does, and sed will apply it and read in the order they're scripted. It's a little more involved though - it entails using one sed to append the Pointer match to the output of another sed in its script.

sed '   /Pointer/!d                  #only operate on first match
        s/[]^$&\./*[]/\\&/g;H        #escape all metachars, Hold
        s|.*|/&/!p;//!d|p;g          #print commands, exchange
        s|.|r file1&a\\&|;q' file2|  #more commands, quit
        sed -nf - file2              #same input file

So basically the first sed writes the second sed a script, which the second sed reads on standard-input (maybe...) and applies in turn. The first sed only works on the first match for Pointer found, and afterward quits input. Its job is to...

  1. s/[]^$&\./*[]/\\&/g;H
    • Make sure that all pattern chars are safely backslash-escaped because the second sed is going to need to interpret every bit it reads literally to get it right. Once that's done, put a copy in Hold space.
  2. s|.*|/&/!p;//!d|p; x
    • Tell the second sed to print every input line !but the /&/ one we just pattern-safed; and then to delete all of the same. print the commands at the second sed, then exchange the hold and pattern buffers to work on our saved copy.
  3. s|.|r file1&a\\&|p;q
    • The only char we work with here is a \newline because sed will have prepended one when we Held the line before. So we insert the command r file1 and follow it with our \newline then the command a\\ for append followed also by a \newline. All of the rest of our Held line follows that last \newline.

The script that the first writes looks something like this:

/Pointer-file2   "23"/!p;//!d
r file1
a\
Pointer-file2   "23"

Basically the second sed will print every line but the one the first sed sets it up to append. For that particular line two delayed writes to standard-out are scheduled - the first is the read of file1 and the second is a copy of the line we want after it. The first sed's doctoring isn't even necessary in this case (see? no backslashes) but it is important to safely escape in the way I do here whenever a pattern match is repurposed as input.

Anyway, so... there are a few ways.

3

This is fairly simple with AWK:

File1 into File2 before pattern = "Pointer"

First load the contents of File1 into a variable

f1="$(<File1)"

then do the insertion

awk -vf1="$f1" '/Pointer/{print f1;print;next}1' file2

(Or, if you want to insert File1 after "Pointer")

awk -vf1="$f1" '/Pointer/{print;print f1;next}1' file2
3

my preferred way: templating.

sed 's/CHANGEME/$x/g' origfile | x="$(<file2insert)" envsubst '$x' > newfile

This will replace every CHANGEME occurence in origfile with the content of file2insert. Remove last g from sed to replace only the first occurrence of CHANGEME.

2
  • How can you use $x in your first command, when it is only defined in your second command?
    – Totor
    Oct 21, 2016 at 13:03
  • the "$x" in the first command is only a placeholder to be evaluated by envsubst in the second command. With single quotes of sed script, $x is not evaluated by your shell.
    – nrc
    Oct 21, 2016 at 13:12
1
 awk '/Pointer/{system("cat File1")}1' File2
-2
sed '2r fil1' fil2

output

line1-file2     "25"  
line2-file2     "24"  
line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 
Pointer-file2   "23"  
line4-file2     "22" 
line5-file2     "21"
1
  • There are other sed-based options that answer the question about dynamically reading in file2 at the line designated by "Pointer" and not fixed at line 2.
    – Jeff Schaller
    Oct 15, 2021 at 13:01

You must log in to answer this question.

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