*.c é um padrão glob, enquanto grep procura por expressões regulares.
Você deseja que ls | grep '\.c$' encontre todos os arquivos que terminam em .c .
grep corresponde a qualquer substring por padrão, não a string inteira, portanto, não é necessário escrever algo que corresponda ao início do nome do arquivo. Se você quisesse escrever isso, seria .* em uma expressão regular. . indica "qualquer caractere, exceto para uma linha que termina", e * indica "qualquer número (zero ou mais) da expressão anterior".
Como . tem um significado especial em uma expressão regular, se você quiser corresponder a um literal . , será necessário escapar com \ . Como \ tem um significado especial no shell, é necessário citar a expressão regular com aspas simples ( ' ).
Para garantir que você corresponda apenas no final do nome do arquivo, use $ , que corresponde ao final da linha.