quanta
quanta

Reputation: 4530

bash - `read` cannot read from a redirect, `cat` works fine?

I've been trying to send some AT commands to my modem and want to capture response into a variable. Here's my code:

exec 3<>/dev/ttyUSB3

echo -e "AT+CGSN\n" >&3

cat <&3
#read -r RESPONSE <&3
#echo "Response was $RESPONSE"

exec 3<&-
exec 3>&-

Results:

$ ./imei_checker.sh 
AT+CGSN


356538041935676



OK

AT+CGSN


356538041935676



OK

But if I change cat to read, it doesn't work:

$ ./imei_checker.sh 
Response was AT+CGSN

2 more questions:

  1. Why it show the dupplicate output?
  2. How do I close the file handle properly? exec 3<&- and exec 3>&- seems doesn't work. I have to press Ctrl+C to get control of the Terminal back.

Upvotes: 2

Views: 996

Answers (3)

paxdiablo
paxdiablo

Reputation: 881273

read will only read a single line, unlike cat which will basically read and echo until end of file.

For a read version, you're best off reading with a timeout up until the point you get the OK (and storing any line that contains a lot of digits).

I think you'll find that it's not the closing of the number 3 file handle that's stopping things - it's more likely to be the cat which will continue to read/echo until an end-of-file event that isn't happening.

You can be certain of this if you just put:

echo XYZZY

immediately before the closing exec statements. If it's still in the cat, you'll never see it.

So, using a looping read version will probably fix that as well.


By way of example, here's how you can use read to do this with standard input:

#!/bin/bash

NUM=
while true ; do
    read -p "> " -t 10 -r RESP <&0
    if [[ $? -ge 128 ]] ; then RESP=OK ; fi
    echo "Entered: $RESP"
    if [[ $RESP = OK ]] ; then break ; fi

    if [[ $RESP =~ ^[0-9] ]] ; then NUM=$RESP ; fi
done

echo "Finished, numerics were: '$NUM'"

It uses the timeout feature of read to detect if there's no more input (setting the input to OK so as to force loop exit). If you do get an OK before then, it exits normally anyway, the timeout simply caters for the possibility that the modem doesn't answer as expected.

The number is set initially to nothing but overwritten by any line from the "modem" that starts with a number.

Two sample runs, with and without an OK response from the "modem":

pax> ./testprog.sh
> hello
Entered: hello
> 12345
Entered: 12345
> OK
Entered: OK
Finished, numerics were: '12345'

pax> ./testprog.sh
> hello
Entered: hello
> now we wait 10 secs
Entered: now we wait 10 secs
> Entered: OK
Finished, numerics were: ''

It wouldn't be too hard to convert that to something similar with you modem device (either read <&3 or read -u3 will work just fine).


That would basically translate to your environment as:

exec 3<>/dev/ttyUSB3
echo -e "AT+CGSN\n" >&3
NUM=
while true ; do
    read -t 10 -r RESP <&3
    if [[ $? -ge 128 ]] ; then RESP=OK ; fi
    echo "Entered: $RESP"
    if [[ $RESP = OK ]] ; then break ; fi
    if [[ $RESP =~ ^[0-9] ]] ; then NUM=$RESP ; fi
done
echo "Finished, numerics were: '$NUM'"
exec 3<&-
exec 3>&-

Now I haven't tested that since I don't have a modem hooked up (been on broadband for quite a while now) but it should be close to what you need, if not exact.

Upvotes: 2

bitmask
bitmask

Reputation: 34628

If you want to get the individual lines into variables I'd suggest to wrap read into a while:

while read -r RESPONSE <&3; do
  echo "Response was $RESPONSE"
  ## e.g.:
  [ "$RESPONSE" = "OK" ] && break
done

However, if you want "everything" that is sent back to you to reside in $RESPONSE you could do it like this:

RESPONSE="$(cat <&3)"

Upvotes: 1

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798546

read takes the descriptor to read from as the argument following -u. See help read for details.

Upvotes: 1

Related Questions