Using File Descriptors In Bash For Background Monitoring
Suppose you want to write a script that interfaces with a serial port and allows you to run commands remotely on the device connected to the serial port.
One approach (incorrect) that you could take is to do it like this:
#!/bin/bash
# Ensure proper usage
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <serial_port> <command>"
echo "Example: $0 /dev/ttyUSB0 'wifi status'"
exit 1
fi
# Arguments
SERIAL_PORT="$1"
shift;
COMMAND="$@"
# Serial port configuration
BAUD_RATE=115200 # Modify based on your device's baud rate
TIMEOUT=2 # Seconds to wait for response
# Ensure the serial port exists
if [ ! -e "$SERIAL_PORT" ]; then
echo "Error: Serial port $SERIAL_PORT not found!"
exit 1
fi
# Open the serial port with proper settings
stty -F "$SERIAL_PORT" $BAUD_RATE cs8 -cstopb -parenb -ixon -ixoff
# Function to send a command and read response
send_command() {
local port="$1"
local cmd="$2"
# Send the command followed by newline
echo "$cmd" > "$port"
# Read the response from the serial port
while IFS= read -r -t $TIMEOUT line < "$port"; do
echo "$line"
# Exit the loop if we encounter the device prompt
[[ "$line" == "uart:*" ]] && break
done
}
# Send the command and read the response
send_command "$SERIAL_PORT" "$COMMAND"
We should now be able to execute a remote command and print its output and then exit the script like this:
$ ./serial.sh /dev/ttyUSB0 'wifi status'
This script is designed to stop and exit when it encounters Zephyr RTOS shell prompt. You may need to modify it yourself if your device responds differently.
However you will notice that the first lines of the output are missing. This is because the script is reading the output from the serial port too late.
What we want to do instead is to somehow open the serial port for both reading and writing first, then write the command and then read the response.
This is where file descriptors can help us. We modify our script as follows:
- # Send the command followed by newline
- echo "$cmd" > "$port"
+ # Open file descriptor 3 for reading and writing
+ exec 3<> "$port"
+ # Write the command to the file descriptor
+ echo "$cmd" >&3
# Read the response from the serial port
- while IFS= read -r -t $TIMEOUT line < "$port"; do
+ while IFS= read -r -t $TIMEOUT line <&3; do
echo "$line"
What we do is open the file and assign it a file descriptor number 3 (since 0 to 2 are stdin, stdout, and stderr respectively). We then write the command to the file descriptor and then read the response from the same file descriptor.
Notice the use of exec
when opening the file descriptor. Normally exec is a
command that would replace the current shell process with another one, but when
we use it with file descriptors, it opens the descriptor in the current shell
process so that we can then access it in the rest of our script.
Now our script will not be skipping any lines. We would get both the echo of the command itself and the response.