123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- #!/bin/bash
- set -euo pipefail
- if [ "$#" == "0" ]
- then
- echo recall
- exit 0
- fi
- . "${BASH_SOURCE[0]%/*}"/helpers/common.sh
- stderr_deps=/dev/null
- check_deps 3>&2 2>"$stderr_deps" 1>/dev/null
- LOG_ROOT="$HOME"/.local/var/log/shell
- EXIT_SUCCESS=false
- LIST_MODE=false
- CMD_STRING=false
- ORS='\n'
- usage () {
- cat <<EOF
- Recall prints the last captured output from the specified PROG and exits with
- the captured exit status.
- usage: "${0##*/} [ -l ] [ -n NUM ] PROG [ ARGS ... ]
- examples:
- - Print the output of last captured find command and exit with captured
- exit status
- ${0##*/} find
- - Print the output of last captured find command and exit with success
- ${0##*/} -z find
- - List paths to all captured commands
- ${0##*/} -l
- - List paths to all captured find commands
- ${0##*/} -l find
- - List at most 10 paths to all captured find commands
- ${0##*/} -l -n 10 find
- - List paths null delimited for pipe (in case of paths with new line char)
- ${0##*/} -l -0 find | xargs -r -0 ls -lahd
- - List last 30 command strings (bash escaped)
- ${0##*/} -lsn 30
- EOF
- }
- OPTSTRING=":zhln:s0"
- parse_opt () {
- case "${opt}" in
- h)
- usage
- exit 0
- ;;
- l)
- LIST_MODE=true
- ;;
- n)
- if [ "${OPTARG:-}" ] && [[ "$OPTARG" =~ [0-9]+ ]] && (( OPTARG > 0 ))
- then
- NUM="$OPTARG"
- else
- echo "-n paremeter must be an integer greater than zero" >&2
- echo >&2
- usage >&2
- exit 1
- fi
- ;;
- z)
- EXIT_SUCCESS=true
- ;;
- s)
- CMD_STRING=true
- ;;
- 0)
- ORS='\0'
- ;;
- \?)
- echo "Unknown switch: -$OPTARG" >&2
- echo >&2
- usage >&2
- exit 1
- ;;
- esac
- }
- validate () {
- # exit if vars are not valid
- # ie, set any dynamic defaults here
- if ! "$LIST_MODE" && [ "${NUM:=1}" ] && ! (( NUM == 1 ))
- then
- echo "-n != 1 can only be specified with -l" >&2
- echo >&2
- usage >&2
- exit 1
- fi
- if ! "$LIST_MODE" && [ "$#" == "0" ]
- then
- echo "PROG must be provided" >&2
- echo >&2
- usage >&2
- exit 1
- fi
- }
- while getopts "${OPTSTRING}" opt
- do
- parse_opt
- done
- shift $((OPTIND-1))
- validate "$@"
- fzf_rw0_inplace_preview () {
- # NOTE: to preview and select captured command directories
- header_len="$1"
- # shellcheck disable=SC2016
- fzf \
- --read0 --print0 \
- -m \
- --no-sort \
- -d / \
- --preview-window="~$header_len" \
- --preview='cat {}/info; head -n "$LINES" {}/dat' \
- && :
- }
- awk_strip_first_path_part_script () {
- cat <<'EOF'
- {
- for (i=2; i<=NF; i++)
- printf "%s%s", $(i), (i<NF ? OFS : ORS)
- }
- EOF
- }
- awk_rw0_strip_first_path_part () {
- # NOTE: ./a/b/c -> a/b/c (null delimited)
- awk \
- -v FS='/' -v RS='\0' \
- -v OFS='/' -v ORS='\0' \
- "$(awk_strip_first_path_part_script)" \
- && :
- }
- awk_r0_add_prefix () {
- # NOTE: some-string -> <prefix>some-string (null delimited)
- : "${2?awk_r0_add_prefix requires exactly two positional args}"
- : "${1:?awk_r0_add_prefix output record separator must be set and non-empty}"
- [ "${1:+DefinedNotEmpty}" == "DefinedNotEmpty" ]
- [ "${2+DefinedMaybeEmpty}" == "DefinedMaybeEmpty" ]
- awk \
- -v prefix="$2/" \
- -v RS='\0' \
- -v ORS="$1" \
- '{print prefix $0}' \
- && :
- }
- awk_r0_add_suffix () {
- # NOTE: some-string -> some-string<suffix> (null delimited)
- : "${2?awk_r0_add_suffix requires exactly two positional args}"
- : "${1:?awk_r0_add_suffix output record separator must be set and non-empty}"
- [ "${1:+DefinedNotEmpty}" == "DefinedNotEmpty" ]
- [ "${2+DefinedMaybeEmpty}" == "DefinedMaybeEmpty" ]
- awk \
- -v suffix="/$2" \
- -v RS='\0' \
- -v ORS="$1" \
- '{print $0 suffix}' \
- && :
- }
- sort_rw0 () {
- # NOTE: sort by program, or timestamp
- if [ "$1" = "prog" ]
- then
- sort -zrV
- elif [ "$1" = "ts" ]
- then
- awk \
- -v FS='/' -v RS='\0' \
- -v OFS='/' -v ORS='\0' \
- '{print $NF,$0}' \
- | sort -zrV \
- | awk_rw0_strip_first_path_part \
- :
- else
- echo "unsupported sort option: $1" >&2
- false
- fi
- }
- list_r0 (){
- ors="$1"
- if ! "$CMD_STRING"
- then
- awk_rw0_strip_first_path_part \
- | awk_r0_add_prefix "$ors" "$LOG_ROOT" \
- && :
- else
- awk_r0_add_suffix '\0' 'info' \
- | xargs -r0n1 head -n1 \
- | awk \
- -v RS='\n' \
- -v ORS="$ors" \
- '{print $0}' \
- && :
- fi
- }
- if "$LIST_MODE"
- then
- # TODO: make sort option configurable
- sort_opt="ts"
- find_name=()
- if [ "${1:-}" ]
- then
- slugs=( "$1" )
- if [ "${2:-}" ]
- then
- slugs+=( "$2" )
- fi
- query="$(bash -ac 'IFS=/; echo "$*"' "recall-bash" "${slugs[@]}")"
- find_name=( -wholename "**/$query/**" )
- fi
- (
- cd "$LOG_ROOT"
- find . \
- \( -type l -o -type d \) -wholename './????????_??????_?????????' -prune -o -type d "${find_name[@]}" \( \
- -links 2 \
- -o \( \
- -links 1 -exec bash -c '! [ "$(find "$1" -mindepth 1 -type d)" ]' find-bash {} \; \
- \) \
- \) \
- -print0 \
- | sort_rw0 "$sort_opt" \
- | head -zn "${NUM:--0}" \
- | fzf_rw0_inplace_preview "2" \
- | list_r0 "$ORS" \
- && :
- )
- else
- PROG="$1"
- SUBPROG="$(get_subprog "$@")"
- LOG_DIR="$LOG_ROOT"/"$PROG"/"$SUBPROG"
- LOG_DIR="$LOG_DIR"/"$(cd "$LOG_DIR" && find . -mindepth 1 -maxdepth 1 -print0 | sort -zV | tail -zn1 | xargs -r -0 echo)"
- OUT="$LOG_DIR"/stdout
- ERR="$LOG_DIR"/stderr
- # shellcheck disable=SC2034
- DAT="$LOG_DIR"/dat
- INF="$LOG_DIR"/info
- # shellcheck disable=SC1090
- . <(tail -n +2 "$INF") # first line is commandline
- "$EXIT_SUCCESS" && status=0
- #TODO: allow user to request stderr in case it is needed for
- # testing redirections
- # disabled by defualt as it won't be interleaved correctly
- # on the console
- false && cat "$ERR" >&2
- cat "$OUT"
- exit "$status"
- fi
|