A Weird Imagination

Debouncing shell commands

The problem#

For a compile-on-save workflow where some computation is done in response to every change to a file, if there may sometimes be many changes close together, it may be wasteful to respond to all of them. This is often handled by debouncing the events: instead of responding to every change, ignore changes that occur too close together in time.

The solution#

watch_todo_debounced.sh is a modification of the watch_todo.sh script from my recent post on converting todo.txt files to HTML. It uses a script I found called debounce.sh to wait until there have been no updates to the todo.txt file for 5 seconds before generating the HTML file. The core logic looks like this:

while true
do
    inotifywait -e close_write "$1" >/dev/null
    echo "TODO file updated."
done | debounce.sh read $delay "./do-something.sh \"$1\""

$delay is the delay in seconds to wait before taking an action. $1 is file to watch for changes on and ./do-something.sh is the script to run on it when it changes.

The details#

Read more…

Interpreting keypresses in shell

The problem#

Last time, I presented a script read_keypress.sh which would read any single keypress in a shell script. For printable characters, it's straightforward what this means, but for keys like left arrow (🠈), it's not obvious how to deal with them.

The solution#

keytest.sh demonstrates using read_keypress.sh to respond to keypresses including 🠈 and Ctrl+c:

#!/bin/bash

esc=$(printf '\e')
ctrlc=$(printf '\x03')

while true
do
  key="$(./read_keypress.sh)"
  case $key in
    q|"$ctrlc")
      echo "Quitting..."
      exit
      ;;
    "${esc}[D")
      echo "Moved left."
      ;;
    *)
      echo "Unexpected key: $key ($(echo -n "$key" | xxd))"
      ;;
  esac
done

The details#

Read more…

Handling keypresses in shell

The problem#

For certain applications it can be useful to get many quick responses from the computer, so we would like it to react to individual keypresses instead of requiring an entire command to be typed out and confirmed by pressing Enter. While this is a common feature of GUI and TUI toolkits (e.g., ncurses), it can also be useful for very lightweight inactive experiences written as simple shell scripts.

The solution#

read_keypress.sh will wait for the next keypress and return it as a string:

#!/bin/sh

STTY=$(stty --save)
stty raw -echo
SEL=$(dd if=/dev/tty bs=1k count=1 2>/dev/null)
stty "$STTY"
echo -n "$SEL"

The details#

Read more…

Recreate moves from zfs diff

Posted in

The problem#

When doing an incremental backup, any moved file on the source filesystem usually results in recopying the file to the destination filesystem. For a large file this can both be slow and possibly waste space if the destination keeps around deleted files (e.g. ZFS holding on to old snapshots). If both sides are ZFS, then you can get zfs send/recv to handle all of the details efficiently. But if only the source filesystem is ZFS or the ZFS datasets are not at the same granularity on both sides, that doesn't apply.

zfs diff gives the information about file moves from a snapshot, but its output format is a little awkward for scripting.

The solution#

Download the script I wrote, zfs-diff-move.sh and run it like

zfs-diff-move.sh /path/ /tank/dataset/ tank/dataset@base @new

The following is an abbreviated version of it:

#!/bin/bash
zfs diff -H "$3" "$4" | grep '^R' | while read -r line
do
  get_path() {
    path="$(echo -e "$(echo "$line" | cut -d$'\t' "-f$3")")"
    echo "${path/#$2/$1}"
  }

  from="$(get_path "$1" "$2" 2)"
  to="$(get_path "$1" "$2" 3)"
  mkdir -vp -- "$(dirname "$to")"
  mv -vn -- "$from" "$to" || echo "Unable to move $from"
done

The details#

Read more…

Listing files into a file

Posted in

The problem#

$ ls > file

doesn't do what you expect:

$ touch foo
$ touch bar
$ ls > filelist
$ cat filelist
bar
filelist
foo

You probably didn't expect, or want, filelist to be listed in filelist.

The solution#

$ filelist=$(ls); echo "$filelist" >filelist

Read more…