Uncommon tools worth knowing
$ sponge
Soak up all of stdin, then write it to a file — solving the classic "read and write to the same file" problem.
In shell scripting, you've almost certainly hit this wall: you want to filter a file and write the result back to the same file. The naive approach silently destroys your data:
grep -v "debug" config.log > config.log
The shell truncates config.log before
grep reads it. Result: an empty file. The usual workaround
is a temporary file:
grep -v "debug" config.log > config.log.tmp && mv config.log.tmp config.log
It works, but it's verbose and error-prone (what if mv fails?).
sponge
sponge — part of the moreutils package by
Joey Hess — soaks up all of its standard input into memory,
then writes it to the specified file. This means the
entire pipeline reads from the original file before it gets overwritten.
grep -v "debug" config.log | sponge config.log
That's it. Clean, one-liner, no temp files.
grep -v "^#" /etc/hosts | sponge /etc/hosts
Remove all comments from /etc/hosts in place.
sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd
Rename root to toor and remove joey's entry, all in one pipeline.
sort logfile.txt | sponge logfile.txt
sort -u hosts.txt | sponge hosts.txt
sed 's/[[:space:]]*$//' source.c | sponge source.c
# Debian / Ubuntu / Linux Mint
sudo apt install moreutils
# Alpine
sudo apk add moreutils
# macOS (Homebrew)
brew install moreutils
# FreeBSD
pkg install moreutils
# NetBSD
pkgin install moreutils
sponge is conceptually simple: it reads all of stdin into a
memory buffer, then opens the target file for writing and flushes the
buffer. The key insight is the separation of read and write
phases — the file is never opened until all upstream pipeline
commands have finished reading.
Think of it like a kitchen sponge: it absorbs everything first, then you squeeze it out where you want.
moreutilsWhile you're at it, these siblings are worth knowing:
chronic — run a command silently unless it fails (chronic cp backup.tar /mnt/backup/)ts — timestamp every line of stdin (tail -f app.log | ts "%Y-%m-%d %H:%M")vidir — edit a directory listing in your editor, then apply changespee — tee stdin to multiple pipes (cat data.csv | pee grep "A" grep "B")mispipe — pipe two commands but return the exit status of the first onevipe — insert a text editor into a pipegit-annex and debhelper)