Você precisa remover os caracteres de espaço em branco do parâmetro $IFS para read para parar de pular os pontos iniciais e posteriores (com -n1 , o caractere de espaço em branco se algum deles estiver à esquerda e à direita):
while IFS= read -rn1 a; do printf %s "$a"; done
Mas, mesmo assim, o read do bash irá ignorar caracteres de nova linha, com os quais você pode contornar:
while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done
Embora você possa usar IFS= read -d '' -rn1 ou melhor IFS= read -N1 (adicionado em 4.1, copiado de ksh93 (adicionado em o )), que é o comando para ler um caractere.
Observe que o read do bash não pode lidar com caracteres NUL. E o ksh93 tem os mesmos problemas que o bash.
Com zsh:
while read -ku0 a; do print -rn -- "$a"; done
(zsh pode lidar com caracteres NUL).
Note que os read -k/n/N lêem um número de caracteres , não bytes . Portanto, para caracteres multibyte, eles podem ter que ler vários bytes até que um caractere completo seja lido. Se a entrada contiver caracteres inválidos, você pode acabar com uma variável que contém uma seqüência de bytes que não formam caracteres válidos e que o shell pode acabar contando como vários caracteres . Por exemplo, em uma localidade UTF-8:
$ printf '50000ABC' | bash -c '
IFS= read -rN1 a; echo "${#a}"'
6
Esse 5 introduziria um caractere UTF-8 de 6 bytes. No entanto, o sexto ( A ) acima é inválido para um caractere UTF-8. Você ainda acaba com 50000A em $a , que bash conta como 6 caracteres embora os primeiros 5 não sejam realmente caracteres, apenas 5 bytes não fazem parte de nenhum caractere. / p>