Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I've been trying to use bash to read a file character by character.

After much trial and error, I have discovered that this works:

exec 4<file.txt 
declare -i n
while read -r ch <&4; 
     while [ ! $n -eq ${#ch} ]
           do  echo -n "${ch:$n:1}"
               (( n++ ))
     echo "" 

I.e., I can read it line by line and then loop through each line char by char.

Before doing this, I had tried: exec 4<file.txt && while read -r -n1 ch <&4; do; echo -n "$ch"; done but it would skip all whitespaces in the file.

Could you please explain why? Is there a way to make the second strategy (i.e. reading char by char with bash's read) work?

share|improve this question
Set IFS to nothing to have whitespaces survive the word-splitting. – manatwork Oct 1 '12 at 12:30
Tried that with IFS='', but I guess it had to be just IFS=. Thanks! – PSkocik Oct 1 '12 at 17:53
up vote 8 down vote accepted

You need to remove whitespace characters from the $IFS parameter for read to stop skipping leading and trailing ones (with -n1, the whitespace character if any would be both leading and trailing, so skipped):

while IFS= read -rn1 a; do printf %s "$a"; done

But even then bash's read will skip newline characters, which you can work around with:

while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done

Note that bash's read can't cope with NUL characters. And ksh93 has the same issues as bash.

With zsh:

while read -ku0 a; do print -rn -- "$a"; done

(zsh can cope with NUL characters)

share|improve this answer
Thanks. Simple and beautiful. I actually tried something to this end (modifying the IFS variable), but it kind of didn't work for me so I ended up with that concoction of mine (Unnecessary playing with file descriptors, etc.). – PSkocik Oct 1 '12 at 17:39

This is a simple example using cut, a for loop & wc :

bytes=$(wc -c < /etc/passwd)

for ((i=0; i<bytes; i++)); do
    echo $file | cut -c $i

KISS isn't it ?

share|improve this answer
If that is KISS, then what is a pure bash solution: file="$(</etc/passwd)"; bytes="${#file}"; for ((i=0;i<bytes;i++)); do echo "${file:i:1}"; done? – manatwork Oct 1 '12 at 12:52
Thanks to both. Yeah, if i have to resort to getting those characters from lines, I might as well get them from the whole file. I find sch's solution the most KISS, though. – PSkocik Oct 1 '12 at 17:44

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.