When automating shell scripts, a common pain point occurs when child processes get stuck waiting for standard input (stdin). This creates silent failures where:
- Scripts appear to hang without output
- No clear indication of why execution paused
- Automation pipelines break unpredictably
Consider this blocking script (demo.sh
):
#!/bin/bash
echo "About to read input..."
read -p "Enter value: "
echo "Continuing execution..."
When piped into another command (failifwaitingonstdin
in the original question), we need to detect the read
operation's blocking state.
Method 1: Process State Monitoring
Linux processes show specific states in /proc
:
grep -q "State:\tS (sleeping)" /proc/$PID/status &&
echo "Process blocked on I/O"
Method 2: Non-blocking I/O with Timeout
Using expect
with timeout:
#!/usr/bin/expect
set timeout 5
spawn ./demo.sh
expect {
"Enter value: " { exit 1 }
timeout { exit 0 }
}
Method 3: LD_PRELOAD Interception
For advanced users, intercept read()
calls:
// stdin_watchdog.c
#define _GNU_SOURCE
#include
#include
ssize_t (*original_read)(int, void*, size_t);
ssize_t read(int fd, void *buf, size_t count) {
if(fd == STDIN_FILENO) {
fprintf(stderr, "Blocking stdin read detected!\n");
_exit(EXIT_FAILURE);
}
return original_read(fd, buf, count);
}
Compile with: gcc -shared -fPIC -o stdin_watchdog.so stdin_watchdog.c -ldl
For production systems, combine multiple approaches:
#!/bin/bash
TIMEOUT=30
# Start process in background
./long_running_script.sh &
PID=$!
# Monitoring loop
while kill -0 $PID 2>/dev/null; do
if grep -q "State:\tS" /proc/$PID/status; then
if lsof -p $PID | grep -q "0u.*CHR.*1,3"; then
echo "STDIN BLOCK DETECTED" >&2
kill $PID
exit 1
fi
fi
sleep 1
TIMEOUT=$((TIMEOUT-1))
[ $TIMEOUT -le 0 ] && break
done
For common cases, consider:
yes | ./script.sh
- Feed continuous 'y' responses./script.sh < /dev/null
- Explicit empty inputstdbuf -i0 -o0 -e0 ./script.sh
- Disable I/O buffering
Essential Linux tools for analysis:
strace -e trace=read -p $PID # System call tracing
lsof -p $PID # Open file descriptors
gdb -p $PID # Live process inspection
When automating shell scripts, we often encounter situations where a process appears hung but is actually blocked waiting for stdin input. This creates a significant challenge in automation pipelines where we need to distinguish between genuine processing time and input waiting states.
In Unix-like systems, we can inspect process states through several methods:
# Check process state
ps -o state= -p [PID]
# Possible states:
# S - Sleeping (interruptible)
# D - Uninterruptible sleep
# R - Running
# T - Stopped
# Z - Zombie
Here are three effective approaches to detect stdin blocking:
Method 1: Using lsof
#!/bin/bash
./demo &
PID=$!
sleep 0.1 # Allow process to start
if lsof -p $PID | grep -q '0u.*CHR'; then
echo "Process is waiting on stdin" >&2
kill $PID
exit 1
fi
Method 2: Expect-based Solution
#!/usr/bin/expect -f
spawn ./demo
expect {
timeout { exit 1 }
eof { exit 0 }
}
Method 3: Non-blocking Pipe
#!/bin/bash
mkfifo mypipe
(./demo < mypipe &)
PID=$!
# Try writing to the pipe with timeout
if ! echo "" > mypipe & sleep 0.1; then
kill $PID
rm mypipe
exit 1
fi
For more sophisticated scenarios, consider these approaches:
Using strace:
strace -e trace=read -p [PID] 2>&1 | grep '^read(0,'
PTY-based Solution:
#!/bin/bash
socat EXEC:./demo,pty,ctty,setsid STDIO
When implementing these solutions in production:
- Use minimal sleep intervals (0.1s is usually sufficient)
- Clean up any temporary files or pipes
- Handle multiple concurrent processes carefully
- Consider using process groups for clean termination
Here's how to integrate this check into a Jenkins pipeline:
pipeline {
agent any
stages {
stage('Run Test') {
steps {
script {
def status = sh(
script: 'timeout 5s ./check_stdin.sh || true',
returnStatus: true
)
if (status == 124) {
error("Process hung waiting for input")
}
}
}
}
}
}