Home > Software design >  Linux utility to interleave contents of two files, line by line
Linux utility to interleave contents of two files, line by line

Time:12-12

I wrote a script for interweaving contents of two files. The script goes like this

#!/bin/bash

touch t_10.txt

numer1=$(cat $1 | wc -l)
numer2=$(cat $2 | wc -l)

count=1
while [ $count -le $numer1 -a $count -le $numer2 ]
    do
        head -n $count $1 | tail -n 1 >> merge.txt
        head -n $count $2 | tail -n 1 >> merge.txt
        count=$((count   1))
done

count=$((count-1))
if [ $count -lt $numer1 ]; then
    rem=$(( $numer1 - $count ))
    tail -n $rem $1 >> merge.txt
else
    rem=$(( $numer2 - $count ))
    tail -n $rem $2 >> merge.txt
fi

Can you tell me if there is any better cli utility??

CodePudding user response:

Please read why-is-using-a-shell-loop-to-process-text-considered-bad-practice. The guys who invented shell to create/destroy files and processes and sequence calls to tools also invented awk for shell to call to manipulate text.

 $ head file{1,2}
==> file1 <==
foo 1
foo 2
foo 3
foo 4
foo 5

==> file2 <==
BAR 1
BAR 2
BAR 3

$ cat tst.awk
BEGIN {
    file2 = ARGV[2]
    ARGV[2] = ""
    ARGC--
}
{ print }
(getline < file2) > 0
END {
    while ( (getline < file2) > 0 ) {
        print
    }
}

$ awk -f tst.awk file1 file2
foo 1
BAR 1
foo 2
BAR 2
foo 3
BAR 3
foo 4
foo 5

$ awk -f tst.awk file2 file1
BAR 1
foo 1
BAR 2
foo 2
BAR 3
foo 3
foo 4
foo 5

This is one of the rare occasions where it's appropriate to use getline, see http://awk.freeshell.org/AllAboutGetline.

CodePudding user response:

Try the following.

paste file1 file2 |tr "\t" "\n" >> merge.txt

CodePudding user response:

Just for fun in pure bash, making good use of array indexes, interleaving an arbitrary number of file arguments:

#!/bin/bash

fd=( stdin stdout stderr "$@" )
unset fd[0] fd[1] fd[2]

for i in "${!fd[@]}"
do
    eval "exec $i< $(printf %q "${fd[i]}")" || unset fd[i] # you could exit instead
done

until ${eof-false}
do
    eof=true
    for i in "${!fd[@]}"
    do
        if eval "IFS='' read -r line <&$i"
        then
            printf '%s\n' "$line"
            eof=false
        else
            unset fd[i]
            eval "exec $i<&-"
        fi
    done
done

note: The good part is that this code won't try to read more bytes in a file after reaching its end.

I wonder if you can do it without eval.

  • Related