Smol Finger Client
a little plan / project browser with find and fzf
2024-09-01
The finger
command was allegedly written in 1971 by some
guy named Les. Its real world analogy is pointing, the act of running
your finger down a directory listing or phone book, looking for a
certain person or contact. It is one of, if not the very first, presence
protocols for remote users. (The same way Slack,
AIM, and
xmpp
report presence or status: available, away, etc.) You can run the finger
command on a host running the finger daemon and get information over the
finger protocol. (That’s a lot of finger!) In response, you get users’
time logged in, time spent idle, .plan and .project file contents, and
sometimes phone number and office location.
You get somebody’s finger info remotely by running the
finger
command obviously.
e.g. finger dozens@tilde.town
. Or in lynx,
lynx finger://dozens@tilde.town
. Or by using a smolnet
browser like Kristall or Lagrange. Or with necat: `echo “dozens” | nc
tilde.town 79”
Anyway, I’m not interested in all of that. I just want to browse the .plan and .project files of some of my friends because we’ve organized a kind of accountability jam called #tilde30. Jam rules require one to update one’s .plan with some kind of goal that will be worked toward over 30 days, and also a couple of milestones toward reaching that goal. Weekly check-ins and updates go in .project.
So I just wanna see those .plans and .projects!
There are 58 users who have updated their .plan file in the last 30 days:
2>/dev/null find /home -maxdepth 2 -type f -mtime -30 -name .plan | wc -l
Explanation
2>/dev/null
: This is a stream redirection.2
is the stream that errors are printed to. So here I’m redirecting all errors to /dev/null to swallow them up. Otherwise I get a ton of “Permission denied” errors when trying to access certain files.find /home -maxdepth 2
: It took a really long time to get confident withfind
. The options and flags just never felt intuitive to me, and I had a hard time committing them to memory. But now that I know it, I’d say learning it is very worthwhile! Anyway, this part says to search/home
directory, at a maximum depth of 2, going no further into anybody’s home directory. Because .plan and .project are always in the root of a home directory. e.g./home/howdy/.plan
-type f -mtime -30
: … find “file” types only (not directories), with a modified time of less than 30 days.-name .plan
: … named “.plan”| wc -l
… and count how many lines there are.
The contents of the default .plan
file is “This is my
.plan file. There are many like it, but this one is mine.” Let’s be sure
to filter those out.
2>/dev/null find /home -maxdepth 2 -type f \( -name .plan -o -name .project \) \
-exec grep -L "This is my .plan file. There are many like it, but this one is mine." {} \+
Explanation
Let’s start by tweaking the first line: drop the -mtime
flag to return all the files instead of just fresh ones. While we’re at
it, include .project
files in addition to
.plan
files: -o
is the “or” flag, and we group
the two conditionals with escaped round brackets:
\( -name .plan -o -name .project \)
.
-exec
executes a command on the results of the found
files. The {}
is a stand in for the filename. The
+
apppends all the found file names to the command. It must
be escaped with a backslash.
grep
searches all the files for the supplied string.
(The default plan contents.) The -L
inverts the default
behavior: it returns results that DON’T match the string.
The next part is easy. We’ll sort the list by most recently updated
by piping the files to ls -t
via xargs
.
2>/dev/null find /home -maxdepth 2 -type f \( -name .plan -o -name .project \) \
-exec grep -L "This is my .plan file. There are many like it, but this one is mine." {} \+ \
| xargs ls -t
Now we’ll clean up the file paths a little bit for fzf:
2>/dev/null find /home -maxdepth 2 -type f \( -name .plan -o -name .project \) \
-exec grep -L "This is my .plan file. There are many like it, but this one is mine." {} \+ \
| xargs ls -t \
| sed 's#/home/##' \
| fzf --preview='cat /home/{}'
sed
just strips the /home/
off the filepath
so that it looks nice. And then pipes the list of filenames into
fzf
.
In the preview pane, we cat the contents of the file. And that’s it! That’s the whole UI!
Everything else is just fzf keystrokes: ctrl-n and ctrl-p to scroll list items. shift-up and shift-down to scroll the preview. Start typing to fuzzy search for a specific file.
There are a couple extra things we can do to spice up the preview:
#!/usr/bin/env sh
2>/dev/null find /home -maxdepth 2 -type f \( -name .plan -o -name .project \) \
-exec grep -L "This is my .plan file. There are many like it, but this one is mine." {} \+ \
| xargs ls -t \
| sed 's#/home/##' \
| fzf \
--preview='printf "%s: %s\n" $(date -d"$(stat /home/{} -c %y)" -I) {}; fold -sw 80 /home/{}' \
--cycle --reverse --preview-window=75%
Explanation
Adding the ‘shebang’ #!/usr/bin/env sh
on line 1 makes
it possible to execute this as a script.
The preview consists of two expressions: a printf and a fold.
printf "%s: %s\n" $(date -d"$(stat /home/{} -c %y)" -I) {}
: This prints the date and the filename.stat /home/{} -c $y
prints the timestamp the file was last modified.date -d"..." -I
formats the date. Inprintf "%s: %s\n" <date> <fileame>
, “%s: %s” is the template string.date
andfilename
are passed as parameters to the template.$(...)
is how you nest commands inside other commands.fold
wraps the contents of/home/{}
at width (-w
) 80, and softwraps (-s
) it, which means it only breaks on whitespace. Not in the middle of words.--cycle
allows fzf to loop around to the beginning or end of the list.--reverse
makes it so that the prompt is at the top of the screen instead of the bottom, which is something I like. And--preview-window
sets the size of the preview window.
Save this to ~/bin/fing
and
chmod +x ~/bin/fing
. And now you got a sort of finger
browser powered by find and fzf!
Small catch though: It only works on the server!
Or does it?
I have the server listed in my ssh config:
Host server
HostName server.example.com
User myname
IdentityFile ~/.ssh/id_rsa
so I can use the shorthand ssh server
instead of
ssh myname@server.example.com
and then typing in my
password.
This is convenient, and allows me to trivially run remote commands
over ssh. e.g. ssh server ls
.
You can’t run fzf non-interactively though. And trying to will cause
a ssh error. Fortunately, we can forcefully allocate a tty with a
-tt
flag.
ssh server -tt ~/bin/fing
This works!
Let’s pop this into a file with a shebang, chmod +x
it,
and now I have a local script that runs the browser remotely!
cat<<EOF > ~/bin/local_fing && chmod +x ~/bin/local_fing
#!/usr/bin/env sh
ssh server -tt ~/bin/fing
EOF