Вам нужно удалить пробельные символы из $IFSпараметра, readчтобы прекратить пропускать начальные и конечные -n1символы (с помощью символа пробела, если он есть, и начальный, и конечный символы, поэтому пропускаются):
while IFS= read -rn1 a; do printf %s "$a"; done
Но даже тогда bash's readпропустит символы новой строки, с которыми вы можете обойти:
while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done
Хотя вы можете использовать IFS= read -d '' -rn1вместо этого или даже лучше IFS= read -N1(добавлено в 4.1, скопировано из ksh93(добавлено в o)) команду, которая читает один символ.
Обратите внимание, что bash не readможет справиться с NUL-символами. И у ksh93 есть те же проблемы, что и у bash.
С зш:
while read -ku0 a; do print -rn -- "$a"; done
(Zsh может справиться с NUL-символами).
Обратите внимание, что они read -k/n/Nчитают количество символов , а не байтов . Поэтому для многобайтовых символов им, возможно, придется читать несколько байтов, пока не будет прочитан полный символ. Если входные данные содержат недопустимые символы, вы можете получить переменную, которая содержит последовательность байтов, которая не образует допустимых символов и которую оболочка может в итоге считать за несколько символов . Например, в локали UTF-8:
$ printf '\375\200\200\200\200ABC' | bash -c '
IFS= read -rN1 a; echo "${#a}"'
6
Это \375введет 6-байтовый символ UTF-8. Тем не менее, шестой ( A) выше недействителен для символа UTF-8. Вы по-прежнему получаете \375\200\200\200\200Ain $a, который bashсчитается как 6 символов, хотя первые 5 из них на самом деле не являются символами, только 5 байтов не являются частью какого-либо символа.
IFSничего не нужно, чтобы пробелы выживали при разделении слов.