Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Get all the information you could ever want about stdin, stdout, and stderr file descriptors in Python (e.g. is it a TTY, terminal, pipe, redirection, etc.)
#!/usr/bin/env python3
# Get all the information you could ever want about the STDIN, STDOUT, STDERR file descriptors (e.g. is it a TTY, terminal, pipe, redirection, etc.)
# Works cross-platform on Windows, macOS, Linux, in Docker, and in Docker-Compose by using stat library
#
# Useful for detecting and handling different stdin/stdout redirect scenarios in CLI scripts,
# e.g. is the user piping a file in or are they interactively typing things in?
# is the process output being saved to a file or being printed to a terminal?
# can we ask the user for input or is it a non-interactive masquerading as a TTY?
#
# Further reading:
# - https://stackoverflow.com/questions/13442574/how-do-i-determine-if-sys-stdin-is-redirected-from-a-file-vs-piped-from-another
# - https://stackoverflow.com/questions/33871836/find-out-if-there-is-input-from-a-pipe-or-not-in-python
# - https://stackoverflow.com/questions/30137135/confused-about-docker-t-option-to-allocate-a-pseudo-tty
import os
import sys
import stat
def log_dict_summary(obj: dict):
sys.stderr.write(' '.join(f'{key}={str(val).ljust(6)}' for key, val in obj.items()) + '\n')
def get_fd_info(fd):
FILENO = fd.fileno()
MODE = os.fstat(FILENO).st_mode
IS_PIPE, IS_TERMINAL = stat.S_ISFIFO(MODE), stat.S_ISREG(MODE)
return {
'NAME': fd.name[1:-1],
'FILENO': FILENO,
'MODE': MODE,
'IS_PIPE': IS_PIPE,
'IS_FILE': IS_TERMINAL,
'IS_TERMINAL': not (IS_PIPE or IS_FILE),
'IS_TTY': hasattr(fd, 'isatty') and fd.isatty(),
'IS_LINE_BUFFERED': fd.line_buffering,
'IS_READABLE': fd.readable(),
}
sys.stdout.write('[>&1] this is python stdout\n')
sys.stderr.write('[>&2] this is python stderr\n')
log_dict_summary(get_fd_info(sys.stdin))
log_dict_summary(get_fd_info(sys.stdout))
log_dict_summary(get_fd_info(sys.stderr))
### BASH STDIN/STDOUT/STDERR BEHAVIOR (as tested on macOS in bash)
### BASH w/ stdin
# python3 test_stdin_stdout_stderr.py
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# echo test | python3 test_stdin_stdout_stderr.py
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# python3 test_stdin_stdout_stderr.py < testin
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=False IS_FILE=True IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
### BASH w/ stdout
# python3 test_stdin_stdout_stderr.py > testout
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=False IS_FILE=True IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
# python3 test_stdin_stdout_stderr.py | cat
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=True IS_READABLE=False
#### DOCKER-COMPOSE STDIN/STDOUT/STDERR BEHAVIOR (as tested on macOS in bash)
### TTY w/ stdin
# docker-compose run test (X docker sends stderr to terminal stdout)
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# docker-compose run test < testin (√ docker sends stderr to terminal stderr!)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# echo test | docker-compose run test (√ docker sends stderr to terminal stderr!)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
### TTY w/ stdout
# docker-compose run test > testout (X docker sends stderr to file as well)
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# docker-compose run test | cat (X docker sends stderr to pipe as well)
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
### NON-TTY w/ stdin
# docker-compose run -T test (√ docker sends stderr to terminal stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# docker-compose run -T test < testin (√ docker sends stderr to terminal stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# echo test | docker-compose run -T test (√ docker sends stderr to terminal stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
### NON-TTY w/ stdout
# docker-compose run -T test > testout (√ docker sends stderr to terminal stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
#### DOCKER STDIN/STDOUT/STDERR BEHAVIOR (tested on macOS in bash)
### TTY w/ stdin
# docker run -it test (X docker sends stderr to terminal stdout)
# NAME=stdin FILENO=0 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=True IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=False
# docker run -it test < testin (X stdin with TTY not allowed)
# echo test | docker run -it test (X stdin with TTY not allowed)
### TTY w/ stdout
# docker run -it test > testout (X docker sends stderr to file as well)
#
# (same behavior as docker-compose...)
#
# docker run -it test | cat (X docker sends stderr to pipe as well)
#
# (same behavior as docker-compose...)
#
### NON-TTY w/ no stdin
# docker run test (X stdin is ignored, stderr goes to stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# docker run test < testin (X stdin is ignored)
#
#
#
# echo test | docker run test (X stdin is ignored)
#
#
#
### NON-TTY w/ stdin
# docker run test (√ stdin->stdin, stdout->stdout, stderr->stderr)
# NAME=stdin FILENO=0 IS_TTY=False IS_PIPE=False IS_FILE=False IS_TERMINAL=True IS_LINE_BUFFERED=False IS_READABLE=True
# NAME=stdout FILENO=1 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# NAME=stderr FILENO=2 IS_TTY=False IS_PIPE=True IS_FILE=False IS_TERMINAL=False IS_LINE_BUFFERED=False IS_READABLE=False
# docker run -i test < testin (√ file->stdin, stdout->stdout, stderr->stderr)
#
# (same behavior as docker-compose...)
#
# echo test | docker run -i test (√ pipe->stdin, stdout->stdout, stderr->stderr)
#
# (same behavior as docker-compose...)
#
### NON-TTY w/ stdout
# docker run test > testout (√ null->stdin, stdout->file, stderr->stderr)
#
# (same behavior as docker-compose...)
#
# TODO: test behavior over SSH with -T and without
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment