“Redemption” (George Herbert)

“Redemption” by George Herbert, paraphrased by Art Eschenlauer

As long-time tenant to a wealthy Lord,
not thriving, I resolvèd to be bold
and make suit to the Owner to afford
a new, reduced-rent lease, and cancel th’old.

At Heaven’s manor, thus, my Lord I sought:
They told me that my Lord was lately gone
about some land on Earth, so dearly bought
quite long ago, to take possession.

Returning, knowing of my Lord’s great birth,
I searched accordingly in great resorts;
in cities, theaters, gardens, parks, and courts:

At length, I heard a ragged noise and mirth
of thieves and murd’rers: there my Lord I spied,
who said, “Your suit is granted,” and then died.

I paraphrased George Herbert’s poem “Redemption” (published in 1633 in The Temple; see e.g. https://tinyurl.com/GeorgeHerbertRedemption) because I wanted more modern language (more gender-neutral and less confusing), but I wanted to try to preserve the “feel” and meaning as best I could.

License: CC BY-SA 4.0

Cross Purpose

Cross Purpose:

A response to George Herbert’s “Redemption”

At Heaven’s manor’s door I stood and knocked
to ask my Lord about this mystery.
I hoped my loving Lord would help me see
how, from my sin, my spirit is unlocked.

“My cross is where your condemnation ends,
so you can view your past without despair,
and, freed from Satan’s sway, you can repair
relationships and start to make amends.”

It seems I want to have a better past
with facts that are much less unsettling.
I want my Savior’s precious blood to bring
me far from those regrets which hold me fast.

“My child,” my Lord said,  “that can never be.
Accept your past, and know you live with Me.”

“The peace of God, which passeth all understanding, shall
keep your hearts and minds through Christ Jesus.”
– Philippians 4:7 KJV

The Greek and Hebrew words term translated as “sin” both literally are the archery term meaning “missing the mark”[1].   It is not the error that hurts me the most, but rather the impact on the person whom I have hurt, along with its effect on our relationship (which is changed even though my victim may have forgiven me).  This makes it difficult to be fully present when reconciling with my victims while I am at the same time feeling wracked with guilt.  How does redemption change my relationship with the fact of what I have done?  I wrote this sonnet as I reflected on this question.

I like the sonnet as a way to structure thought: the form classically begins with contemplation of an issue (“the argument”) and then moves on to proposing how the issue might be addressed (“the resolution”) [2].

[1] https://en.wikipedia.org/wiki/Hamartia#In_Christian_theology
[2] https://en.wikipedia.org/wiki/Sonnet#Petrarchan_sonnet


License: CC BY-SA 4.0

Two Days After

Two Days After

Beside a boulder, Mary Magdalene
stands looking through the tomb’s door and dark space
at empty grave cloths lying in the place
where Jesus’ lifeless body last was seen.

And Crucifixion I see all the time
as Christ-within-all suffers at the hand
of scared folks like me who don’t understand
that they themselves are victims of their crime.

An empty tomb! So rare, bewildering.
Her hopes and dreams are shattered; with her there
comes grief and will to show the corpse the care
that, to the living, people would not bring.

So, blind with grief, by Mary now I stand,
Not recognizing Jesus-now-at-hand!

“… she turned around and saw Jesus standing there,
but she did not know that it was Jesus.”
– John 20:14 NRSV

License: CC BY-SA 4.0

When nslookup is missing but python is not

nslookup replacement

Here’s how to look up an IP address when you don’t have nslookup but you have Python:

python -c "import socket; print(socket.gethostbyname('google-public-dns-a.google.com'))"

Of course, this all goes on one line; I think that when you copy the code above it will have no line breaks.

Here’s how to look up a host name:

python -c "import socket; print(socket.gethostbyaddr('')[0])"

In this case, gethostbyaddr returns a tuple so [0] is needed to get the first member of the tuple.

netstat replacement

Unfortunately, a replacement for netstat is not as concise in python – 241 lines at
https://github.com/da667/netstat. But if you have wget or curl and an Internet connection you can do something like this:

python -c "$( wget -O - https://raw.githubusercontent.com/da667/netstat/master/netstat.py )"

tinyurl.com to the rescue:

python -c "$( wget -O - https://tinyurl.com/netstat-py )"


2018.05.01 – updated for python3 syntax


Docker – remove exited containers and dangling volumes

Here’s my dockernuke script which does what it says it does when it says it’s doing it.

echo find and destroy exited containers
echo ---
sudo docker rm $( echo $( \
sudo docker ps -a --filter="status=exited" -q) )
echo ...
echo find and destroy orphaned volumes
echo ---
sudo docker volume rm $( \
sudo docker volume ls -q -f 'dangling=true' )
echo ...
echo "tabula rasa!"

R equivalent to SQL select … group … by … having

In principle,  you can use the R ‘sqldf’ package for embedding SQL in R.  However, sqldf does not allow you to use R functions as aggregation functions.  This post is to help me remember how to translate SQL statements that include groupoing into base R.  Suppose the following test data:

test_df <- data.frame(
  sample      = c(1,2,3,4,5,6)
, sample_type = c(1,1,1,2,2,2)

A single aggregation can be performed to produce a single data.frame.  For example, this SQL:

select sample_type, count(sample) as sample_count
  from test_df
 group by sample_type 
 order by sample_type
having sample_count > 4

can be expressed in R as:

  x <- with(
    # FROM test_df
  , aggregate(
      # SELECT COUNT(sample) AS sample_count
      x = data.frame(sample_count = sample)
          # second column(s) of resulting data.frame
    , # SELECT sample_type
      # GROUP BY sample_type
      # ORDER BY sample_type
      by = list(sample_type = sample_type)
          # first column(s) of resulting data.frame
          # 'by' determines both the groups to be 
          #   aggregated and the order of the result
    , # SELECT COUNT(sample)
      FUN = length
          # R function to mimic SQL 'count' function
, # HAVING sample_count > 4
  x[ sample_count > 4, ]

This gets rather busy when aggregating multiple columns because each aggregation produces a data frame, so you need to “merge” the data frames (analogous to a SQL join):

pseudo SQL (pretending that SQL has statistical aggregation functions)

select sample_type
     , count(sample) as sample_count
     , mean(sample)  as sample_mean
     , var(sample)   as sample_var
  from test_df
 group by sample_type 
 order by sample_type
having sample_count > 4


  x <-  with(
  , {
      by <- list(sample_type = sample_type)
        f = function(dtf1, dtf2) merge(dtf1, dtf2)
      , x = list(
            x = data.frame(sample_count=sample)
          , by = by , FUN = length)
        , aggregate(
            x = data.frame(sample_mean =sample)
          , by = by, FUN = mean)
        , aggregate(
            x = data.frame(sample_var =sample),
            by = by, FUN = var)
, x[ sample_count > 4, ]


R S4 objects and overloaded data.frame

Here is some code extracted from this answer http://stackoverflow.com/a/14607290 to the question “How to create a dataframe of user defined S4 classes in R“. The answer and question are well worth reading, but I wanted to have the code example in one place without the intervening comments:

# Create S4 class person(name,age)
  slots = c(

# Create subsetting operator
  function(x, i, j) {
    initialize(x, name=x@name[i], age=x@age[i])

# Create overload for format()
format.person <- function(x) {
  paste0(x@name, ", ", x@age)

# Create overload for as.data.frame()
as.data.frame.person <-
  function(x, row.names=NULL, optional=FALSE)
  if (is.null(row.names))
    row.names <- x@name
    value <- list(x)
    attr(value, "row.names") <- row.names
    class(value) <- "data.frame"

# Create overload for c()
c.person = function(...) {
  args = list(...)
      name=sapply(args, function(x) x@name),  
      age=sapply(args, function(x) x@age)

# Demonstrate the code above; writes "John, 20"
            new("person", name="Tom", age=30),
            new("person", name="John", age=20)


Conda Install R packages Tcl Error – solved

When I try to install R packages in conda, sometimes I get the following error:

. . .
Error: .onLoad failed in loadNamespace() for 'tcltk', details:
  call: fun(libname, pkgname)
  error: Can't find a usable init.tcl in the following directories: 
    /opt/anaconda1anaconda2anaconda3/lib/tcl8.5 ./lib/tcl8.5 ./lib/tcl8.5 ./library ./library ./tcl8.5.18/library ./tcl8.5.18/library

This is happening because install.packages is trying to paint the repository picker window – I don’t understand why it’s not using the command-line repository picker.

My workaround was to set the working directory to


so that


was on my path. Then it painted the repository-picker just fine, and I was able to install my update.

An alternative workaround might have been to ssh to the same box (without X forwarding).

bash get directory for script

First, some warnings from the BashFAQ:

  • Your script does not actually have a location! Wherever the bytes end up coming from, there is no “one canonical path” for it. Never.

  • $0 is NOT the answer to your problem. If you think it is, you can either stop reading and write more bugs, or you can accept this and read on.

The BashFAQ also describes BASH_SOURCE and the applicable caveats.  Here’s some workable, albeit fallible, code from http://stackoverflow.com/questions/59895/can-a-bash-script-tell-which-directory-it-is-stored-in:

# find directory where script may reside
# resolve $SOURCE till not a symlink
while [ -h "$SOURCE" ]; do 
  DIR="$( cd -P "$(dirname "$SOURCE")" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd && \
  echo x)"
echo Script $0 resides in directory $DIR

docker subverts ufw

The Problem

I found on Ubuntu that Docker modifies iptables such that ufw cannot effectively control incoming connections, as documented here:


A Workaround

This link also points out that it is possible to override this behavior by adding the --iptables=false option to the command line that starts the Docker daemon. For a systemd example, this option is appended to the ExecStart line in

ExecStart=/usr/bin/dockerd -H fd:// –iptables=false

A Side-Effect of the Workaround

On Debian, with dockerd lauched with the --iptables=false option, I tried to do “docker build” for a DockerFile that included:

RUN npm install -g ethercalc pm2

But, this failed with:

npm info retry will retry, error on last attempt: Error: getaddrinfo ENOTFOUND registry.npmjs.org registry.npmjs.org:443

So, I had to

  • restart dockerd without the --iptables=false option
  • do the docker build
  • restart dockerd with the --iptables=false option

I would like to find a more elegant solution!!

Update 1:

On a Debian without any bridges, I don’t notice a difference in the output of ‘iptables -L’ with or without the option set.  So, I’m a bit stumped. Could this issue only affect Ubuntu? Could it only affect machines that have bridges?

Update 2:

I tried this again with my Ubuntu machine that does indeed have bridges, and I found that ‘iptables -L’ output is unaffected, yet, when I started dockerd without the --iptables=false option, my machine accepted connections even when ufw was set to reject all incoming connections.  So, I’m still stumped.