Awk verbose version:
{
OFS=":"
FS=":"
j=NF;
for(i=8;i>=1;i--) {
# If the i field is empty and there are still missing fields (j<i)
if (!$(i) && j<i) {
# if j has a value, copy to i and clean j
if($(j)) {
$(i)=$(j);
$(j)="";
j--
# if not, fill i it with zero
} else {
$(i)=0
}
}
# Now just add the leading 0
$(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
}
print
}
And the oneliner compact one:
awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
You can use it reading from input:
$ echo ::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
0000:0000:0000:0000:0000:0000:0000:0001
$ echo 2001::1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i))) } print}'
2001:0000:0000:0000:0000:0000:0000:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0001:0001:0001:0001:0001
$ echo 2001:1:1::1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0000:0001:0001:0001:0001
$ echo 2001:1:1:1:1:1:1:1 | awk -v OFS=":" -F: '{j=NF;for(i=8;i>=1;i--) {if (!$(i) && j<i) {if($(j)) {$(i)=$(j);$(j)="";j--} else $(i)=0} $(i)=gensub(" ","0","g",sprintf("%4s",$(i)))
} print}'
2001:0001:0001:0001:0001:0001:0001:0001
Do not expect it to work when it is not a valid IPv6 address. There is no validation as IPv6 with more than 8 fields or the use of :: twice.