0

I would like to get some explanation here! I'm new to awk and I'd like to know how do I insert a line at to of a file.

This is what I've tried so far

file.txt

content line
another line

awk command

awk  'BEGIN {print "first line" } {print}' file.txt

the output

first line
content line
another line

however, when run with -i inplace doesn't write to the file only gives me this output

first line

I would like to know what I am doing wrong and if you guys can explain it I'd really appreciate.

Minux-Dev
  • 23
  • 3

4 Answers4

2

The BEGIN{} block is processed before any files are processed which means any output generated by the BEGIN{} block has nowhere to go but to stdout.

To get the line inserted into the file you need to move the print "first line" into the main body of the script where it can be processed along with the file.

One idea based on inserting the new row while reading the first line of the input file:

$ awk -i inplace 'FNR==1 {print "first line"}1' file.txt

$ cat file.txt
first line
content line
another line

NOTES:

  • FNR==1 will apply to each file if you happen to feed multiple files to awk, otherwise NR==1 will also suffice if it's just the one input file
  • the stand-alone 1 is non-zero and thus considered by awk as 'true'; here it applies against all input lines and the default behavior for a 'true' is to pass the input line through to stdout (effectively the same as {print})
markp-fuso
  • 28,790
  • 4
  • 16
  • 36
  • I'm thankful @mark-fuso, I was really confused about it. `the BEGIN{} block has nowhere to go but to stdout.`, that means the `END` block as well, right? – Minux-Dev Jan 09 '23 at 17:13
  • @Minux-Dev correct, same applies to the `END{}` block; *NOTE:* `GNU awk` supports `BEGINFILE{}` and `ENDFILE{}` blocks whereby if the extra `print` is in one of these blocks then the output *will* go to the file – markp-fuso Jan 09 '23 at 17:15
2

The -i inplace includes the built-in inplace.awk include file to emulate sed -i in-place editing but there are some caveats that come from the method used for this emulation.

Because it works by fiddling with the processing of each input file (by using the BEGINFILE pattern), anything printed in the BEGIN selector still goes to the start-up output stream rather than to the "fiddled" output stream. That's because the first input file has yet to begin processing at that point.

So what you'll see is first line going to standard output then the two lines from the input file being printed in-place back to that file. You just may not have realised that last bit since you don't change the lines in the file when you write them back.

This was no doubt a difficult implementation decision for the creators of inplace.awk since in-place editing over multiple files needs to be catered for. The question is: where should output go in BEGIN for in-place editing?

You have a couple of options here.


First, if you know you'll only ever process one file, you can use the normal trick, with BEGIN but without inplace:

awk 'BEGIN {print "first line" } {print}' file.txt > /tmp/$$ && mv /tmp/$$ file.txt

Second, using inplace but not BEGIN, you need to first decide which of the input files it should affect. If you want it to affect all input files, that means something like:

awk -i inplace 'FNR==1 {print "first line";print;next} {print}' file1.txt file2.txt

If you want it to affect just the first input file, use NR rather than FNR (the former never decreases, the latter resets to one for each new input file).


Finally, for the case where all files should be affected, you can use the same method that inplace itself uses.

As special patterns like BEGIN are executed in order of definition (and -i comes before processing of your script), simply use BEGINFILE rather than BEGIN, as per the following transcript:

=====
pax@styx:~$ cat xx.001
Original line from xx.001

=====
pax@styx:~$ cat xx.002
Original line from xx.002

=====
pax@styx:~$ cat xx.awk
BEGINFILE {
    print "inserted line"
}
{
    print
}

=====
pax@styx:~$ awk -i inplace -f xx.awk xx.001 xx.002

=====
pax@styx:~$ cat xx.001
inserted line
Original line from xx.001

=====
pax@styx:~$ cat xx.002
inserted line
Original line from xx.002

The BEGINFILE from inplace will first weave its magic to capture output (per input file), then your BEGINFILE will print to that capture area.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
0

What I would do:

$ awk -i inplace 'NR==1{print "first line";print;next} {print}' file ; cat file
first line
original content line 1
original content line 2
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
0

Non-awk version:

I was excited to see the -i inplace option, but it's not available in macOS and BSDs. So in addition to @paxdiablo's solution of a tmp file, here's how you can prepend lines with /bin/ed.

ed -s file.txt << EOF
1i
first line
.
w
EOF
Eric Fortis
  • 16,372
  • 6
  • 41
  • 62
  • Surely macOS and *BSD have the ability to install extra packages (`gawk` in this instance)? That's gotta be better than using an editor that should have gone the way of the dinosaurs as soon as Bill Joy added `ex/vi` way back in (I think) 2BSD :-) Good answer though. – paxdiablo Jan 10 '23 at 23:11