bm-check (2504 bytes)
1 #!/bin/sh 2 3 # bm-check: report dead and permanently moved links. 4 # 5 # bm-check the default bookmark file 6 # bm-check file ... these files ("-" is stdin) 7 # bm --tag code --list | bm-check - 8 # 9 # Reads bookmark lines (only the first field matters, so a plain list of URLs 10 # works too) and prints, for each link that is in trouble, 11 # 12 # status<tab>url[<tab>new location] 13 # 14 # where status is the HTTP status, or 000 when there was no answer. 301 and 15 # 308 are reported because the bookmark should be updated. Exits 1 when it 16 # printed anything. 17 18 probe='%{http_code} %{redirect_url}' 19 20 check () { 21 res=$(curl -sI --max-time 10 -A 'Mozilla/5.0' -o /dev/null -w "$probe" \ 22 -- "$1" 2>/dev/null) 23 case ${res%% *} in 24 403|405|000|'') 25 # Some servers refuse HEAD; ask for the first byte instead. 26 res=$(curl -s -r 0-0 --max-time 10 -A 'Mozilla/5.0' -o /dev/null \ 27 -w "$probe" -- "$1" 2>/dev/null) 28 ;; 29 esac 30 code=${res%% *} 31 case $code in 32 301|308) printf '%s\t%s\t%s\n' "$code" "$1" "${res#* }" ;; 33 2*|3*) ;; 34 *) printf '%s\t%s\n' "${code:-000}" "$1" ;; 35 esac 36 } 37 38 case ${1-} in 39 -h|--help) sed -n '3,16s/^# \{0,1\}//p' "$0"; exit 0 ;; 40 esac 41 42 command -v curl >/dev/null 2>&1 || { printf 'bm-check: needs curl\n' >&2; exit 1; } 43 [ $# -gt 0 ] || set -- "${BOOKMARKS:-${XDG_DATA_HOME:-$HOME/.local/share}/sbm/bookmarks}" 44 45 # Eight probes in flight until the list runs out, like xargs -P 8, with a 46 # FIFO as the semaphore: it starts out holding eight tokens, a probe takes one 47 # before it starts and puts it back when it is done. Opening a FIFO blocks 48 # until the other end is opened too, hence the helper that opens it for 49 # writing while we open it for reading. Every report is one short line from 50 # one printf, so concurrent probes do not garble each other. 51 fifo=${TMPDIR:-/tmp}/bm-check.$$ 52 mkfifo -m 600 "$fifo" || { printf 'bm-check: cannot create %s\n' "$fifo" >&2; exit 1; } 53 trap 'rm -f "$fifo"' EXIT INT TERM 54 : > "$fifo" & 55 # shellcheck disable=SC2094 # both ends of the FIFO, on purpose 56 exec 3< "$fifo" 4> "$fifo" 57 rm -f "$fifo" 58 printf '\n\n\n\n\n\n\n\n' >&4 59 60 bad=$(cat -- "$@" | awk -F'\t' 'NF && !/^#/ { print $1 }' | { 61 while IFS= read -r url; do 62 # shellcheck disable=SC2034 # the token is only waited for 63 read -r token <&3 64 { check "$url"; echo >&4; } & 65 done 66 wait 67 }) 68 [ -z "$bad" ] && exit 0 69 printf '%s\n' "$bad" 70 exit 1