latest.sh 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. # Usage: latest [-h] [options] [WHERE] [MTIME] [[WHAT] REGEX]]
  2. #
  3. # Find lastest modified files[ and grep them].
  4. # Because my life is in .org files now...
  5. #
  6. # This is really just a wrapper around find -exec grep,
  7. # but it encapsulates some defaults/patterns I use a lot
  8. #
  9. # - Looking for .org files (default)
  10. # - Looking for recently modified (MTIME) files
  11. # - Looking for other types of files (WHAT, e.g. .txt)
  12. # - Looking in the current directory (WHERE) usually
  13. # - Grep(1)ing for file content (GREP)
  14. # + sometimes case insensitive
  15. # + usually using --color
  16. # - Ignoring junk (PRUNE)
  17. #
  18. # This is bash, be safe
  19. #
  20. # shellcheck shell=bash
  21. # shellcheck disable=SC3046 # "source" is more readable than "."
  22. # shellcheck disable=SC1090 # allow source from other locations
  23. # shellcheck disable=SC2112 # "function" is more readable than "()"
  24. # shellcheck disable=SC1036,SC1065,SC1088
  25. function latest ()
  26. {
  27. (
  28. # This is bash. Be safe.
  29. set -u
  30. set -e
  31. # These are running in the context of the subshell
  32. # so they will not affect the parent (login) shell
  33. # but will give us extra checks/safety.
  34. # Pull in my logging utils if available
  35. { test -f ~/lib/bash/bashutils.sh && source ~/lib/bash/bashutils.sh ; } ||
  36. {
  37. # fall back to echo
  38. function info() { echo "$@"; };
  39. function warn() { echo "$@"; };
  40. function error() { echo "$@"; };
  41. function die { echo "$@"; exit 1; };
  42. }
  43. #
  44. # check dependancies
  45. #
  46. #
  47. # Defaults for parameters that control find(1)
  48. #
  49. WHAT=${WHAT:-org$}
  50. MTIME=${MTIME:-7}
  51. WHERE=${WHERE:-.}
  52. TIMEOUT=30 # max run at 30 seconds
  53. FIND_REGEX="-regex" # case sensitive by default
  54. which timeout > /dev/null || die "Timeout not found"
  55. # define filenames/paths to be ignored
  56. # "junk" stanard on linux systems
  57. PRUNE_LINUX='/.git/|backups/|auto-save-list|/.config/|snap/|/.cache/|/.local/|/.mozilla/|/.targe|/.rustup/|/.cargo/|/.venv/'
  58. # junk specific to me Your junk milage may vary.
  59. PRUNE_JUST_ME='blog/docs|orgfiles'
  60. PRUNE=".*(${PRUNE_JUST_ME}|${PRUNE_LINUX}).*"
  61. #
  62. # defaults for parameters that control grep of file content
  63. #
  64. REGEX=${REGEX:-}
  65. GREPCOLOR="always"
  66. #
  67. # define errors to be ignored
  68. #
  69. # TODO grep out?
  70. function usage {
  71. verbosity=${1:-"short"}
  72. cat <<EOF 1>&2
  73. Usage: latest [options] [WHERE] [MTIME] [[WHAT] REGEX]]
  74. Find the latest files and what's in them.
  75. Options
  76. -h print short help text
  77. --help print long help text
  78. -d|--debug print debugging
  79. -t|--timeout TIMEOUT Timeout find command Default: $TIMEOUT
  80. Find Optons
  81. -L Follow links Default: Do not follow links.
  82. --mtime MTIME Max age of files to find. Default: $MTIME
  83. 0 means "forever"
  84. -w|--where DIR Where to search for files. Default: $WHERE
  85. Grep Options
  86. |--color COLOR Grep color. Default: $GREPCOLOR
  87. "always","never", or "auto".
  88. -i|--ignore-case Ignore case. Default: case sensitive.
  89. -g|--grep REGEX Regex to grep in files. Default: None.
  90. -l|--files-with-matches Only print filename Default: print match as well.
  91. Arguments
  92. First one or two positons.
  93. WHERE A path (incuding "/"). Overrides -w.
  94. MTIME Max age. Digits. Overrides --mtime.
  95. First non-age (digits), non-path (contains "/") argument
  96. WHAT Filenames to search for, in full path. Default: $WHAT
  97. Second non-age (digits), non-path (contains "/") argument
  98. REGEX Regex to grep. Overrrides -g.
  99. EOF
  100. if [ "$verbosity" == "long" ]; then
  101. cat <<EOF2 1>&2
  102. Examples:
  103. # default: find latest .org files in the current directory
  104. $ latest
  105. ./test.org
  106. # find org files with an mtime <= 14 days in the current directory
  107. $ latest 14
  108. ./test.org
  109. ./xxy.org
  110. # Find latest .txt files in current directory
  111. $ latest .txt
  112. ./foo.txt
  113. # Find latest .txt files and grep for "baz"
  114. $ latest .txt baz
  115. ./foo.txt:foo bar baz
  116. ./foo.txt:foo|bar|baz
  117. # Find org files in ~/Org and grep for "DONE"
  118. $ latest ~/Org .org DONE
  119. /home/gmj/Org/agenda-files/blogging.org:**** DONE g/re/p
  120. /home/gmj/Org/agenda-files/blogging.org:**** DONE Write next "40 years of walled garden & open platforms" article
  121. # Find the lastest .org and py files, 120 days old or les
  122. latest 120 '(\.py$|\.org$)'
  123. EOF2
  124. fi
  125. }
  126. # Save extra FIND/GREP flags here
  127. GREPFLAGS=()
  128. FINDFLAGS=()
  129. # parse optons
  130. while [[ $# -gt 0 ]]; do
  131. case ${1} in
  132. -h) usage "short" && return 1;;
  133. --help) usage "long" && return 1;;
  134. -d|--debug) DEBUG=1 && shift;;
  135. -t|--timeout)
  136. shift;
  137. TIMEOUT="$1"; { { [[ $# -gt 0 ]] && shift; } || die '--timeout requires an argument'; } ;;
  138. # Find options
  139. --mtime)
  140. shift;
  141. MTIME="$1"; { { [[ $# -gt 0 ]] && shift; } || die '--mtime requires an argument'; } ;;
  142. -w|--where)
  143. shift;
  144. WHERE="$1"; { { [[ $# -gt 0 ]] && shift; } || die '--where requires an argument'; } ;;
  145. -L)
  146. shift;
  147. FINDFLAGS+=("-L");;
  148. # Grep options
  149. --color)
  150. shift;
  151. GREPCOLOR="${1}"; { { [[ $# -gt 0 ]] && shift; } || die '--color requires an argument'; } ;;
  152. -g|--grep)
  153. shift;
  154. REGEX="${1}"; { { [[ $# -gt 0 ]] && shift; } || die '--grep requires an argument'; } ;;
  155. -i|--ignore-case)
  156. shift;
  157. FIND_REGEX="-iregex"
  158. GREPFLAGS+=("-i");;
  159. -l|--files-with-matches)
  160. shift;
  161. GREPFLAGS+=("-l");;
  162. -*) error "Unknown flag: $1" && return 1;;
  163. *) break;;
  164. esac
  165. done
  166. # parse args
  167. found_what=false
  168. while [[ $# -gt 0 ]]; do
  169. if [[ "$1" = */* ]]; then
  170. WHERE=$1;
  171. shift;
  172. elif [[ "$1" =~ ^[0-9]+$ ]]; then
  173. MTIME="$1"
  174. shift
  175. elif [[ $found_what == true ]]; then
  176. REGEX="$1";
  177. shift;
  178. else
  179. found_what=true;
  180. WHAT="$1";
  181. shift;
  182. fi
  183. done
  184. if test "$MTIME" = 0; then MTIME=999999; fi
  185. # Do things to make regular expressions pass down correctly
  186. #
  187. # - ' ' -> '\\s'
  188. REGEX="${REGEX// /\\s}"
  189. if [[ $# -gt 0 ]]; then
  190. die "too many arguments given. At most 3 allowed. Unknow: ${ARGV[*]}"
  191. fi
  192. if [[ ${REGEX} != "" ]]; then
  193. GREPFLAGS=("-exec" "grep" "${GREPFLAGS[@]}" "--color=${GREPCOLOR}" "-H ""-E" "$REGEX" '{}' ';')
  194. else
  195. GREPFLAGS=( '-print' )
  196. fi
  197. [[ -v DEBUG ]] && set -x
  198. # this shoud be removed; If not, find failed/timed out
  199. RUNNING=$(mktemp /tmp/latest-running.XXXXXX)
  200. # shellcheck disable=SC2046 # Quote this to prevent word splitting.
  201. #
  202. # This is to all the
  203. #
  204. # $(: COMMENT comments here)
  205. #
  206. # syntax work without warning.
  207. # Run in a subshell to allow timeout
  208. ( \
  209. $(: COMMENT limit run to "$TIMEOUT" seconds) \
  210. timeout "$TIMEOUT" \
  211. find \
  212. "${FINDFLAGS[@]}" \
  213. "${WHERE}" \
  214. \
  215. $(: COMMENT global options) \
  216. -regextype posix-extended \
  217. -xdev \
  218. \
  219. $(: COMMENT prune 'junk' files and dirs: "${PRUNE}" )\
  220. -regex "${PRUNE}" -prune -o \
  221. $(: only look at regular files) \
  222. -type f \
  223. \
  224. $(: COMMENT restrict to mtime "$MTIME" days ago) \
  225. -mtime -"${MTIME}"\
  226. \
  227. $(: COMMENT restrict to files that match "$WHAT" in full pat) \
  228. ${FIND_REGEX} ".*${WHAT}.*" \
  229. \
  230. $(: COMMENT run grep if requested: "${GREPFLAGS[*]}") \
  231. ${GREPFLAGS[*]} && \
  232. \
  233. $(: COMMENT If find finished, remove RUNNING file "${RUNNING}". Timeout will leave it) \
  234. /bin/rm -f "$RUNNING" \
  235. ) \
  236. # |& grep -v -E ': Permission|: Too many levels'
  237. # shellcheck enable=quote-safe-variables
  238. if [[ -f "${RUNNING}" ]]; then
  239. warn "Find did not finish. Timeout or error";
  240. \rm "${RUNNING}"
  241. fi
  242. [[ -v DEBUG ]] && set +x
  243. )}
  244. # "see" where something is
  245. alias see=latest