r/bash • u/Awkward_Process_9627 • 7d ago
fm: a single-file bash file manager — fzf picker, gallery grid, trash undo
I wrote a ~1500-line bash file manager. fzf for the picker, eza for
listing, Kitty's graphics protocol for inline image/video/PDF previews.
Everything else is stock coreutils.

Repo: https://github.com/eHab-codeX/fm
Why post it here
r/commandline got the "here's a tool" version last week. The bash bits are probably more interesting to this crowd, so here they are.
The bash-specific parts
Double-click detection without EPOCHREALTIME getting in the way.
EPOCHREALTIME returns a locale-formatted decimal — . in en_US,
, in de_DE. Strip the separator before comparing:
now=${EPOCHREALTIME/[.,]/}
(( idx == last_idx && now - last_t < 400000 )) # 400ms
A hand-rolled CSI/SS3 parser for the gallery grid. The full-screen grid needs arrow keys, PgUp/PgDn, Home/End, and SGR mouse events. Rather than depend on a helper, it reads bytes into a buffer and dispatches on the final byte:
case "$final" in
A) KEY=up ;; B) KEY=down ;;
C) KEY=right ;; D) KEY=left ;;
'~') case "${seq%%;*}" in
5) KEY=pgup ;; 6) KEY=pgdn ;;
esac ;;
esac
The tricky part is not swallowing stray bytes: an unknown CSI sequence
(\e[1;5H for Ctrl+Home, for example) has to be fully consumed or it
leaks into the next read as literal characters.
**stty -g around the alternate screen.** The grid switches to cbreak
mode (stty -icanon -echo min 1 time 0). Saving the previous state and
restoring it in the EXIT trap is what keeps Ctrl-C from leaving the
terminal in a broken state:
STTY_SAVED=$(stty -g </dev/tty) || STTY_SAVED=""
trap '[[ -n "$STTY_SAVED" ]] && stty "$STTY_SAVED" </dev/tty' EXIT
Two-phase rename so swaps work. A naive mv old new loop breaks
a→b, b→a. First move every source to a temp name, then move temps to
final names:
for k in "${!ro[@]}"; do
mv -T -- "${ro[k]}" "${ro[k]}.fm-tmp.$$"
done
for k in "${!ro[@]}"; do
mv -T -- "${ro[k]}.fm-tmp.$$" "${rn[k]}"
done
Parallel thumbnails by self-invocation. The thumbnail builder is
the same script with --thumb-any:
printf '%s\0' "${slice[@]}" | \
xargs -0 -r -n1 -P"$(nproc)" bash "$SELF" --thumb-any
Bash dispatches; ImageMagick/ffmpeg/pdftoppm do the work.
GNU vs BSD head. head -z is GNU-only. A contributor on macOS
sent a PR that falls back to ghead, then to a pure-bash reader:
while ((count < $1)) && IFS= read -r -d '' record; do
printf '%s\0' "$record"
((++count))
done
What I'd want reviewed
- The escape parser in
_g_readkey— there are terminals that emit sequences it might mis-handle (Konsole? older tmux?). - The
_fm_listsort pipeline — it does one batchedstatand oneawkpass, thensort -V. I'm not 100% sure the tie-breaking behavior for identical timestamps is stable. - Anything in
_trash_and_record— I'm handling the Freedesktop.trashinfoformat'sPath=percent-encoding with a bash-only encoder, which is probably slower than it should be.
Requirements
Bash 5+ (uses EPOCHREALTIME and ${var,,}). Linux is the primary
target; macOS works with GNU coreutils installed (the _head0 helper
falls back to bash-only if ghead isn't present).
git clone https://github.com/eHab-codeX/fm.git ~/.local/share/fm
ln -sf ~/.local/share/fm/fm ~/.local/bin/fm
MIT.
2
3
u/anto77_butt_kinkier manpage, my beloved 7d ago
Hey so I hate to do this to you, but it looks like you tried to use markdown format in the rich text editor, which doesn't impliment the formatting.
You need to edit the post, switch to markdown mode, and remove the escape characters from in front of the markdown formatting characters.
1
u/Awkward_Process_9627 6d ago
Fixed — thanks for pointing it out. The rich text editor ate the
markdown and escaped all the formatting characters. Switched to
Markdown mode and re-pasted. Should render properly now.
2
u/anto77_butt_kinkier manpage, my beloved 6d ago
If you re-pasted, you may need to make sure there are no escape characters. For me it's still not showing as formatted, although idk if that's because I'm on mobile on really shitty wifi, and it just hasn't updated for me yet
Edit: nevermind, I refreshed the page and it now renders correctly.
1
u/Ok-Sample-8982 4d ago
Good project. But why you are not using “.” source?
Break that huge file into small chunks then source them where u need them in the main.
Ive written many tools on bash and my whole backend is running on pure bash on openwrt router. If i didnt use source it would have been a nightmare to maintain.
4
u/eifelcode 7d ago
That’s a cool project and shows again how powerful bash is. I’ve taken a look into your source and well…. It looks a bit unstructured because everything is in a single file. I had the same problem in my project that I want a single file application written in bash but code structured in several files. I succeeded this by using a Make file. Take a look at pdfmt maybe you find some useful stuff to adapt to your project. Also tests and a CI pipeline would be cool. ☺️