9

I have the following code:

while ...
  echo -n "some text"
done | while read; do
  echo "$REPLY" >> file
done

but echo works only when used without "-n" flag. looks like when using -n, the output is not flushed/read by next while loop

How can I make sure that "some text" will be read even when not followed by EOL?

codeforester
  • 39,467
  • 16
  • 112
  • 140
optivian
  • 103
  • 8
  • 1
    I can not add comment that is why i put it here. You can go [there](https://stackoverflow.com/a/36848016/4140593) –  May 01 '18 at 08:52
  • 4
    Read waits for the `EOL` or end of input i.e newline – NishanthSpShetty May 01 '18 at 09:07
  • @optivian Then the problem is not what you asked about, the problem is not that the output is not being flushed. NishanthSpShetty's comment is likely spot on. Can you edit your question to avoid saying not flushing is the problem? –  May 01 '18 at 09:20
  • You can change your output with `| fold -s -w120`. When you don't have 120 characters output, that won't be flushed, but it can help when you want to see something from a long running program without EOL's. – Walter A May 01 '18 at 09:57
  • Should `echo -n "some text"` be treated the same as `echo -n "some "; echo -n text`? No program that reads pipe can distinguish those cases. So it's unclear what you want without specifying delimiting rule. – Uprooted May 01 '18 at 10:41
  • No, rather after "some text" I could add EOL to be able to read it in next while loop. Right now it's not `visible` by next loop :( and I can live with additional EOL at the very end. – optivian May 01 '18 at 11:15
  • Why do you need `-n` in the first place? – chepner May 01 '18 at 15:15

4 Answers4

4

You can't distinguish between

echo -n "some text"

and

echo -n "some t"
echo -n "ext"

so you need some kind of delimiting rule. Usually EOL is used for that. read supports custom delimiter via -d or can split based on number of chars via -n or -N. For example you can make read fire on each symbol:

echo -n qwe | while read -N 1 ch; do echo $ch; done
Uprooted
  • 941
  • 8
  • 21
  • That behavior of `read` surprises me. Shouldn't the EOF indicate the end of the last "line" in the case of `(something which produces output) | read`?? So `read` says "I'm supposed to read lines, this is no line, forget it"? – Peter - Reinstate Monica May 01 '18 at 12:50
  • 3
    `read` reads a line of text, and POSIX explicitly defines a line of text as ending with a newline. If `read` gets to the end of the file that doesn't end with a newline, it still populates the variable with what it *could* read, but it returns a non-zero exit status as it failed to read a *complete* line. – chepner May 01 '18 at 15:14
  • You should also `echo` spaces and newlines. – Walter A May 01 '18 at 20:11
1

You can start with defining your own delimiter:

while :; do
  echo -n "some text"
  sleep 2
done | while read -d' ' reply; do
  echo "-$reply-"
done

This prints:

-some-
-textsome-
-textsome-

For an email perhaps it makes sense to use . as a delimiter, but you need to decide on some tokenization scheme.

perreal
  • 94,503
  • 21
  • 155
  • 181
  • thanks but that's not what I want - the some text might be dynamic and I do not know delimiter... maybe sth like putting EOL after while loop finishes and appending it to a stream before reading input in next loop? – optivian May 01 '18 at 11:06
  • yes you need to do something depending on your problem. – perreal May 01 '18 at 11:33
1

The workaround would be (following original example):

while ...
  echo -n "some text"
done | (cat && echo) | while read; do 
  echo "$REPLY" >> file
done

This will append EOL to the test stream & allow read to read it. The side effect will be an additional EOL at the end of stream.

Maciej
  • 1,954
  • 10
  • 14
0

You can make read read one char a time, but should add something for reading special characters (newlines, spaces): IFS=.
I want to show that I really capture the characters, so I will uppercase the replies.

i=0
while (( i++<5 )) ; do
  echo -n "some text $i. "
  sleep 1;
done | while IFS= read -rn1 reply; do
  printf "%s" "${reply^^}"
done

This solution has one feature: You will not see any newlines. When you want to see them too, you need to fix this with

i=1
while (( i++<5 )) ; do
  echo -n "some text $i.
second line."
  sleep 1;
done | while IFS= read -rn1 reply; do
  if (( ${#reply} == 0 )); then
     echo
  else
     printf "%s" "${reply^^}"
  fi
done
Walter A
  • 19,067
  • 2
  • 23
  • 43