Skip to content

Instantly share code, notes, and snippets.

@tos-kamiya
Created April 24, 2025 18:25
Show Gist options
  • Save tos-kamiya/7abac530a6a1e46a82d88368db2e13d4 to your computer and use it in GitHub Desktop.
Save tos-kamiya/7abac530a6a1e46a82d88368db2e13d4 to your computer and use it in GitHub Desktop.
Select files by modification time with slicing semantics.
#!/usr/bin/env python3
"""Select files by modification time with slicing semantics.
Usage:
latest.py [-n SLICE] [-q] <file>...
Examples:
# The single most recent file
latest.py ~/Pictures/*
# The 4 most recent files
latest.py -n 4 ~/Pictures/*
# Skip the most recent, then take files 1–3
latest.py -n 1,4 ~/Pictures/*
# The single oldest file
latest.py -n -1 ~/Pictures/*
Options:
-q, --quiet Suppress all log output to stderr
"""
import sys
import os
import argparse
from typing import Union, List
def parse_slice(arg: str) -> Union[slice, int]:
"""
Parse a slice specification string into a slice or index.
- "4" -> slice(0, 4) (newest 4 files)
- "-1" -> -1 (oldest file)
- "1,4" -> slice(1, 4) (skip newest, take files 1–3)
"""
parts = arg.split(',')
if len(parts) == 1:
n = int(parts[0])
if n >= 0:
return slice(0, n)
return n
if len(parts) == 2:
start, stop = map(int, parts)
return slice(start, stop)
raise argparse.ArgumentTypeError(f"Invalid slice specification: {arg}")
def main() -> None:
script_name = os.path.basename(sys.argv[0])
parser = argparse.ArgumentParser(
description="Select files by modification time with slicing semantics.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
'-n', '--slice',
dest='slice_spec',
type=parse_slice,
default=slice(0, 1),
help=(
"Which files to select (Python slice semantics):\n"
" 4 -> newest 4 files\n"
" 1,4 -> skip newest, take files 1–3\n"
" -1 -> the oldest file"
)
)
parser.add_argument(
'-q', '--quiet',
action='store_true',
help='Suppress all log output to stderr'
)
parser.add_argument(
'files',
nargs='+',
help='Files or glob patterns to consider'
)
args = parser.parse_args()
# Determine whether to emit logs:
# only if stdout is not a tty and not in quiet mode
log_enabled = (not sys.stdout.isatty()) and (not args.quiet)
try:
# Sort by modification time descending (newest first)
all_files: List[str] = sorted(
args.files,
key=lambda f: os.stat(f).st_mtime,
reverse=True
)
except OSError as e:
if log_enabled:
sys.stderr.write(f"{script_name}: Error: {e}\n")
sys.exit(1)
sel = args.slice_spec
if isinstance(sel, slice):
selected: List[str] = all_files[sel]
else:
selected = [all_files[sel]]
if not selected:
if log_enabled:
sys.stderr.write(f"{script_name}: No files selected.\n")
sys.exit(1)
if log_enabled:
# Write confirmation list to stderr with script name prefix
for path in selected:
sys.stderr.write(f"{script_name}: {path}\n")
# Output one file per line to stdout
for path in selected:
print(path)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment