223
223
foo "${a[@]}"
224
224
#+end_src
225
225
226
-
227
226
** Read lines from file
228
227
The =read= command we used just above is part of the usual idiom to
229
228
read a file line by line.
1353
1352
With this, you can find out how many matches happened in that
1354
1353
line. The example above filters lines which have 2 words from the
1355
1354
'foo|bar|baz' regex.
1356
-
1357
-
1358
-
1359
1355
*** Set operations
1360
1356
There are lots of other "set level" operations you can perform on
1361
1357
files/streams using basic unix tools.
@@ -1966,7 +1962,15 @@ coprocs would mess up your inline functions.
1966
1962
and if the user forgets any, the shell will barf.
1967
1963
1968
1964
** Idempotent functions
1965
+ Being able to call the same function multiple times even if you just
1966
+ called it, without having to account for "oh, did this already happen
1967
+ or not?" but instead have a mental model of "I need this to have
1968
+ happened after this function call", and consider it done, is very
1969
+ liberating from the code perspective.
1969
1970
1971
+ Also, there are subgoals, like caching slow functions, downloads, etc.
1972
+
1973
+ *** nop subsequent calls
1970
1974
"My favourite shell scripting function definition technique:
1971
1975
idempotent functions by redefining the function as a noop inside
1972
1976
its body:
@@ -1980,6 +1984,64 @@ coprocs would mess up your inline functions.
1980
1984
echo "This bit will only be executed on the first foo call"
1981
1985
}
1982
1986
#+end_src
1987
+ *** Download once
1988
+ A function might be sideffecty, as in "download files". These sort of
1989
+ cases, you want to download the file only if you haven't downloaded it
1990
+ yet.
1991
+
1992
+ =cache= here gets a cache "id" where it'll store the results of =http=
1993
+ and next time, when called with the same param, it will reuse the saved version.
1994
+
1995
+ This pattern applies to many kinds of functions, so with slight
1996
+ modifications you can adapt it.
1997
+
1998
+ #+begin_src bash
1999
+ cache() {
2000
+ local path=$CACHE_DIR/$1
2001
+ [[ -f $path ]] && cat $path && return 0
2002
+
2003
+ shift
2004
+ $@ | tee $path
2005
+
2006
+ return 1
2007
+ }
2008
+
2009
+ get_repos() {
2010
+ local url="https://api.github.com/search/repositories?q=user:$USER&access_token=$GITHUB_TOKEN&per_page=100&page=$1"
2011
+ cache $USER-repos-$1 http $url
2012
+ }
2013
+ #+end_src
2014
+
2015
+ *** memoize with ttl, with ttl
2016
+ What if you want to cache for some time?
2017
+
2018
+ The tricks here:
2019
+
2020
+ - Use a file as a sentinel for the cache.
2021
+ - Create a file, set it's modification date to now()+12hours
2022
+ - check with `-nt` (newer than) and a temporary file (to have a
2023
+ =now()= value in "file" format).
2024
+
2025
+ #+begin_src bash
2026
+ hmacache="/tmp/.hmacache"
2027
+ is_recent_cache() {
2028
+ [ "$hmacache" -nt <(echo 1) ]
2029
+ }
2030
+ update_cache() {
2031
+ touch -m -t "$(date -v+12H +"%Y%m%d%H%M.%S")" "$hmacache"
2032
+ }
2033
+
2034
+ main() {
2035
+ #...
2036
+ if ! is_recent_cache ; then
2037
+ if ! curl -s https://hello.ts.net > /dev/null; then
2038
+ die "Tailscale must be connected to start Harbormaster"
2039
+ fi
2040
+ # other slow checks
2041
+ update_cache
2042
+ fi
2043
+ }
2044
+ #+end_src
1983
2045
1984
2046
* Debugging
1985
2047
** adding =bash= to a script to debug
0 commit comments