Last modified April 11, 2025
This comprehensive guide explores Python's os.read function,
which performs low-level file reading using file descriptors. We'll cover
file descriptors, buffer sizes, error handling, and practical examples.
The os.read function reads up to n bytes from a file descriptor.
It's a low-level I/O operation that works directly with file descriptors.
Key parameters: fd (file descriptor), n (maximum bytes to read). Returns bytes read as bytes object. May raise OSError for invalid descriptors.
This basic example demonstrates how to open a file and read its contents
using os.read. We first get a file descriptor with os.open.
import os
# Open file and get file descriptor
fd = os.open("example.txt", os.O_RDONLY)
# Read up to 1024 bytes
data = os.read(fd, 1024)
print(f"Read {len(data)} bytes:")
print(data.decode('utf-8'))
# Close file descriptor
os.close(fd)
This example opens a file in read-only mode, reads up to 1024 bytes, then closes the descriptor. The data is returned as bytes and needs decoding.
Note that os.read may return fewer bytes than requested, even if more are available. This is normal behavior for low-level I/O operations.
For large files, reading in chunks is more efficient. This example shows how to read a file piece by piece until EOF is reached.
import os
CHUNK_SIZE = 4096 # 4KB chunks
fd = os.open("large_file.bin", os.O_RDONLY)
total_bytes = 0
while True:
chunk = os.read(fd, CHUNK_SIZE)
if not chunk: # EOF reached
break
total_bytes += len(chunk)
# Process chunk here
print(f"Read chunk of {len(chunk)} bytes")
os.close(fd)
print(f"Total bytes read: {total_bytes}")
The loop continues reading until os.read returns an empty bytes object, indicating EOF. Each chunk is processed as it's read.
This approach is memory-efficient as it doesn't load the entire file at once. The chunk size can be adjusted based on performance needs.
os.read can read from standard input (file descriptor 0).
This example demonstrates reading user input directly from stdin.
import os
import sys
print("Type something and press Enter (Ctrl+D to end):")
# Read from stdin (fd 0)
try:
while True:
data = os.read(0, 1024) # STDIN_FILENO is 0
if not data:
break
print(f"You entered: {data.decode('utf-8').strip()}")
except KeyboardInterrupt:
print("\nInterrupted by user")
This reads directly from standard input until EOF (Ctrl+D) or interrupt. The data is decoded from bytes to a string for display.
Note this is lower-level than input() and requires manual handling of encoding and newlines. It's useful for binary stdin reading.
With non-blocking file descriptors, os.read can be used for
asynchronous I/O. This example shows non-blocking read behavior.
import os
import errno
# Open file in non-blocking mode
fd = os.open("fifo_pipe", os.O_RDONLY | os.O_NONBLOCK)
try:
while True:
try:
data = os.read(fd, 1024)
if data:
print(f"Data received: {data.decode()}")
else:
print("No data available")
break
except BlockingIOError:
print("No data ready - would block")
# In real code, you might wait here
break
finally:
os.close(fd)
When no data is available, non-blocking mode raises BlockingIOError instead of waiting. This is useful for event loops and async I/O.
The example assumes a named pipe (fifo) exists. Real code would need proper error handling and likely an event loop.
os.read is ideal for reading binary files since it returns
raw bytes. This example reads a binary file and processes its contents.
import os
def read_binary_file(filename):
fd = os.open(filename, os.O_RDONLY)
try:
# Read first 4 bytes (could be a magic number)
header = os.read(fd, 4)
print(f"File header: {header.hex()}")
# Read rest of file
remaining = b''
while True:
chunk = os.read(fd, 4096)
if not chunk:
break
remaining += chunk
return header + remaining
finally:
os.close(fd)
data = read_binary_file("image.png")
print(f"Total bytes read: {len(data)}")
print(f"First 16 bytes: {data[:16].hex()}")
This reads a binary file (PNG image), first extracting the header then the remaining contents. The hex representation shows binary data clearly.
Binary mode preserves all bytes exactly as they appear in the file, unlike text mode which may perform newline conversions.
Proper error handling is crucial when using low-level I/O. This example
demonstrates comprehensive error handling for os.read.
import os
import errno
def safe_read(fd, n):
try:
data = os.read(fd, n)
if not data:
print("Reached end of file")
return None
return data
except OSError as e:
if e.errno == errno.EBADF:
print("Error: Bad file descriptor")
elif e.errno == errno.EINTR:
print("Error: Interrupted system call")
elif e.errno == errno.EIO:
print("Error: I/O error occurred")
else:
print(f"Unexpected error: {e}")
return None
# Example usage
try:
fd = os.open("example.txt", os.O_RDONLY)
data = safe_read(fd, 1024)
if data:
print(data.decode('utf-8'))
finally:
if 'fd' in locals():
os.close(fd)
The safe_read function handles various error conditions that os.read might encounter. Each error type gets specific handling.
The finally block ensures the file descriptor is closed even if errors occur. This prevents resource leaks in error scenarios.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.