Summary
Detecting whether a script or function is receiving data from a pipe requires more than testing if stdin is a terminal. The reliable pattern is to inspect the file descriptor 0 with stat/test -p or [[ -p /dev/stdin ]]. This works for both standalone scripts and functions sourced into another shell.
Root Cause
[[ -t 0 ]]only tells you “is stdin a tty?” – it does not differentiate between:- a pipe,
- a redirected file,
- a here‑document, or
- a terminal.
- When a script is sourced, the calling shell’s stdin state is inherited, so the test reflects the caller’s context, not the function’s own usage.
Why This Happens in Real Systems
- Production pipelines often chain many utilities (
cmd1 | cmd2 | cmd3). - Individual components are sometimes reused as both standalone commands and library functions.
- Developers rely on
[[ -t 0 ]]because it’s short, but in a complex orchestration it yields false positives and masks bugs (e.g., a function assumes interactive mode when it is actually receiving streamed data).
Real-World Impact
- Incorrect mode selection – a script may skip essential parsing logic, leading to malformed output.
- Data loss – scripts that default to interactive prompts when fed a pipe can deadlock.
- Hard‑to‑reproduce bugs in CI/CD pipelines where the same code runs both interactively (local dev) and non‑interactively (automation).
Example or Code (if necessary and relevant)
# Detect pipe or redirection in a portable way
if [ -p /dev/stdin ] || [ -f /dev/stdin ] && [ "$(stat -c %F /dev/stdin)" = "fifo" ]; then
echo "Piped input detected"
else
echo "No pipe (interactive or no stdin)"
fi
# Function version (works when sourced)
is_piped() {
[ -p /dev/stdin ] && return 0
return 1
}
# Usage
if is_piped; then
echo "Function received pipe"
else
echo "Function not piped"
fi
How Senior Engineers Fix It
- Prefer
/dev/stdinchecks (-p,-S) over-t. - Wrap the logic in a reusable function/library to avoid duplication.
- When a script must support both interactive and non‑interactive modes, explicitly test for a pipe and for a regular file redirect:
case "$(stat -c %F /dev/stdin)" in FIFO|character special) pipe=true ;; regular) pipe=true ;; # from a file redirect *) pipe=false ;; esac - Document the expected behaviour in the script’s help output, so callers know when interactive prompts will be suppressed.
- Add unit tests that invoke the script with:
- no stdin,
- a pipe (
echo data | script), - a file redirect (
script < file).
Why Juniors Miss It
- They equate “not a terminal” with “being piped”, overlooking other stdin sources.
- They seldom test scripts in non‑interactive CI environments, so the bug stays hidden.
- Lack of familiarity with
/dev/stdinandstatutilities leads them to rely on the shorter but inaccurate[[ -t 0 ]].