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

Sometimes I cat a stream like /dev/input/event0.

I want to write a script that does something every time there is more output.

The definition of more output might be every time it reads a byte.

How can that be done? is there some command that does it?

share|improve this question
up vote 4 down vote accepted

From a shell script you will be limited to complete lines. You'll need to use C/Perl/Python/whatever for finer grained reading.

while read line; do
  # do something based on content of $line; remember to quote it
done </dev/input/event0
share|improve this answer
Many shells support reading less than a line, it is just not standard. ksh can read (e.g.) five bytes with read -N5, bash can read five (possibly multibyte) characters with read -rN5, zsh can read 5 (possibly multibyte) characters with read -u0 -k5. Both bash and zsh can read bytes instead of characters by using the C locale (or probably any 8-bit character ctype). – Chris Johnsen Mar 5 '11 at 5:02
@Chris Consider writing up an answer! – Steven D Mar 5 '11 at 5:13
Apparently read -N is new in bash 4.2, so it may not be everywhere. Also, bash LC_CTYPE=C IFS= read -rN 1 seems to eat certain bytes (0x00, 0xff). ksh seems to eat 0x00 (probably due to using NUL terminated strings internally). zsh seems to work correctly though. – Chris Johnsen Mar 5 '11 at 5:30

A variation on geekosaur's answer:
You might want to try read -n 1 byte to read one byte at a time, then do something with $byte.


Just tried this as I had never used that command before (just looked up info bash), but it seems to munch all white space and line endings. I don't have an explanation for this yet.

Try the following scripts to fine tune command arguments:

(for j in $(seq 1 10); do for i in $(seq 1 100); do echo -n "$i, "; sleep .02; done; echo "& $j."; done) | (while read line; do echo $line; done)

(for j in $(seq 1 10); do for i in $(seq 1 100); do echo -n "$i, "; sleep .05; done; echo "& $j."; done) | (while read -n 1 byte; do echo -n "$byte"; done)

So unfortunately this does not give the expected result.

EDIT (with Chris' help):

(for j in $(seq 1 10); do for i in $(seq 1 100); do echo -n "$i, "; sleep .02; done; echo "& $j."; done) | (while IFS= read -N 1 byte; do echo -n "$byte"; done)

This gives exactly the expected result.
Note: whether I use -n, -N, or -rN does not change the result, it's all good (with text, I did not test the limitation that Chris talks about: 0x00 and 0xff).

share|improve this answer
That "munching" is why I said "remember to quote it"; if you say echo $foo, $foo will get all whitespace (all characters in $IFS, to be pedantic) compacted to single spaces, whereas echo "$foo" will preserve them. But you have to use it consistently to make sure the whitespace is always quoted. – geekosaur Mar 5 '11 at 5:15
@geekosaur, it is not a quoting problem, it is due to the fact that bash seems to ignore IFS members in read -n (and read -N in 4.2). The workaround is to use IFS= read -rn 1 (even then, bash still eats 0x00 and 0xff). – Chris Johnsen Mar 5 '11 at 5:37
This would be why I don't use bash; it always seems to have stupid bugs like that. (My "favorite" bash bug was in some 2.x version where backslashes in case patterns didn't stop * from being a glob match.) – geekosaur Mar 5 '11 at 5:40

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.