Um subshell duplica o shell existente. Tem as mesmas variáveis¹, as mesmas funções, as mesmas opções, etc. Sob o capô, um subshell é criado com o fork chamada do sistema²; o processo filho continua a fazer o que se espera dele enquanto o pai espera (por exemplo, $(…) ) ou continua sua vida (por exemplo, … & ) ou faz o que é esperado dele (por exemplo, … | … ).
sh -c … não cria um subshell. Ele lança outro programa. Esse programa é uma concha, mas isso é apenas uma coincidência. O programa pode até ser um shell diferente (por exemplo, se você executar sh -c … do bash e sh for traço), ou seja, um programa completamente diferente que por acaso tenha semelhanças significativas em seu comportamento. Sob o capô, lançar um comando externo ( sh ou qualquer outro) chama a chamada de sistema fork e, em seguida, a execve chamada do sistema para substituir o programa shell no subprocesso por outro programa (aqui sh ).
¹ Incluindo $$ , mas excluindo algumas variáveis específicas do shell, como bash e mksh BASHPID .
² Pelo menos, essa é a implementação tradicional e usual. Os reservatórios podem otimizar o garfo, caso contrário, podem imitar o comportamento.
Páginas de manual relevantes: fork (2) , execve (2) .