replace lines in one file with lines in another by line number
Using awk
:
awk -v a='2,4,5,7' -v b='1,2,5,8' '
BEGIN { split(a, ax, ","); split(b, bx, ",");
for(n in ax) mapping[ bx[n] ] =ax[n];
};
NR==FNR { if (FNR in mapping) hold[ mapping[FNR] ]=$0; next; };
{ print (FNR in hold)? hold[FNR]: $0; }' fileB fileA
Here, we pass line numbers as an awk -v
ariable in a='...'
(for fileA) and b='...'
(for fileB), then we split()
them into an array on comma character as the separator (note that a
and b
were variables, while now ax
and bx
are arrays).
then we build a another mapping
array from ax
and bx
arrays to map the lines which those should be replaced in fileA with the ones from fileB;
now keys (or indexes) of the mapping
array is line numbers of the fileB and the values of these keys are the line numbers of the fileA, as below:
the mapping
array is:
Key Value
1 2
2 4
5 5
8 7
so now what we need, that is, just to read the line numbers from fileB that match with the keys above (FNRs of 1
, 2
, 5
and 8
), so we do that with:
NR==FNR { if (FNR in mapping) hold[ mapping[FNR] ]=$0; next; };
OK, now what is the value of the mapping[FNR]
? if you check the mapping
array above, that would be:
mapping[1] --> 2; then-we-have hold[ mapping[1] ] --> hold[2]=$0
mapping[2] --> 4; then-we-have hold[ mapping[2] ] --> hold[4]=$0
mapping[5] --> 5; then-we-have hold[ mapping[5] ] --> hold[5]=$0
mapping[8] --> 7; then-we-have hold[ mapping[8] ] --> hold[7]=$0
so we used the value of mapping
array as the key for the hold
array and hold
array is now contains:
Key Value
2 Argentina
4 Switzerland
5 Denmark
7 Colombia
now the last step is to use keys in hold
array as the matched line number in fileA and replace that lines with the values of that key from the hold
array if that line number found in the array or print the line itself if not found (Ternary operator: condition? if-true : if-false
), and we do that with:
{ print (FNR in hold)? hold[FNR]: $0; }
Using standard sed
:
$ printf '%ds/^/%dc\\\\\\\n/p\n' 1 2 2 4 5 5 8 7 | sed -n -f /dev/stdin fileB | sed -f /dev/stdin fileA
Italy
Argentina
USA
Switzerland
Denmark
Japan
Colombia
The command pipeline,
printf '%ds/^/%dc\\\\\\\n/p\n' 1 2 2 4 5 5 8 7 |
sed -n -f /dev/stdin fileB |
sed -f /dev/stdin fileA
first generates a sed
substitute statement for each pair of line numbers using printf
. The output of the printf
call is the following sed
script:
1s/^/2c\\\
/p
2s/^/4c\\\
/p
5s/^/5c\\\
/p
8s/^/7c\\\
/p
This sed
script acts on line 1, 2, 5, and 8, and inserts nc\
followed by a literal newline (for some line number n
) at the start of the affected lines.
Running this across fileB
(with sed -n
) generates a new sed
script:
2c\
Argentina
4c\
Switzerland
5c\
Denmark
7c\
Colombia
The c
command replaces a line with the text following the \
, so the script will replace lines 2, 4, 5, and 7.
Applying this to fileA
generates the result.
Reading the line numbers from a file in which the first column contains line numbers for fileB
, and the second column contains line numbers for fileA
:
$ cat number-pairs
1 2
2 4
5 5
8 7
$ awk '{ printf "%ds/^/%dc\\\\\\\n/p\n", $1, $2 }' number-pairs | sed -n -f /dev/stdin fileB | sed -f /dev/stdin fileA
Italy
Argentina
USA
Switzerland
Denmark
Japan
Colombia
You may obviously swap $1
and $2
in he awk
expression if you want to store the columns in the opposite order.