The standard Python readline extension statically linked against the GNU readline library.
—
Comprehensive history management with programmatic control over command history storage, retrieval, and persistence. The gnureadline history system maintains compatibility with GNU Readline's indexing conventions.
Basic operations for adding, retrieving, and manipulating history entries in memory.
def add_history(line: str):
"""
Add a line to the history buffer.
Parameters:
- line: Command line to add to history
"""
def clear_history():
"""Clear the entire history list."""
def get_history_item(index: int) -> str:
"""
Get history item by index (1-based).
Parameters:
- index: History item index (1-based, 0 returns None)
Returns:
str or None: History item or None if index is 0 or out of range
"""
def get_current_history_length() -> int:
"""
Get the current number of history entries.
Returns:
int: Number of history entries
"""import gnureadline
# Build a command history
commands = ["ls -la", "cd /home", "python script.py", "git status"]
for cmd in commands:
gnureadline.add_history(cmd)
print(f"History length: {gnureadline.get_current_history_length()}") # 4
# Retrieve history items (1-based indexing)
first_cmd = gnureadline.get_history_item(1) # "ls -la"
last_cmd = gnureadline.get_history_item(4) # "git status"
invalid = gnureadline.get_history_item(0) # None
# Clear all history
gnureadline.clear_history()
print(f"After clear: {gnureadline.get_current_history_length()}") # 0Advanced operations for modifying existing history entries using 0-based indexing.
def remove_history_item(index: int) -> str:
"""
Remove history entry by index (0-based).
Parameters:
- index: History item index (0-based)
Returns:
str: Removed history item
"""
def replace_history_item(index: int, line: str) -> str:
"""
Replace history entry at index (0-based).
Parameters:
- index: History item index (0-based)
- line: New history line
Returns:
str: Previous history item at that index
"""import gnureadline
# Set up some history
gnureadline.add_history("command1")
gnureadline.add_history("command2")
gnureadline.add_history("command3")
# Replace second command (0-based index 1)
old_cmd = gnureadline.replace_history_item(1, "modified_command2")
print(f"Replaced '{old_cmd}' with 'modified_command2'")
# Remove first command (0-based index 0)
removed = gnureadline.remove_history_item(0)
print(f"Removed: '{removed}'")
# Check final state
for i in range(gnureadline.get_current_history_length()):
print(f"History[{i}]: {gnureadline.get_history_item(i+1)}") # +1 for 1-based getControl automatic history behavior and size limits.
def set_history_length(length: int):
"""
Set maximum history length.
Parameters:
- length: Maximum number of history entries (-1 for unlimited)
"""
def get_history_length() -> int:
"""
Get maximum history length setting.
Returns:
int: Maximum history length (-1 if unlimited)
"""
def set_auto_history(enabled: bool):
"""
Enable or disable automatic history addition.
Parameters:
- enabled: Whether to automatically add lines to history
"""import gnureadline
# Set history limit
gnureadline.set_history_length(100)
print(f"History limit: {gnureadline.get_history_length()}") # 100
# Disable automatic history (manual control)
gnureadline.set_auto_history(False)
# Unlimited history
gnureadline.set_history_length(-1)
print(f"Unlimited history: {gnureadline.get_history_length()}") # -1Save and load history to/from files for persistence across sessions.
def read_history_file(filename: str = None):
"""
Load a readline history file.
Parameters:
- filename: History file path (default: ~/.history)
"""
def write_history_file(filename: str = None):
"""
Save the history to a file.
Parameters:
- filename: History file path (default: ~/.history)
"""
def append_history_file(nelements: int, filename: str = None):
"""
Append recent history entries to a file.
Parameters:
- nelements: Number of recent entries to append
- filename: History file path (default: ~/.history)
Note: Only available if HAVE_RL_APPEND_HISTORY is defined
"""import gnureadline
import os
# Set up custom history file location
history_file = os.path.expanduser("~/.my_app_history")
# Load existing history on startup
try:
gnureadline.read_history_file(history_file)
print(f"Loaded {gnureadline.get_current_history_length()} history entries")
except OSError as e:
print(f"No existing history file: {e}")
# Add some commands during session
gnureadline.add_history("command 1")
gnureadline.add_history("command 2")
# Save complete history on exit
try:
gnureadline.write_history_file(history_file)
print("History saved successfully")
except OSError as e:
print(f"Failed to save history: {e}")
# Alternatively, append only recent entries
try:
gnureadline.append_history_file(10, history_file) # Last 10 entries
print("Recent history appended")
except OSError as e:
print(f"Failed to append history: {e}")import gnureadline
import re
class HistoryManager:
def __init__(self, max_length=1000, filter_duplicates=True):
self.max_length = max_length
self.filter_duplicates = filter_duplicates
gnureadline.set_history_length(max_length)
gnureadline.set_auto_history(False) # Manual control
def add_filtered_history(self, line: str):
"""Add history with filtering logic."""
# Skip empty lines and whitespace-only lines
if not line.strip():
return
# Skip commands starting with space (privacy feature)
if line.startswith(' '):
return
# Filter out sensitive commands
sensitive_patterns = [r'password', r'secret', r'key\s*=']
if any(re.search(pattern, line, re.I) for pattern in sensitive_patterns):
return
# Remove duplicates if enabled
if self.filter_duplicates:
current_length = gnureadline.get_current_history_length()
if current_length > 0:
last_item = gnureadline.get_history_item(current_length)
if last_item == line:
return
gnureadline.add_history(line)
def search_history(self, pattern: str) -> list:
"""Search history for matching entries."""
matches = []
length = gnureadline.get_current_history_length()
for i in range(1, length + 1):
item = gnureadline.get_history_item(i)
if item and re.search(pattern, item, re.I):
matches.append((i, item))
return matches
def cleanup_history(self):
"""Remove old/redundant entries to keep history manageable."""
length = gnureadline.get_current_history_length()
if length <= self.max_length:
return
# Keep only the most recent entries
keep_entries = []
start_index = length - self.max_length + 1
for i in range(start_index, length + 1):
item = gnureadline.get_history_item(i)
if item:
keep_entries.append(item)
# Clear and rebuild history
gnureadline.clear_history()
for entry in keep_entries:
gnureadline.add_history(entry)
# Usage example
history_mgr = HistoryManager(max_length=500, filter_duplicates=True)
# Add commands with filtering
commands = [
"ls -la",
"cd /home",
"ls -la", # Duplicate - will be filtered
" secret_command", # Starts with space - will be filtered
"export PASSWORD=secret", # Contains sensitive data - will be filtered
"python script.py"
]
for cmd in commands:
history_mgr.add_filtered_history(cmd)
# Search history
matches = history_mgr.search_history("python")
print(f"Found {len(matches)} matches for 'python'")
# Periodic cleanup
history_mgr.cleanup_history()Important: GNU Readline uses different indexing conventions for different operations:
get_history_item(): 1-based indexing (index 0 returns None)remove_history_item() and replace_history_item(): 0-based indexingThis matches the GNU Readline C library behavior for compatibility with existing applications.
import gnureadline
# Add some test data
gnureadline.add_history("first") # Will be at index 0 for remove/replace
gnureadline.add_history("second") # Will be at index 1 for remove/replace
# Getting items (1-based)
first = gnureadline.get_history_item(1) # "first"
second = gnureadline.get_history_item(2) # "second"
# Modifying items (0-based)
gnureadline.replace_history_item(0, "FIRST") # Replace "first" with "FIRST"
gnureadline.remove_history_item(1) # Remove "second"
# Verify the change
print(gnureadline.get_history_item(1)) # "FIRST"
print(gnureadline.get_history_item(2)) # None (removed)Install with Tessl CLI
npx tessl i tessl/pypi-gnureadline