user3246010
user3246010

Reputation: 73

Shell Script - read two lines of file and read one line of another file

Say, I have two files.

File1 contains:

line1
line2
line3
line4
line5
line6
line7
line8
line9
line10

and File2 contains:

anotherline1
anotherline2
anotherline3

I want to do a while loop so that it outputs this into a File3:

line1
line2
anotherline1
line3
line4
anotherline2
line5
line6
anotherline3
line7
line8
anotherline1
line9
line10
anotherline2

I've searched here for an answer but I can't figure out how to do it. I must say I'm pretty noob at shell scripting...

Also: The "while loop" should be until reaching end of file1. Doesn't matter how long is File2, actually, in my personal scenario, File2 will always be shorter than File1. Output should be: File1Line1, File1Line2, File2Line1, File1Line3, File1Line4, File2Line2.... etc

Any help?

note: if this was already answered before, then mods please close this question. I just couldn't find any answers after googling for some hours.

EDIT: Added clarification of when to end the loop.

Upvotes: 6

Views: 1279

Answers (4)

Charles Duffy
Charles Duffy

Reputation: 296049

Easily done in native shell -- no external tools such as awk needed. Open two file descriptors, tied to different files, and iterate over both.

while :; do
  read -r line1 <&3 && printf '%s\n' "$line1" || break
  read -r line2 <&3 && printf '%s\n' "$line2" || break
  read -r line3 <&4 || { exec 4<file2 && read -r line3 <&4; }
  printf '%s\n' "$line3"
done 3<file1 4<file2 1>file3

In this case, 3 is the file descriptor number on which we opened file1, and 4 is the file descriptor number on which we opened file2. 1>file3, or simply >file3, reroutes output on file descriptor 1 (aka stdout) to the output file.

You could pick other numbers if you liked, though only the range between 3-9 is guaranteed to be free for use on all POSIX shells. (0-2 are stdin, stdout and stderr; above 10 may be used by the shell for its own use, though this is rarely the case in newer and more modern shells).

Upvotes: 1

Brett Kail
Brett Kail

Reputation: 33956

I don't know of a convenient way to do something that specific in pure shell script. You're probably better off just writing a python/perl/etc. script. Something like this awk script would work, but it's somewhat messy:

awk '{
  print; getline; print; 
  if (!getline < "file2") { 
    close("file2");
    getline < "file2"
  };
  print
}' file1

The print; getline; print will print the next line from file1, get the line after that, and then print it. The if (!getline < "file2") tries to read the next line from file2, and if it fails, closes it, implicitly reopens it, and reads the first line from file2. The final print will print that line from file2 (next or first).

Upvotes: 2

ghoti
ghoti

Reputation: 46896

If I'm interpreting your question correctly, you want to

  1. step through file1 start to finish, and
  2. insert the "next" line from file2 after every two lines, rotating back to the top of file2 whenever you run out of content.

If that's the goal, you could do it with awk in a number of ways, but the following shell snippet would probably do.

#!/usr/bin/env bash

mapfile -t file2 < file2

max=${#file2[@]}

while read line; do
  ((count++))
  echo "$line"
  if [[ $((count%2)) == 0 ]]; then
    if [[ next -ge max ]]; then
      next=0
    fi
    echo "${file2[next++]}"
  fi
done < file1

This has the down side of loading all of file2 into memory as an array. If that's a problem because the file is too big, you can perhaps add some shell glop to go find specific lines at each invocation of the loop, though that will be very slow. Better yet, define your problem more specifically, or consider alternate tools! :-)

Note also that the mapfile command is a bash-4-ism, so if you're on an older Linux, or OSX (which still ships with bash 3), you'll need a different solution ... or an upgrade to bash.

Upvotes: 3

anubhava
anubhava

Reputation: 786359

This awk should work:

awk 'FNR==NR{a[++i]=$0; next} FNR%2{p=$0; curr=(FNR==1 || curr==i) ? 1 : curr+1; next}
    {print p ORS $0 ORS a[curr]}' file2 file1
line1
line2
anotherline1
line3
line4
anotherline2
line5
line6
anotherline3
line7
line8
anotherline1
line9
line10
anotherline2

EDIT: Even shorter awk (thanks to @geirha):

awk 'FNR==NR{a[n++]=$0;next}1;!(FNR%2){print a[i++%n]}' file2 file1

Upvotes: 2

Related Questions