Let's say I have an associative array in bash
,
declare -A hash
hash=(
["foo"]=aa
["bar"]=bb
["baz"]=aa
["quux"]=bb
["wibble"]=cc
["wobble"]=aa
)
where both keys and values are unknown to me (the actual data is read from external sources).
How may I create an array of the keys corresponding to the same value, so that I may, in a loop over all unique values, do
printf 'Value "%s" is present with the following keys: %s\n' "$value" "${keys[*]}"
and get the output (not necessarily in this order)
Value "aa" is present with the following keys: foo baz wobble
Value "bb" is present with the following keys: bar quux
Value "cc" is present with the following keys: wibble
The important bit is that the keys are stored as separate elements in the keys
array and that they therefore do not need to be parsed out of a text string.
I could do something like
declare -A seen
seen=()
for value in "${hash[@]}"; do
if [ -n "${seen[$value]}" ]; then
continue
fi
keys=()
for key in "${!hash[@]}"; do
if [ "${hash[$key]}" = "$value" ]; then
keys+=( "$key" )
fi
done
printf 'Value "%s" is present with the following keys: %s\n' \
"$value" "${keys[*]}"
seen[$value]=1
done
But it seems a bit inefficient with that double loop.
Is there a piece of array syntax that I've missed for bash
?
Would doing this in e.g. zsh
give me access to more powerful array manipulation tools?
In Perl, I would do
my %hash = (
'foo' => 'aa',
'bar' => 'bb',
'baz' => 'aa',
'quux' => 'bb',
'wibble' => 'cc',
'wobble' => 'aa'
);
my %keys;
while ( my ( $key, $value ) = each(%hash) ) {
push( @{ $keys{$value} }, $key );
}
foreach my $value ( keys(%keys) ) {
printf( "Value \"%s\" is present with the following keys: %s\n",
$value, join( " ", @{ $keys{$value} } ) );
}
But bash
associative arrays can't hold arrays...
I'd also be interested in any old school solution possibly using some form of indirect indexing (building a set of index array(s) when reading the values that I said I had in hash
above?). It feels like there ought to be a way to do this in linear time.