Você está certo de que não pode usar | dessa maneira. A razão é que o shell já procurou por pipelines e os separou em comandos antes de fazer a substituição de variáveis. Portanto, | é tratado apenas como outro caractere.
Uma alternativa possível é colocar o caractere de canal literalmente:
$ cmd="sort -n"
$ ls | $cmd
Caso você não queira um pipeline, use cat como "nop" ou espaço reservado:
$ cmd=cat
$ ls | $cmd
Este método evita o sutilezas de eval . Veja também aqui .
Uma abordagem melhor: matrizes
Uma abordagem mais sofisticada usaria bash arrays no lugar de strings simples:
$ cmd=(sort -n)
$ ls | "${cmd[@]}"
A vantagem dos arrays torna-se importante assim que você precisar do comando cmd para conter argumentos citados.