Em $(echo "echo 'a'; echo 'b'") , o shell vê uma substituição de comando. E é isso. Então, esse será um comando simples cujos argumentos são resultantes do operador split + glob na expansão da substituição do comando.
O shell pegará a saída do comando echo "echo 'a'; echo 'b'" , nesse caso echo 'a'; echo 'b'\n , removerá os caracteres de nova linha à direita para que se torne echo 'a'; echo 'b' , divida isso de acordo com o valor da variável $IFS .
Se $IFS não tiver sido alterado de seu padrão, serão 4 palavras: echo , 'a'; , echo e 'b' . Cada uma dessas palavras estará sujeita a globbing. Aqui, nenhum contém caracteres globbing, então essas palavras permanecerão como estão.
Portanto, temos 4 argumentos para executar um comando simples. O primeiro argumento será usado para derivar o comando a ser executado. echo está embutido na maioria dos shells. Portanto, o shell chamará seu echo integrado com esses 4 argumentos.
echo ignora seu primeiro (0º) argumento e exibe os outros separados por caracteres de espaço e finalizados por um caractere de nova linha. Algumas implementações de echo expandem sequências de escape de barra invertida, mas não há nenhuma aqui. Então, echo irá produzir:
'a'; echo 'b'\n
Se você quiser avaliar, ou seja, se quiser que a string seja interpretada como código de shell, use eval :
eval "echo 'a'; echo 'b'"
Isso também é reconhecido como um comando simples. Por causa das cotações, o shell vê duas "palavras": eval e echo 'a'; echo 'b' . Novamente, eles comporão os argumentos para o comando que é derivado do primeiro.
Aqui o comando é o comando eval builtin do shell. Novamente, eval ignora seu primeiro argumento. O que ele faz é concatenar seus outros argumentos (aqui há apenas um) com espaços e interpretar a string resultante como código shell. Ao interpretar
echo 'a'; echo 'b'
O shell vê um ; sem aspas, que delimita os comandos. O primeiro é tratado como um comando simples que acaba chamando echo com dois argumentos ... etc.