dmenu + historial
Si estás acá seguramente ya conocés dmenu. Es un lanzador de aplicaciones simple y funcional. Pero hay algo que siempre me molestó: por qué no puede recordar qué comandos uso más seguido?
Se que hay un parche para ello (ese navhistory
, pero no se si hace lo que busco), pero lo probé y no me funcionó o no lo supe hacer funcionar, bueno me lo adapto y ya!
Me armé un script que combina el historial de tu shell favorita (en mi caso zsh) con el autocompletado de dmenu. El resultado es un launcher que:
- Te muestra primero todos los comandos disponibles alfabéticamente
- Incluye después los comandos de tu historial más recientes
- Guarda automáticamente los comandos nuevos en tu historial
#!/bin/sh
# Archivo de historial de zsh (en caso de que tu historial sea otro, bash, etc..indicalo)
historial=$HOME/.zsh_history
# Invertimos con tac el archivo para tener los más recientes primero, luego eliminamos duplicados
history_commands=$(tac "$historial" | \
sed 's/^: [0-9]*:[0-9]*;//' | \
awk '!seen[$0]++')
# Obtenemos todos los comandos disponibles en los PATH usando find
path_commands=$(printf '%s\n' "$PATH" | tr ':' '\n' | while read -r dir; do
[ -d "$dir" ] && find "$dir" -maxdepth 1 -type f -executable -printf '%f\n' 2>/dev/null
done | sort -u)
# Combinamos comandos del PATH y del historial, con prioridad a los del PATH
all_commands=$(printf "%s\n%s\n" "$path_commands" "$history_commands" | \
awk '!seen[$0]++')
# Mostramos comandos en dmenu los top 10
selected_command=$(printf '%s\n' "$all_commands" | \
dmenu -i -l 10 -p "Command:")
# Si se selecciona un comando, lo ejecutamos
if [ -n "$selected_command" ]; then
eval "$selected_command" &
# Actualizamos el historial de la shell: eliminamos ocurrencias viejas y agregamos al final
tmp_hist=$(mktemp)
grep -vF "$selected_command" "$historial" > "$tmp_hist" 2>/dev/null
printf ': %s:0;%s\n' "$(date +%s)" "$selected_command" >> "$tmp_hist"
mv "$tmp_hist" "$historial"
fi
El archivo historial de zsh se guardá asi (el de otras shell es sin timestamp, el script es compatible con dicho formato también:
: 1691234567:0;wine notepad.exe
Donde:
-
:
es el prefijo -
1691234567
es el timestamp Unix -
0
es el tiempo de ejecución -
wine notepad.exe
es el comando real que corre, en este caso mi editor de texto favorito
El script usa sed
para extraer solo la parte del comando, si tu historial no tiene estos datos mejor!!!
sed 's/^: [0-9]*:[0-9]*;//'
2. Eliminando duplicados
Uso tac
para invertir el archivo (más recientes primero) y después awk
para eliminar duplicados:
tac "$historial" | \
sed 's/^: [0-9]*:[0-9]*;//' | \
awk '!seen[$0]++'
De esta forma, si ejecutaste wine
tres veces esta semana, aparece alfabéticamente en su lugar correspondiente entre los comandos del PATH, y no se duplica.
3. Autocompletado del PATH
Para agregar todos los comandos disponibles, recorro cada directorio del PATH:
printf '%s\n' "$PATH" | tr ':' '\n' | while read -r dir; do
[ -d "$dir" ] && find "$dir" -maxdepth 1 -type f -executable -printf '%f\n' 2>/dev/null
done | sort -u
4. Combino todo!
Finalmente, combino los comandos del PATH con el historial, dándole prioridad a los comandos del PATH:
all_commands=$(printf "%s\n%s\n" "$path_commands" "$history_commands" | \
awk '!seen[$0]++')
Y lo muestro en dmenu con 10 líneas visibles (vos poné el número que quieras)
selected_command=$(printf '%s\n' "$all_commands" | \
dmenu -i -l 10 -p "Command:")
Configuración
Para usar este script, guardalo por ej en ~/.local/bin/dmenu_history.sh
, dale permisos de ejecución y agregá esta línea a tu config del wm favorito. En mi caso i3:
bindsym $mod+d exec ~/.local/bin/dmenu_history.sh
Resultado final
Cuando llamo a este script y escribo algo del comando que quiero correr muestra:
Primero: Todos los comandos disponibles alfabéticamente en los PATH
Después: Los comandos del archivo historial que no están en el PATH en orden de los más recientes primero
Por ej si escribo "fire", inmediatamente filtra a firefox
, si alguna vez corrí firefox pornhub.com
, me mostrará este último en segundo lugar.
Si escribo un comando nuevo, se ejecuta y se guarda automáticamente en mi historial para la próxima vez.
Me ocurría muchas veces que corría comandos complejos en el terminal y luego no podía buscarlos en dmenu, esto lo resuelve con creces.
Espero les sea útil!
** Update **: hice un pequeño cambio, que si el comando ya está listado lo mueva a la lista de los últimos ejecutados. Ocurría que lo mantenía donde ya estaba si el comando existía, ahora lo mueve a los últimos ejecutados y borra el duplicado.
Tags: Scripts