Adding elements to a hash from an output
You don't have to write code for testing of key existence because autovivification will do the work for you. So your cycle can be written as simple:

foreach my $line (@data) {
    my ($mac, $ip, $if) = (split /\s+/, $line)[0,1,5];
    push @{$data_str{$if}}, {$ip =&gt; $mac};
}


It is good practice to name your data instead of pollute indexes all over your code. It is much better if they are all in one place. Alternative way is:

my ($mac, $ip, undef, undef, undef, $if) = split /\s+/, $line;


which shows line structure visually.

Another thing is your data structure. Why it has hash value which is array of hashes with IP as one key and mac address. This structure is not good to work with. If I would have to find which MAC belong to given IP I would have to write something like

sub get_mac {
    my ($hash, $if, $ip) = @_;
    for my $x (@{$hash-&gt;{$if}}) {
       return $x-&gt;{$ip} if exists $x-&gt;{$ip};
    }
    return ();
}


which is slow and tedious. Much better is construct two level hash:

foreach my $line (@data) {
    my ($mac, $ip, $if) = (split /\s+/, $line)[0,1,5];
    $data_str{$if}{$ip} = $mac;
}


Now if I would like to know MAC of given IP

my $mac = $data_str{$if}{$ip};


Easy and efficient. I still can get all IPs and MACs

my @ips = keys %{$data_str{$if}};
my @macs = values %{$data_str{$if}};


I can also make original pairs out of this structure

my @pairs = map {+{$_ =&gt; $data_str{$if}{$_}}} keys %{$data_str{$if}};