diff --git a/README.md b/README.md index df9c44d..c03c371 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,37 @@ $ git clone git://github.com/spencertipping/bash-lambda $ source bash-lambda/bash-lambda ``` +*Before you do this, be aware that bash-lambda garbage-collects a part of your +filesystem. Think about what might theoretically go wrong with this.* I source +it in my .bashrc without problems, but as always, your mileage may vary. + +Sourcing `bash-lambda` will do a few things: + +1. Create a heap directory in `$TMPDIR` (usually `/tmp`) +2. Add this heap directory to your `$PATH` +3. Add the asynchronous GC hook to your `$PROMPT_COMMAND` +4. Add lots of variables and functions to your shell process +5. Add an `EXIT` trap to nuke the heap directory + Loading the library takes very little time even though a new heap is created for every process. This is because the heap setup and sourcing of your `.bash-lambda` file (run `defs` to edit it) is all done asynchronously. When the background initialization is complete, a small `λ` will appear in the rightmost column of your window. +You can write arbitrary text to the right-hand side of the terminal by using +the `message` function: + +``` +$ message hi +$ hi +``` + +Before getting started, go ahead and run `defs` once. This will initialize your +`~/.bash-lambda` file with some useful functions for common file operations. If +you already have a `~/.bash-lambda` file from an earlier version, you can stash +it somewhere, run `defs`, and then merge in previous definitions. + ## Defining functions The functions provided by bash-lambda take their nomenclature from Clojure, and @@ -177,6 +202,15 @@ $ seq 1 4 | reductions $add 0 $ ``` +Filtering is useful when working with files, especially in conjunction with +some builtins that come with the standard `~/.bash-lambda` file. For example: + +``` +$ ls | filter is-d # same as ls | filter $(fn '[[ -d $1 ]]') +src +$ ls | filter $(newerthan build) +``` + ### Flatmap (mapcat in Clojure) It turns out that `map` already does what you need to write `mapcat`. The lists @@ -364,6 +398,29 @@ $ The exit code is also memoized; however, standard error and other side-effects are not. There is no way to clear a future after it has finished executing. +### Mapping + +You can use the `future_map` function to transform the output of a future when +it completes. You can also use this to trigger alerts. For example: + +``` +$ f=$(future $(fn 'sleep 10')) +$ future_map $f $(fn 'message done') +$ +``` + +If you do this, the word 'done' will show up on the right side of the terminal +in a few seconds. + +Command output is usually captured by the future. The only reason `message` +works is that it prints to standard error, which is let through synchronously. +This way you can immediately observe failures in future processes. + +Note that `future_map` is not a multimethod. The reason is that the regular +`map` function needs to be able to process stdin, which has no `ref_type`. As a +result, `map` is not a multimethod, so by extension, `future_map` is not an +implementation of `map`. + ### Partial results You can use `unsafe_get` to retrieve whatever stdout has been produced without diff --git a/bash-lambda b/bash-lambda index 07d611f..164a432 100755 --- a/bash-lambda +++ b/bash-lambda @@ -5,6 +5,7 @@ # functions in the shell. The heap will be deleted automatically when the shell # exits. See https://github.com/spencertipping/bash-lambda for documentation. +#!/bin/bash # Bash-lambda disk-based heap # We need 128 bits of entropy for the heap directory name. This gives us @@ -56,7 +57,7 @@ bash_lambda_cons() { cat > $(bash_lambda_gc_guard "$file") && # Take everything from stdin chmod u+x "$file" && # All conses are executable echo "$file"; } - +#!/bin/bash # Bash-lambda reference functions bash_lambda_ref() { @@ -129,7 +130,7 @@ bash_lambda_ref_children() { egrep -o "$BASH_LAMBDA_HEAP/[^ [:space:]/\)\}\"']+" | (declare ref while read ref; do [[ -e "$ref" ]] && echo "$ref"; done); } - +#!/bin/bash # Bash-lambda concurrent mark-sweep garbage collector # BASH_LAMBDA_GC_SECONDS=0 will disable automatic GC @@ -233,7 +234,7 @@ bash_lambda_gc_unpin() { rm -f "$BASH_LAMBDA_HEAP/.gc-permanent/${1##*/}"; bash_lambda_gc_roots() { declare; ps ax; ls -d "$BASH_LAMBDA_HEAP/.gc-permanent"/* | (declare x while read x; do echo "$BASH_LAMBDA_HEAP/${x##*/}"; done); } - +#!/bin/bash # Bash-lambda function and closure allocation bash_lambda_fn_body() { @@ -265,14 +266,12 @@ bash_lambda_defn() { declare name=$1; shift # pinned so that they will never be garbage-collected. bash_lambda_extern() { bash_lambda_gc_pin $( (echo "#!/bin/bash" - echo "$1_main() {" - declare -f "$1" | grep '^ ' - echo "}" - echo "$1_main \"\$@\"") | bash_lambda_cons -n $1); } + declare -f "$1" + echo "$1 \"\$@\"") | bash_lambda_cons -n $1); } bash_lambda_def() { rm -f $BASH_LAMBDA_HEAP/$1 ln -s $2 $(bash_lambda_gc_pin $BASH_LAMBDA_HEAP/$1); } - +#!/bin/bash # Bash-lambda functional programming constructs # $(comp $f $g $h) x = f $(g $(h x)) @@ -287,7 +286,7 @@ bash_lambda_comp() { bash_lambda_partial() { bash_lambda_fn "$* \"\$@\""; } - +#!/bin/bash # Bash-lambda multimethods # These work only on fully-named parameters, not on stuff coming from stdin. If @@ -324,7 +323,7 @@ bash_lambda_defmulti() { count \ grab release wrap; do bash_lambda_defmulti $method; done) > /dev/null - +#!/bin/bash # Bash-lambda pipe locks # A pipe-lock is a way to block one process until another one lets it through. @@ -343,7 +342,7 @@ bash_lambda_pipelock() { bash_lambda_pipelock_grab() { cat $1 >& /dev/null; } bash_lambda_pipelock_release() { echo > $1; } - +#!/bin/bash # Bash-lambda semaphores and mutexes # Semaphores are directories that contain empty numbered subdirectories along @@ -400,7 +399,7 @@ bash_lambda_mutex_wrap() { declare status=\$? bash_lambda_mutex_release \$lock exit \$status; fi"; } - +#!/bin/bash # Bash-lambda atomic values # An atomic value supports thread-safe assignment and access. It does this by @@ -439,7 +438,7 @@ bash_lambda_atom_wrap() { declare status=\$? bash_lambda_atom_release \$lock exit \$status; fi"; } - +#!/bin/bash # Bash-lambda list programming constructs bash_lambda_list() { declare x @@ -496,7 +495,7 @@ bash_lambda_every() { cat ${2:--} | (declare x; while read x; do if ! $1 "$x" > /dev/null; then echo "$x"; return 1; fi; done; return 0); } - +#!/bin/bash # Bash-lambda futures (asynchronous processes) # Future locking @@ -519,7 +518,7 @@ bash_lambda_future() { declare result=$(printf $"%s\n%s\n%s\n" $output $status $state | \ bash_lambda_cons future) - ("$@" > $output; echo $? > $status; notify $result) >& /dev/null & + ("$@" > $output; echo $? > $status; notify $result) > /dev/null & echo $result; } bash_lambda_future_finished() { @@ -551,6 +550,10 @@ bash_lambda_future_get() { cat "$(bash_lambda_nth 0 "$1")" return "$(< "$(bash_lambda_nth 1 "$1")")"; } +bash_lambda_future_map() { + # Returns a future of a function applied to this future's value. + bash_lambda_future $(fn "$2 \$(future_get $1)"); } + bash_lambda_future_unsafe_get() { # Gets whatever stdout has been produced so far. The process may not have # exited, so this function returns 0. @@ -559,7 +562,7 @@ bash_lambda_future_unsafe_get() { bash_lambda_future_transpose() { bash_lambda_future $(bash_lambda_partial \ bash_lambda_map bash_lambda_future_get $1); } - +#!/bin/bash # Bash-lambda remote functions # Remote functions allow you to transparently migrate the execution of a @@ -594,7 +597,7 @@ bash_lambda_remote() { bash_lambda_remote_clean() { # Removes this instance's heap snapshot from the remote instance. ssh $host "rm -rf \"$BASH_LAMBDA_HEAP\""; } - +#!/bin/bash # Bash-lambda wait functions # Various ways to wait for things. Usually you would use this with semaphores, @@ -626,7 +629,7 @@ bash_lambda_spin_until() { $(bash_lambda_spinning_fn "$@"); } bash_lambda_wait_wrap() { $(wait_until $(bash_lambda_partial wrap "$@")); } bash_lambda_spin_wrap() { $(spin_until $(bash_lambda_partial wrap "$@")); } - +#!/bin/bash # Bash-lambda parallel execution # Parallel tasks are defined as an interface that transposes parallelism over @@ -663,37 +666,48 @@ bash_lambda_parallel_map() { $(bash_lambda_semaphore_wrap $s $f)) bash_lambda_map $(bash_lambda_partial bash_lambda_future $blocking_f) $xs; } - +#!/bin/bash # Bash-lambda rc file functions export BASH_LAMBDA_RC=${BASH_LAMBDA_RC:-$HOME/.bash-lambda} export BASH_LAMBDA_EDITOR=${BASH_LAMBDA_EDITOR:-${EDITOR:-$VISUAL}} +export COLUMNS # so that bash_lambda_message can be called from subshells + bash_lambda_message() { declare m="$*" - (( $COLUMNS )) && echo -en "\033[s\033[$((COLUMNS - ${#m}))G$m\033[u"; } + (( $COLUMNS )) && echo -en "\033[s\033[$((COLUMNS - ${#m}))G$m\033[u" 1>&2; } bash_lambda_setup_rc() { - [[ -e "$BASH_LAMBDA_RC" ]] || sed 's/^ //' > "$BASH_LAMBDA_RC" \ - <<<"#!/bin/bash - # You can put function defs here. Variables you define here aren't visible, - # since this file is always evaluated (usually asynchronously) from inside a - # subshell. - # - # This file is sourced asynchronously when you start your shell, so adding - # definitions won't increase the amount of time required to open a new - # terminal. - # - # See https://github.com/spencertipping/bash-lambda for details about - # defining functions. - # - # Examples: - # - # def my-computers \$(list host1 host2 host3) - # defn check-status 'hostname; df -h | grep /$; uptime' - # defn check-computers 'map \$(partial remote check-status)' - # - "; } + [[ -e "$BASH_LAMBDA_RC" ]] || sed 's/^ //' > "$BASH_LAMBDA_RC" <<'EOF' +#!/bin/bash +# You can put function defs here. Variables you define here aren't visible, +# since this file is always evaluated (usually asynchronously) from inside a +# subshell. +# +# This file is sourced asynchronously when you start your shell, so adding +# definitions won't increase the amount of time required to open a new +# terminal. +# +# See https://github.com/spencertipping/bash-lambda for details about +# defining functions. +# +# For example: + +# File tests (wrappers for -d, -x, etc) +def bash_tests $(ref $(list a b c d e f g h k p r s t u w x G L N O S z n)) +defn deffiletest x 'defn is-$x f "[[ -$x \$f ]]"' +map deffiletest $(bash_tests) + +defn newerthan file 'bash_lambda_fn f "[[ \$f -nt $file ]]"' +defn olderthan file 'bash_lambda_fn f "[[ \$f -ot $file ]]"' +defn eq file 'bash_lambda_fn f "[[ \$f -ef $file ]]"' + +# Content tests +defn contains pattern 'egrep -o $pattern' +defn without pattern 'sed "s/${pattern/\//\\\/}//g"' +EOF +} bash_lambda_reload_rc() { [[ -e "$BASH_LAMBDA_RC" ]] && (. "$BASH_LAMBDA_RC" > /dev/null); } @@ -702,7 +716,7 @@ bash_lambda_defs() { bash_lambda_setup_rc $BASH_LAMBDA_EDITOR "$BASH_LAMBDA_RC" (bash_lambda_reload_rc &); } - +#!/bin/bash # Bash-lambda function exporting and GC hooks # Export the bash_lambda library into the current heap @@ -720,4 +734,3 @@ bash_lambda_init() { # Run a GC, if necessary, after each command export PROMPT_COMMAND="$PROMPT_COMMAND; bash_lambda_auto_gc" - diff --git a/build b/build index 7a46ff8..4c8d222 100755 --- a/build +++ b/build @@ -1,11 +1,6 @@ #!/bin/bash (cat src/header - for file in heap ref gc fn fp multi \ - pipelock semaphore atom \ - list \ - future remote wait parallel \ - rc init; do - cat src/$file | grep -v '^#!' - echo; done) > bash-lambda + cat src/{heap,ref,gc,fn,fp,multi,pipelock,semaphore,atom} \ + src/{list,future,remote,wait,parallel,rc,init}) > bash-lambda chmod +x bash-lambda diff --git a/src/fn b/src/fn index dd12e08..d492ce8 100644 --- a/src/fn +++ b/src/fn @@ -30,10 +30,8 @@ bash_lambda_defn() { declare name=$1; shift # pinned so that they will never be garbage-collected. bash_lambda_extern() { bash_lambda_gc_pin $( (echo "#!/bin/bash" - echo "$1_main() {" - declare -f "$1" | grep '^ ' - echo "}" - echo "$1_main \"\$@\"") | bash_lambda_cons -n $1); } + declare -f "$1" + echo "$1 \"\$@\"") | bash_lambda_cons -n $1); } bash_lambda_def() { rm -f $BASH_LAMBDA_HEAP/$1 ln -s $2 $(bash_lambda_gc_pin $BASH_LAMBDA_HEAP/$1); } diff --git a/src/future b/src/future index 9954f5c..95a22b6 100644 --- a/src/future +++ b/src/future @@ -21,7 +21,7 @@ bash_lambda_future() { declare result=$(printf $"%s\n%s\n%s\n" $output $status $state | \ bash_lambda_cons future) - ("$@" > $output; echo $? > $status; notify $result) >& /dev/null & + ("$@" > $output; echo $? > $status; notify $result) > /dev/null & echo $result; } bash_lambda_future_finished() { @@ -53,6 +53,10 @@ bash_lambda_future_get() { cat "$(bash_lambda_nth 0 "$1")" return "$(< "$(bash_lambda_nth 1 "$1")")"; } +bash_lambda_future_map() { + # Returns a future of a function applied to this future's value. + bash_lambda_future $(fn "$2 \$(future_get $1)"); } + bash_lambda_future_unsafe_get() { # Gets whatever stdout has been produced so far. The process may not have # exited, so this function returns 0. diff --git a/src/rc b/src/rc index 48f7f69..7a9864e 100644 --- a/src/rc +++ b/src/rc @@ -4,31 +4,42 @@ export BASH_LAMBDA_RC=${BASH_LAMBDA_RC:-$HOME/.bash-lambda} export BASH_LAMBDA_EDITOR=${BASH_LAMBDA_EDITOR:-${EDITOR:-$VISUAL}} +export COLUMNS # so that bash_lambda_message can be called from subshells + bash_lambda_message() { declare m="$*" - (( $COLUMNS )) && echo -en "\033[s\033[$((COLUMNS - ${#m}))G$m\033[u"; } + (( $COLUMNS )) && echo -en "\033[s\033[$((COLUMNS - ${#m}))G$m\033[u" 1>&2; } bash_lambda_setup_rc() { - [[ -e "$BASH_LAMBDA_RC" ]] || sed 's/^ //' > "$BASH_LAMBDA_RC" \ - <<<"#!/bin/bash - # You can put function defs here. Variables you define here aren't visible, - # since this file is always evaluated (usually asynchronously) from inside a - # subshell. - # - # This file is sourced asynchronously when you start your shell, so adding - # definitions won't increase the amount of time required to open a new - # terminal. - # - # See https://github.com/spencertipping/bash-lambda for details about - # defining functions. - # - # Examples: - # - # def my-computers \$(list host1 host2 host3) - # defn check-status 'hostname; df -h | grep /$; uptime' - # defn check-computers 'map \$(partial remote check-status)' - # - "; } + [[ -e "$BASH_LAMBDA_RC" ]] || sed 's/^ //' > "$BASH_LAMBDA_RC" <<'EOF' +#!/bin/bash +# You can put function defs here. Variables you define here aren't visible, +# since this file is always evaluated (usually asynchronously) from inside a +# subshell. +# +# This file is sourced asynchronously when you start your shell, so adding +# definitions won't increase the amount of time required to open a new +# terminal. +# +# See https://github.com/spencertipping/bash-lambda for details about +# defining functions. +# +# For example: + +# File tests (wrappers for -d, -x, etc) +def bash_tests $(ref $(list a b c d e f g h k p r s t u w x G L N O S z n)) +defn deffiletest x 'defn is-$x f "[[ -$x \$f ]]"' +map deffiletest $(bash_tests) + +defn newerthan file 'bash_lambda_fn f "[[ \$f -nt $file ]]"' +defn olderthan file 'bash_lambda_fn f "[[ \$f -ot $file ]]"' +defn eq file 'bash_lambda_fn f "[[ \$f -ef $file ]]"' + +# Content tests +defn contains pattern 'egrep -o $pattern' +defn without pattern 'sed "s/${pattern/\//\\\/}//g"' +EOF +} bash_lambda_reload_rc() { [[ -e "$BASH_LAMBDA_RC" ]] && (. "$BASH_LAMBDA_RC" > /dev/null); }