Take the 2-minute tour ×
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.

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; 
     n=0
     while [ ! $n -eq ${#ch} ]
           do  echo -n "${ch:$n:1}"
               (( n++ ))
          done
     echo "" 
     done

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
4  
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! –  ThorX89 Oct 1 '12 at 17:53
add comment

2 Answers

up vote 5 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.). –  ThorX89 Oct 1 '12 at 17:39
add comment

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

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

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

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. –  ThorX89 Oct 1 '12 at 17:44
add comment

Your Answer

 
discard

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.