langs não é uma matriz, mas uma string no seu código.
Para criar uma matriz e usá-la:
langs=( EN GE )
dir_EN=/xx
dir_GE=/zz
dir_ml=()
for i in "${langs[@]}"; do
declare -n p="dir_$i"
dir_ml+=( "$p" )
done
printf 'dir_ml = "%s"\n' "${dir_ml[@]}"
No loop acima, $i receberá os valores EN e GE por sua vez. Isso também introduz uma variável referência de nome p . Quando o valor de p é acessado, a string que foi atribuída à variável quando foi declarada será interpretada como um nome de variável e o valor da variável é retornado.
A saída dos itens acima será
dir_ml = "/xx"
dir_ml = "/zz"
Para usar referências de nome em bash , você precisará de bash versão 4.3 ou posterior.
Outra possibilidade interessante (mas inferior):
dir_EN=/xx
dir_GE=/zz
# (can't set dir_ml=() here as its name would be picked up by the loop)
unset dir_ml
for i in "${!dir_@}"; do
dir_ml+=( "${!i}" )
done
printf 'dir_ml = "%s"\n' "${dir_ml[@]}"
Aqui, $i obterá os valores dos nomes das variáveis dir_EN e dir_GE por sua vez. Em seguida, usamos variável indireta com ${!i} para obter o valor dessa variável. Essa variação não precisa da matriz langs , mas assume que nenhuma outra variável é chamada dir_ -something (que pode ser considerada um pouco frágil, pois um usuário pode facilmente injetar variáveis com nomes como estes no ambiente do script).
A saída é a mesma para este código que para o código acima.