A construção [ ... ] (ou o equivalente test ... ) retorna um código de conclusão de 0 para true ou 1 para false, mas nunca gera nenhuma saída, portanto $( [ ... ] ) sempre retorna uma string vazia. O código de conclusão retornado está em $? , portanto r pode ser definido como 0 ou 1 por:
[ -z "foo" ]; r=$?
Se você quiser os valores de texto true e false em r (em oposição a 0 e 1), então sua expressão está basicamente correta, mas o seguinte é um pouco mais simples:
[ -z "foo" ] && r=true || r=false