6

I have a fileA.txt with many lines and I want to replace specific lines with other specific lines from a second file fileB.txt which also has many lines

For example: fileA.txt

Italy
Korea
USA
England
Peru
Japan
Uruguay

fileB.txt

Argentina
Switzerland
Spain
Greece
Denmark
Singapore
Thailand
Colombia

I want to replace line 2, 4, 5 and 7 in the first file with lines 1, 2, 5 and 8 from the second file:

Output:

Italy
Argentina
USA
Switzerland
Denmark
Japan
Colombia

I guess I can do it with awk or sed but if awk this code does not seems provide the information of the lines of the second file:

awk 'NR==FNR{ a[$2]=$1; next }FNR in a{ $0=a[FNR] }1' fileA.txt fileB.txt

Any suggestion?

4
  • 1
    What's the logic behind 1/2, 4, 5, 7/8? You chose them randomly? If you want to script it, it implies you want to automate some larger job, but you haven't given me a feel for what the true problem is.
    – cryptarch
    Feb 10, 2021 at 7:13
  • it is a bit complex to explain why I selected those, let's just say it random because I will have another pair of files and there the line numbers I will need to replace it is going to be different so I need a code where you input the line numbers that you want to substitute in the first file and the line numbers of the second file Feb 10, 2021 at 7:19
  • so I just modify the same code where I input different values of line numbers every time Feb 10, 2021 at 7:20
  • it would be good to have also two different files where the values of the line numbers are stored perhaps Feb 10, 2021 at 7:56

2 Answers 2

7

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 -variable 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; }
5

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.

You must log in to answer this question.

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