Olá Rafael,
Esse comportamento ocorre porque o pipeline faz com que o comando read seja executado em um subshell, criando uma nova variável de contexto. Com isso o loop do while é executado em um novo contexto com a cópia da variável criada inicialmente 0, quando o loop é terminado a cópia é descartada assim como o subshell e a variável original do shell pai não foi alterada e é sempre 0.
Dessa forma, para evitar esse cenário podemos colocar todo o comando em uma linha para termos assim o redirect de uma saída de um comando para outro como você fez, se for necessário fazermos em linha separada, uma alternativa seria executar todo o bloco do while em um subshell:
i=0
cat /etc/passwd |
{
while read L
do
i=$((i+1))
done
echo $i
}