There is a lot of hype going around with people saying they have 10x or more productivity but a lot of skeptics rightfully ask, where is the tenfold increase in feature volume or quality?
I make no attempt to address this. I suspect that 10x may be very near or already exceeding our capacity to keep up from a planning/design cognitive perspective, so we may feel in the moment that buildout productivity is 10x or more, but in the end we'll be limited by our ideation speed.
Still, one thing I have found very valuable so far in the past few years is a lowered barrier to improving ux in all sorts of areas.
The most recent example is something really narrow and simple that still took some iteration to get to something I like, but I think I will be pretty satisfied with it. It is an example of a tool that maybe I could have attempted to do in the past, and maybe I could have achieved a similar result in 2 hours of work, but I think if I did it without AI the quality would be much lower.
To set the stage: On most computers ejecting a removable disk is done with something in the GUI in the operating system. That does not change even when I am on my Mac and using the terminal. It's far easier, and arguably more suitable, to click the eject button in Finder.
On Linux though, my primary environment is much more so the terminal. The Linux machine in question may be my NAS that is currently under my desk, but I will typically not bother to dedicate and route mouse, keyboard, and display to this machine. Since it is the NAS, and for the time being when it happens to be physically accessible, it becomes the natural place to connect things like SD cards for media dumping into my ZFS pool. Although the eventual setup will involve high speed home networking and dumping media to the same ZFS pool over Samba from another computer, I decided that I was fed up with not having a truly convenient way to quickly eject disks like this from the Linux command line.
So I came up with a script and it took about 30 minutes to do. I expected it to take 2 minutes but there was some iteration that had to happen. You see, following up on the usual barriers to unmounting a filesystem leads down several rabbit holes.
Honestly handling all of this is a pretty tall order... but we have "unlimited" productivity on tap.
All I can say is I have this working now, and the first 3 cases are cleanly handled... I now have a script cwd-holders which builds a useful report about what's going on preventing the unmount, and a streamlining script that has simple and sane semantics that i can call that will eject the disk if the current directory is that disk, or it will produce the report for what to clean up before I can cleanly eject the disk. I will flesh this article out with more details on the implementation soon, especially full examples of how this thing looks when you use it, but I can dump it out here for now:
It has two components, a helper shell function that is portable between zsh and bash, inserted in my aliases dotfile (e.g. source this from your bashrc or zshrc):
__eject_decode_findmnt() {
printf '%b' "$1"
}
__eject_findmnt_target_for_path() {
__eject_decode_findmnt "$(findmnt -T "$1" -n -o TARGET 2>/dev/null | head -n 1)"
}
__eject_findmnt_source_for_path() {
__eject_decode_findmnt "$(findmnt -T "$1" -n -o SOURCE 2>/dev/null | head -n 1)"
}
__eject_findmnt_target_for_dev() {
__eject_decode_findmnt "$(findmnt -rn -S "$1" -o TARGET 2>/dev/null | head -n 1)"
}
eject() {
if [[ "$(uname -s)" != "Linux" ]]; then
echo "eject: Linux-only helper" >&2
return 1
fi
if [[ $# -gt 0 ]]; then
command /usr/bin/eject "$@"
return
fi
local target mountpoint source_dev disk_name disk_dev removable hotplug tran
local dev mp failed_mountpoint
local -a mounted_devs
target="$(pwd -P)"
mountpoint="$(__eject_findmnt_target_for_path "$target")"
source_dev="$(__eject_findmnt_source_for_path "$target")"
[[ -n "$mountpoint" ]] || { echo "eject: could not find a mount containing $target" >&2; return 1; }
[[ "$target" == "$mountpoint" ]] || { echo "eject: run from the mounted filesystem root, not from $target" >&2; return 1; }
case "$mountpoint" in
/run/media/*|/media/*|/mnt/*) ;;
*) echo "eject: $mountpoint is not under /run/media, /media, or /mnt" >&2; return 1 ;;
esac
case "$mountpoint" in
/|/boot|/boot/*|/dev|/dev/*|/etc|/etc/*|/home|/home/*|/opt|/opt/*|/root|/root/*|/srv|/srv/*|/usr|/usr/*|/var|/var/*)
echo "eject: refusing to operate on system mountpoint $mountpoint" >&2
return 1
;;
esac
[[ "$source_dev" == /dev/* ]] || { echo "eject: $mountpoint is mounted from $source_dev, not a block device" >&2; return 1; }
source_dev="${source_dev%%\[*}"
source_dev="$(readlink -f "$source_dev")" || { echo "eject: could not resolve block device for $mountpoint" >&2; return 1; }
[[ -b "$source_dev" ]] || { echo "eject: $source_dev is not a block device" >&2; return 1; }
disk_name="$(lsblk -no PKNAME "$source_dev" | head -n 1 | tr -d '[:space:]')"
if [[ -z "$disk_name" ]]; then
disk_dev="$source_dev"
else
disk_dev="/dev/$disk_name"
fi
[[ -b "$disk_dev" ]] || { echo "eject: $disk_dev is not a block device" >&2; return 1; }
removable="$(lsblk -dno RM "$disk_dev" | tr -d '[:space:]')"
hotplug="$(lsblk -dno HOTPLUG "$disk_dev" | tr -d '[:space:]')"
tran="$(lsblk -dno TRAN "$disk_dev" | tr -d '[:space:]')"
if [[ "$removable" != 1 && "$hotplug" != 1 && "$tran" != usb && "$tran" != mmc && "$tran" != firewire ]]; then
echo "eject: $disk_dev does not look removable or hotplugged (RM=$removable HOTPLUG=$hotplug TRAN=${tran:-unknown})" >&2
return 1
fi
while IFS= read -r dev; do
mp="$(__eject_findmnt_target_for_dev "$dev")"
[[ -n "$mp" ]] && mounted_devs+=("$dev")
done < <(lsblk -nrpo NAME "$disk_dev")
[[ ${#mounted_devs[@]} -gt 0 ]] || { echo "eject: no mounted filesystems found on $disk_dev" >&2; return 1; }
for dev in "${mounted_devs[@]}"; do
mp="$(__eject_findmnt_target_for_dev "$dev")"
[[ -n "$mp" ]] || continue
case "$mp" in
/run/media/*|/media/*|/mnt/*) ;;
*) echo "eject: refusing to power off $disk_dev while $dev is mounted at non-removable-media path $mp" >&2; return 1 ;;
esac
done
echo "Unmounting filesystems on $disk_dev:"
for dev in "${mounted_devs[@]}"; do
mp="$(__eject_findmnt_target_for_dev "$dev")"
echo " $dev${mp:+ mounted at $mp}"
done
builtin cd / || return
for dev in "${mounted_devs[@]}"; do
if ! sudo udisksctl unmount -b "$dev"; then
failed_mountpoint="$(__eject_findmnt_target_for_dev "$dev")"
if command -v cwd-holders >/dev/null 2>&1; then
cwd-holders "${failed_mountpoint:-$mountpoint}" >&2
else
echo "eject: install cwd-holders for detailed cwd/SSH/tmux diagnostics" >&2
fi
return 1
fi
done
echo "Powering off $disk_dev"
sudo udisksctl power-off -b "$disk_dev"
}
And a helper bash script cwd-holders:
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: cwd-holders [path]
List shell processes whose current working directory is at or under path.
On Linux, also shows SSH connection details and useful tmux client context.
If path is omitted, the current directory is used.
EOF
}
die() {
echo "cwd-holders: $*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}
have_cmd() {
command -v "$1" >/dev/null 2>&1
}
proc_env_value() {
local pid=$1
local name=$2
[[ -r /proc/"$pid"/environ ]] || return 1
tr '\0' '\n' < /proc/"$pid"/environ 2>/dev/null |
awk -F= -v name="$name" '$1 == name { sub(/^[^=]*=/, ""); print; exit }'
}
cmdline_for_pid() {
local pid=$1
local cmdline=
if [[ -r /proc/"$pid"/cmdline ]]; then
cmdline=$(tr '\0' ' ' < /proc/"$pid"/cmdline 2>/dev/null | sed 's/[[:space:]]*$//')
fi
if [[ -z $cmdline ]]; then
cmdline=$(ps -p "$pid" -o args= 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
fi
[[ -n $cmdline ]] && printf '%s' "$cmdline"
}
hostname_for_ip() {
local ip=$1
local name=
if have_cmd getent; then
name=$(getent hosts "$ip" 2>/dev/null | awk 'NR == 1 { print $2 }')
fi
if [[ -z $name && $ip == 100.* ]] && have_cmd tailscale; then
name=$(tailscale status 2>/dev/null | awk -v ip="$ip" '$1 == ip { print $2; exit }')
fi
if [[ -n $name && $name != "$ip" ]]; then
printf '%s(%s)' "$name" "$ip"
else
printf '%s' "$ip"
fi
}
ssh_parts_for_pid() {
local pid=$1
local ssh_connection ssh_client
local -a ssh_parts
ssh_connection=$(proc_env_value "$pid" SSH_CONNECTION || true)
if [[ -n $ssh_connection ]]; then
read -r -a ssh_parts <<< "$ssh_connection"
if [[ ${#ssh_parts[@]} -ge 4 ]]; then
printf '%s|%s|%s|%s' "${ssh_parts[0]}" "${ssh_parts[1]}" "${ssh_parts[2]}" "${ssh_parts[3]}"
else
printf '%s||||%s' "unknown" "$ssh_connection"
fi
return 0
fi
ssh_client=$(proc_env_value "$pid" SSH_CLIENT || true)
if [[ -n $ssh_client ]]; then
read -r -a ssh_parts <<< "$ssh_client"
if [[ ${#ssh_parts[@]} -ge 3 ]]; then
printf '%s|%s|server|%s' "${ssh_parts[0]}" "${ssh_parts[1]}" "${ssh_parts[2]}"
else
printf '%s||||%s' "unknown" "$ssh_client"
fi
fi
}
ssh_remote_for_pid() {
local pid=$1
local client_ip client_port server_ip server_port extra
IFS='|' read -r client_ip client_port server_ip server_port extra < <(ssh_parts_for_pid "$pid" || true)
[[ -n ${client_ip:-} ]] || return 0
if [[ $client_ip == unknown ]]; then
printf '%s' "${extra:-unknown}"
else
printf '%s:%s' "$(hostname_for_ip "$client_ip")" "$client_port"
fi
}
ssh_route_for_pid() {
local pid=$1
local client_ip client_port server_ip server_port extra
IFS='|' read -r client_ip client_port server_ip server_port extra < <(ssh_parts_for_pid "$pid" || true)
[[ -n ${client_ip:-} ]] || return 0
if [[ $client_ip == unknown ]]; then
printf '%s' "${extra:-unknown}"
else
printf '%s:%s->%s:%s' "$client_ip" "$client_port" "$server_ip" "$server_port"
fi
}
ancestor_summary_for_pid() {
local pid=$1
local parent comm chain=
while [[ $pid =~ ^[0-9]+$ && $pid -gt 1 ]]; do
parent=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d '[:space:]')
[[ -n $parent && $parent =~ ^[0-9]+$ ]] || break
comm=$(ps -p "$parent" -o comm= 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[[ -n $comm ]] || break
case "$comm" in
sshd|sshd-session|tmux:*|tmux)
chain="${chain:+$chain<-}$comm($parent)"
;;
esac
pid=$parent
done
[[ -n $chain ]] && printf '%s' "$chain"
}
normalize_tty() {
case "$1" in
/dev/*) printf '%s' "$1" ;;
\?|"") printf '%s' "$1" ;;
*) printf '/dev/%s' "$1" ;;
esac
}
tmux_pane_for_tty() {
local tty=$1
local line pane_pid pane_tty session_name window_index window_name pane_index pane_id pane_current_path
have_cmd tmux || return 0
tty=$(normalize_tty "$tty")
while IFS= read -r line; do
IFS='|' read -r pane_pid pane_tty session_name window_index window_name pane_index pane_id pane_current_path <<< "$line"
[[ $pane_tty == "$tty" ]] || continue
printf '%s:%s.%s:%s %s pane-root-pid=%s path=%s' \
"$session_name" "$window_index" "$window_name" "$pane_index" "$pane_id" "$pane_pid" "$pane_current_path"
return 0
done < <(tmux list-panes -a -F '#{pane_pid}|#{pane_tty}|#{session_name}|#{window_index}|#{window_name}|#{pane_index}|#{pane_id}|#{pane_current_path}' 2>/dev/null || true)
}
parent_summary_for_pid() {
local pid=$1
local parent comm cmdline
parent=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d '[:space:]')
[[ -n $parent && $parent =~ ^[0-9]+$ && $parent -gt 1 ]] || return 0
comm=$(ps -p "$parent" -o comm= 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[[ -n $comm ]] || return 0
cmdline=$(cmdline_for_pid "$parent" || true)
if [[ -n $cmdline ]]; then
printf '%s %s -- %s' "$parent" "$comm" "$cmdline"
else
printf '%s %s' "$parent" "$comm"
fi
}
print_details() {
local ssh_route=$1
local ancestor_summary=$2
local detail=
[[ -n $ssh_route ]] && detail="ssh=$ssh_route"
[[ -n $ancestor_summary ]] && detail="${detail:+$detail }ancestors=$ancestor_summary"
[[ -n $detail ]] && printf ' details: %s\n' "$detail"
}
print_optional_field() {
local label=$1
local value=$2
[[ -n $value ]] || return 0
printf ' %-8s %s\n' "$label:" "$value"
}
is_shell_command() {
case "$1" in
bash|zsh|fish|sh|dash|ksh|mksh|tcsh|csh|nu|xonsh|elvish) return 0 ;;
*) return 1 ;;
esac
}
print_shell_holders() {
local target=$1
local proc_cwd pid real_cwd comm tty cmdline remote_host tmux_pane parent_summary ssh_route ancestor_summary
local found=0
for proc_cwd in /proc/[0-9]*/cwd; do
pid=${proc_cwd#/proc/}
pid=${pid%/cwd}
real_cwd=$(readlink -e "$proc_cwd" 2>/dev/null || true)
[[ -n $real_cwd ]] || continue
case "$real_cwd" in
"$target"|"$target"/*) ;;
*) continue ;;
esac
comm=$(ps -p "$pid" -o comm= 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' || true)
is_shell_command "$comm" || continue
tty=$(ps -p "$pid" -o tty= 2>/dev/null | tr -d '[:space:]' || true)
cmdline=$(cmdline_for_pid "$pid" || true)
remote_host=$(ssh_remote_for_pid "$pid" || true)
tmux_pane=$(tmux_pane_for_tty "$tty" || true)
parent_summary=$(parent_summary_for_pid "$pid" || true)
ssh_route=$(ssh_route_for_pid "$pid" || true)
ancestor_summary=$(ancestor_summary_for_pid "$pid" || true)
printf 'PID %s TTY %s CMD %s\n' "$pid" "${tty:-?}" "${comm:-?}"
print_optional_field "cmdline" "$cmdline"
print_optional_field "remote" "$remote_host"
print_optional_field "tmux" "$tmux_pane"
print_optional_field "parent" "$parent_summary"
print_optional_field "cwd" "$real_cwd"
print_details "$ssh_route" "$ancestor_summary"
echo
found=1
done
[[ $found -eq 1 ]]
}
print_tmux_clients() {
local line client_pid client_tty client_name client_session remote_host ssh_route ancestor_summary
local -a tmux_lines
have_cmd tmux || return 0
mapfile -t tmux_lines < <(tmux list-clients -F '#{client_pid}|#{client_tty}|#{client_name}|#{client_session}' 2>/dev/null || true)
[[ ${#tmux_lines[@]} -gt 0 ]] || return 0
echo
echo "Attached tmux clients:"
for line in "${tmux_lines[@]}"; do
IFS='|' read -r client_pid client_tty client_name client_session <<< "$line"
[[ -n $client_pid ]] || continue
remote_host=$(ssh_remote_for_pid "$client_pid" || true)
ssh_route=$(ssh_route_for_pid "$client_pid" || true)
ancestor_summary=$(ancestor_summary_for_pid "$client_pid" || true)
printf 'PID %s TTY %s\n' "$client_pid" "${client_tty:-?}"
print_optional_field "remote" "$remote_host"
print_optional_field "session" "$client_session"
print_optional_field "name" "${client_name:-?}"
print_details "$ssh_route" "$ancestor_summary"
echo
done
}
if [[ ${1-} == "-h" || ${1-} == "--help" ]]; then
usage
exit 0
fi
[[ "$(uname -s)" == "Linux" ]] || die "Linux only"
[[ $# -le 1 ]] || die "unexpected arguments"
need_cmd awk
need_cmd ps
need_cmd readlink
need_cmd sed
need_cmd tr
target=${1:-$PWD}
orig_target=$target
target=$(readlink -e "$target") || die "could not resolve $orig_target"
[[ -d $target ]] || die "$target is not a directory"
echo "Shell processes with cwd under $target:"
if print_shell_holders "$target"; then
:
else
echo "No shell processes with cwd under $target were found."
fi
print_tmux_clients
Pretty boring, pretty narrow tool. But it's clear that if I make a thousand of these tools, quality of life will shoot through the roof. It's also interesting to consider how someone else might have basically zero use for the vast majority of these thousand things of mine. Such is the fractal nature of life.