O utilitário expr analisa seus argumentos como uma expressão. Os operadores devem aparecer como argumentos independentes.
expr "$j" : '.*'
Acima, expr recebe 4 argumentos: expr , o conteúdo de $j , : e .* . Supondo que o conteúdo de $j não seja ( ou ! (ou coisas como length com algumas implementações), expr sob isso, como o operador de correspondência de padrões : aplicado ao conteúdo de $j .
Para torná-lo mais robusto, você deseja:
expr " $j" : '.*' - 1
(o segundo argumento que começa com um espaço não pode ser reconhecido como um operador expr de modo que funcione em torno dos problemas mencionados acima).
com
expr $j:'.*'
Isso seria dois argumentos ( expr e o conteúdo de $j seguido por :.* (assumindo que $j não contém caracteres em branco ou curinga, veja abaixo)). Como expr só vê um argumento (ao lado do nome do comando), não há nenhuma operação solicitada, isso é apenas um argumento de string que expr simplesmente ecoa de volta.
Agora, há vários outros problemas com seu código:
Expansão de variável e substituição de comando ( $((...)) ou a forma '...' de uso em desuso que você usa), quando não referenciado, passar split+glob . Você deseja que a parte split da parte 'cat $1' (deveria ter sido $(cat < "$1") ) a divida em palavras, mas não a parte glob, pois de outra forma expandiria * palavras por exemplo para a lista de arquivos no diretório atual; e todas as outras expansões de variáveis devem ser citadas (não necessárias em atribuições, mas as citações não prejudicam).
Além disso, você não pode usar echo para dados arbitrários .
Então deve ser:
w=0 c=0
set -f # disable glob
for i in $(cat < "$1"); do
printf '%s\n' "$i"
w="$((w + 1))"
c="$(expr " $i" : '.*' + "$c" - 1)"
done