Sunday, September 15, 2013

What made the past week so great

Sorry, this isn't about Assad supposedly agreeing to turn over his stockpile of chemical weapons or about progress containing California fires—great though those things are. And I am grateful that violence and devastation are averted or reduced or at least not increased.

No, what I want to tell you about is just stuff about my life. Starting with about a week ago, when I had a wonderful time interacting with our fellowship group about Jesus in Matthew 8–9. What hit me then, and I am still processing today, is the question: "What is my life about?" Because it's so different from the life of Jesus.

Looking at Jesus in those two particular chapters (which happened to be my teaching assignment) made me see that his life was all about bringing grace and truth into the world.

Grace and truth—an important combination. Earlier in the week, when talking with the lovely Carol about this, I said that Jesus was all about bringing salvation—salvation in the broad sense: freedom from debilitating illness, illusions, alienation &c.—but perhaps “grace and truth” is a better phrase.

My life on the other hand is a lot about staying out of trouble. Yes, I want to do good, and sometimes I actually do. (And now that I think of it, I actually have a lot of joy at times.) But am I all about bringing grace and truth into the world? Healing and mercy? Not so much.

So yes, last Sunday's class was a lot of fun for me. I even wrote a paper (I spent about 5 minutes of class time introducing it) on an engineering/inductive approach to faith and miracles. I'll send you softcopy if you're interested; let me know.

More joy came in the form of writing code. Some of that is for work (I could show it to you, but then my boss would have to kill me) but more of it had to do with a couple of little projects. One was related to "kenken" puzzles. I wrote a solver on last month's vacation, but the input was kinda unfriendly. I mean it looked like this:

    11+ 1A 2A
    2/ 1b 1c
    3- 2b 2c
    20* 1d 2d
    6* 1e 1f 2f 3f
    3/ 2e 3e
    6x 3c 3d
    240* 3a 3b 4a 4b
    …remainder elided
What I did this week was change the front-end to accept input that looks more like this:
11+    2/    <    20*   6*   <
^      3-    <    ^     3/   ^
240*   <     6x   <     ^    ^
>      ^     …remainder elided
So now the kenken solver takes friendlier (or at least not-so-cryptic) input. Am I getting my inner nerd on or what?

My old friend Jan (she's younger than I am; what I mean is we met in 1968, before either of us met Jesus) was in the area, and we had breakfast Friday morning. I heard about some of the joys and disappointments in her family, some close calls and deliverance. Jan exemplifies Colossians 4:6 for me: Let your speech always be with grace, seasoned with salt…, as well as those verses in Psalms that encourage us to tell of [God's] works (see for example Psalm 73:28).

Another cause for celebration: my keys, which had been missing for the past week, were discovered Friday. They once were lost, but now are found. Happy day!

And yesterday, I worked with my daughter Sheri on her new website. Javascript was involved. So was coffee. As of about 9 this morning, it worked. I discovered happily that network solutions have Python 2.6; they do support cgi-bin. The layout is a big quirky, though; if you create a directory "icons" under htdocs, you can't ever get to it, because "/icons" redirects to some magic place where they put their icons. Arrows and dots and such. My icons, which were a few arrows and dots I created with convert(1), were nowhere to be found. So of course I renamed the directory my_icons. Hurmpf.

But it works! The challenge now is to make it easy to update and maintain.

Church was lovely this morning; besides the amusing quote If you have to ask whether you're young or old, you're old, the message was powerful: the importance of community. I heard something recently on NPR about how with economic prosperity comes a breakdown of community. Exactly how does this happen? I'll tell you. I don't know. The basic idea described on the radio was the idea that in poor villages, people have to rely on each other a lot more. But as prosperity comes, people become less reliant on each other; they're more "self-sufficient" (or so they/we tend to think) and therefore become more isolated. Dumb, huh? Yet I resemble that remark. I need to ponder that one too.

The lovely Carol and I went for a walk at Pulgas Ridge this afternoon. Our faithful pup also enjoyed the walk, though it was rather warm. It was great being outdoors and moving our bodies around.

Dinner tonight was fried rice—many leftover veggies make for an interesting dish. The lovely Carol offered to do the dishes since I'd done the cooking.

So I am thankful today. Sometimes I wonder how it was that I got to live in such a time and place as this, to have only minor problems in life, and so on. Not that I'm complaining. Praise God from whom all blessings flow! Amen.

Friday, August 16, 2013

Buying a lei in Kailua (as in Kaneohe, not as in Kona)

Aren't you glad I didn't title this "getting lei'd in Kailua"?

Suppose you're a young newlywed and you want to buy a lei for your honey, and you're staying on the windward side of Oahu. You type "lei" into Google maps and you notice this entry.

Well, it doesn't work. First, 776 Kailua Road isn't where google maps says it is. Second, 776 Kailua Road actually has a surfboard shop and an Arthur Murray studio. So what's a young lei-seeker to do?

Well, it turns out that Walgreen's has leis. At least an orchid lei (a "vanda lei" as I would call it). The folks at the Portuguese Bake Shop told me that Foodland, 7-eleven, and Long's (yes, it's still called Long's here in the islands) all have leis.

But not Lavender Lei, and not at 776 Kailua Road.

Tuesday, August 13, 2013

A Praying Life

The younger ex-teen got this book, A Praying Life by Paul E. Miller (NavPress, 2009). My first comment: Wow!

Chapter 1 is titled “What good does it do?”—something we've asked in the quiet (or noise) of our hearts, but the author recounts a time when he said to his then-teenager, “Let’s pray” (about a missing contact lens). In response she

… burst into tears. “What good does it do? I’ve prayed for Kim to speak, and she isn’t speaking”

Kim struggles with autism and developmental delay. …[S]he is also mute.

page 15
Indeed. What good does prayer do? Doesn't God already know what we need before we ask?

The author does address that question, and many others. I'll tell you about this other part from chapter 4, “Learn to talk to your father”—related to Matthew 7:11 If you, then, though you are evil, know how to give good gifts to your children, how much more will your Father in heaven give good gifts to those who ask him!

Kim got her first speech computer when she was five years old. … We explained the keys to her and waited. She leaned over and pressed the key with little McDonald’s golden arches on it. It was two o’clock in the afternoon, and we’d just eaten lunch. We dropped everything, leapt into the car with Kim raced off to McDonald’s, and got Kim a hamburger and a soda. We were thrilled.
page 58
What a lovely picture of our heavenly father's delight in giving good gifts to his children!

Another thing that's impressed me so far: when Jesus says we need to become like little children, we also need to pray like little children. Quiz: How do little children ask for things?

Well, messy might be a good word. They ask distractedly, repeatedly, annoyingly; they ask with abandon, without pretense or consideration. I used to hang around with a guy who said his default method of praying involved listing a dozen or two things he wanted from God. And he talked about that as though it were a bad thing.

But really, if that's on our minds, that's what we should talk about. I'll chew on that one for a while.

Tuesday, July 02, 2013

Why fast?

Our self-denial is sterile and absurd if we practice it for the wrong reasons or, worse still, without any valid reason at all.
Merton, No Man Is an Island 6.7 (p. 101)
So what's a reason that's not wrong? What's a valid reason for fasting?

For a while I had a habit of fasting on Sundays: I'd skip lunch. But why did I do it? Well, I didn't have a valid reason, and I stopped after a while.

So what is a valid reason? Merton helps us again here:

… The perfection of Christian renunciation is the total offering of ourselves to God in union with the sacrifice of Christ.

To offer this sacrifice perfectly we must practice asceticism, without which we cannot gain enough control over our hearts and their passions to reach such a degree of indifference to life and death.

Merton, op. cit., p. 102
So that's the point: to gain control over our hearts and their passions, so that we can become indifferent to food or whatever we're attracted (or addicted) to, so that we can get closer to the ideal: to offer our whole selves to God, to be indifferent to everything except the will of God.

And that's a valid reason: to be free of our own appetites and passions, not just regarding food, but anything that one might be (or become) addicted to. As Paul writes to married couples in 1 Corinthians 7.5, which I had not understood before: Do not deprive each other except by mutual consent and for a time, so that you may devote yourselves to prayer.

The point, of course, isn't that husbands and wives spend so much time in bed that they have no time to pray; rather, it's that by abstaining voluntarily for a (brief) time they can surrender more of themselves to God.

For a brief time, that is.

Let's see if I can free up 11GB (or: finding and taking care of duplicate files on a mac mini)

The lovely Carol has a mac mini, which currently serves[sic] as an NFS server for my desktop. It also backs up our laptops. This machine, which is backed up off-site, has a huge hard drive that I thought would keep us in disk space for a long long time.

You can guess what happened: photos and music—especially photos—tend to expand to fill the space available. It doesn't help that we have multiple copies of stuff. So I thought to run some Perl or Python script to help me find said copies.

Since I've become a Python partisan I went that route. A web search turned up some helpful hints on stackoverflow and particularly this post on endlesslycurious.com. The mac mini has python2.6, so I made a few modifications; you can see the whole thing at http://cpwriter.net/dup2/.

I ran that on /Users on the lovely Carol's mac mini, putting the results into dups.out.

mini1:~ collin$ wc -l dups.out
   12489 dups.out
mini1:~ collin$ 
Yep, that's a lot of files. A couple of big offenders:
mini1:~ collin$ grep Best.*Wedding dups.out
[621850501, ['/Users/carol/from-macbook/Movies/Best of Wedding.mov', \
   '/Users/collin/from-pbook/Desktop/Best of Wedding.mov']]
[989954048, ['/Users/carol/from-macbook/Desktop/Redeemer Marriage Series/Best of Wedding-DVD.img', \
   '/Users/collin/from-pbook/Desktop/Best of Wedding-DVD.img']]
mini1:~ collin$ 
That's 621Mbytes and 989Mbytes. So about 1.5GB freed up just like that. But I think we have a lot more. I discovered a lot of files under "archives" and "from-pbook" that are the same, like this:
mini1:~ collin$ grep archives dups.out|grep -m5 from-pbook
[1049177, ['/Users/collin/archives/collin-laptop/Pictures/iPhoto Library/2009/01/01/IMG_3001.JPG', \
   '/Users/collin/archives/data1/pix-dec08/img_3001.jpg', \
   '/Users/collin/from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_3001.JPG', 
   '/Users/collin/pix/2008/12/pix-dec08/img_3001.jpg']]
…
Wow, four paths to the same file. Hey, can I get rid of all those pix-dec08 paths? Yes, because:
  1. A "diff -r archives/data1/pix-dec08 pix/2008/12/pix-dec08" showed that these two directories are identical;
  2. every "large" (not a thumbnail or slide) image file under pix/2008/12/pix-dec08/ appeared in dups.out. Except those under 1024×1024 bytes:
    mini1:~ collin$ for F in pix/2008/12/pix-dec08/*jpg; do if grep -qF $F dups.out; then : OK; else ls -l $F; fi; done       
    -rwxr-xr-x  1 collin  _lpoperator  1002328 Dec 31  2008 pix/2008/12/pix-dec08/img_2961.jpg
    -rwxr-xr-x  1 collin  _lpoperator  858104 Jan  1  2009 pix/2008/12/pix-dec08/img_2988.jpg
    -rwxr-xr-x  1 collin  _lpoperator  863361 Jan  1  2009 pix/2008/12/pix-dec08/img_2994.jpg
    -rwxr-xr-x  1 collin  _lpoperator  865777 Jan  1  2009 pix/2008/12/pix-dec08/img_2995.jpg
    -rwxr-xr-x  1 collin  _lpoperator  994298 Jan  1  2009 pix/2008/12/pix-dec08/img_2996.jpg
    -rwxr-xr-x  1 collin  _lpoperator  811491 Jan  1  2009 pix/2008/12/pix-dec08/img_2997.jpg
    mini1:~ collin$
I'm going to take the leap of faith that the remaining files are in fact there in the other paths... well, no I won't:
mini1:~ collin$ for F in pix/2008/12/pix-dec08/*jpg; do if grep -qF $F dups.out; then : OK; else \
     Y=`basename $F|tr [:lower:] [:upper:]`; \
     Z=`/bin/ls from-pbook/Pictures/iPhoto\ Library/200*/*/*/$Y`; \
     echo $Z;cmp "$F" "$Z"; echo; fi; done                                                          
from-pbook/Pictures/iPhoto Library/2008/12/31/IMG_2961.JPG

from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_2988.JPG

from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_2994.JPG

from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_2995.JPG

from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_2996.JPG

from-pbook/Pictures/iPhoto Library/2009/01/01/IMG_2997.JPG

mini1:~ collin$ 
So we can kill off those two paths. That might have saved another Gbyte or so.

Now, can we maybe hardlink the /Users/collin/archives/collin-laptop/Pictures/ stuff to/from the /Users/collin/from-pbook/Pictures/ stuff? And how much space might that save?

mini1:~ collin$ du -sh archives/collin-laptop/Pictures/iPhoto\ Library/ from-pbook/Pictures/iPhoto\ Library/                                    
 12G archives/collin-laptop/Pictures/iPhoto Library/
 11G from-pbook/Pictures/iPhoto Library/
mini1:~ collin$ 
Quite a bit. That plus the 1.5GB already saved earlier would be a significant help here:
collin@p3:/mnt/home/collin> df -h .
Filesystem            Size  Used Avail Use% Mounted on
mini1:/Users          298G  257G   42G  87% /mnt/home
collin@p3:/mnt/home/collin> ssh mini1 df -h .
Filesystem     Size   Used  Avail Capacity  Mounted on
/dev/disk0s2  298Gi  256Gi   41Gi    87%    /
collin@p3:/mnt/home/collin> 
Not sure why the difference, but there it is. Anyway, I wanted to hardlink one set of files to the other. (Why? Because the from-pbook directory may get rsync'd. If I delete the from-pbook directory, then it may come back later. And if I delete the other directory, and subsequently decide to remove the files from the pbook, then we'll lose the photos. So hardlink is the way to go.) Consequently I wrote this silly script:
collin@p3:/mnt/home/collin> cat tmp/photos.sh 
#!/bin/sh
D2="archives/collin-laptop/Pictures/iPhoto Library"
D1="from-pbook/Pictures/iPhoto Library"

find "$D1" -type f | while read AFILE; do
    SUB=${AFILE#$D1/}
    #echo SUB=$SUB
    BFILE=$D2/$SUB 
    if [[ -s $BFILE ]] && [[ ! "$AFILE" -ef "$BFILE" ]] && cmp -s "$AFILE" "$BFILE"; then 
        if [[ $AFILE -ot $BFILE ]] ; then
     echo ln -f "'$AFILE'" "'$BFILE'"
 else
     echo ln -f "'$BFILE'" "'$AFILE'"
 fi
    fi
done
collin@p3:/mnt/home/collin> time tmp/photos.sh > foo.out

real 41m18.878s
user 0m11.009s
sys 0m33.146s
collin@p3:/mnt/home/collin> 
then ran it as you see above. A quick sanity check of "foo.out" looked reasonable. Ah, I probably should have run it on mini1, rather than on the NFS client. And the same here:
collin@p3:/mnt/home/collin> df -h .; ./foo.out; df -h .
Filesystem            Size  Used Avail Use% Mounted on
mini1:/Users          298G  257G   42G  87% /mnt/home
-bash: ./foo.out: Permission denied   # D'oh! I didn't say "chmod +x"; well, let me fix it the easy way...
Filesystem            Size  Used Avail Use% Mounted on
mini1:/Users          298G  257G   42G  87% /mnt/home
collin@p3:/mnt/home/collin> df -h .; sh ./foo.out; df -h .
Filesystem            Size  Used Avail Use% Mounted on
mini1:/Users          298G  257G   42G  87% /mnt/home
Filesystem            Size  Used Avail Use% Mounted on
mini1:/Users          298G  246G   53G  83% /mnt/home
collin@p3:/mnt/home/collin> 
OK, that's enough for now.

Wednesday, June 26, 2013

A gift at the supermarket

The other day I received a gift while shopping at our neighborhood grocery store.

It wasn't a gift from Key Market; it was a gift from God. Here's what it was: a sense of wonder and gratitude. I was heading over to the baking aisle, and I thought of how wonderful it was to be in a land of plenty, where flour and sugar and salt and baking powder and dozens of herbs and spices, are all on the shelf. And not just flour, but bags of the stuff in various sizes, "all-purpose" flour and bread flour and cake flour, whole wheat flour and organic flour and unbleached flour. And salt—which used to be so valuable that people were paid in it (thus our word salary)—plain salt and iodized salt, sea salt and kosher salt and ice cream salt, from various makers and in various sizes.

And milk! Whole milk and cream and half-and-half and reduced fat and nonfat and organic, soy "milk" in various flavors, almond "milk" and Mocha Mix® all right there with "sell by" dates stamped on them. Fresh eggs are nearby, too—white eggs and brown eggs by the dozen, organic and low-cholesterol eggs, eggs laid by cage-free hens. The grocery store is a marvel, and even if not everything is "health food," the store has a variety of food and drink (and housewares and personal-care products, not to mention pet food and stationery) not imaginable to people a century ago (or even a few decades), or to many people today born in, say, Mali.

Which reminds me of a time when Solomon "made silver and gold as common in Jerusalem as stones" (2 Chronicles 1:15). Solomon may have been richer than anyone else in history, but a middle-class resident of Jerusalem could not have imagined what you and I can see at the neighborhood market.

But here in the United States, and in particular in Silicon Valley, we do live in a sort of Solomon's Jerusalem. The wealth and consumption (and destruction) available to some folks at a whim—astonishing. And even those of us in the "middle-class" truly are rich, as in the richest 1% or 2% of people on the planet.

So that was my gift: a reminder that by any sane measure, I'm rich and fortunate beyond what anyone has a right to deserve. And so, for some time anyway, I complained a bit less than I usually do, I experienced a bit more joy than I usually do. And I hope I also spread a bit more joy, a bit less frustration and consternation than usual.

But this morning I found things to grumble to God about. Wretched man that I am! Thanks be to God that there is no condemnation for us in Christ. And that he will, according to his promise, sanctify us (1 Thessalonians 5:23f) and complete the work he began.

Thursday, June 20, 2013

But wait, Spot-It! has only 55 cards.

In a previous post and its follow-up I described the process by which I decided that Spot-It! likely used 57 symbols and that it could have up to 57 cards in its deck. But as mentioned earlier, the deck actually has only 55 cards. Did they just leave two cards out? Do they have more symbols, or fewer?

Yes, they just left two cards out; no, they use exactly 57 symbols. To figure this out, I created a file "cards.txt", with the name of each symbol on a single line. The symbols on a given card are grouped together in an eight-line stanza; an empty line separates stanzas.

yin-yang
paint
tree
lightning
zebra
clef
ladybug
Canada

bomb
dragon
carrot
paint
ghost
question
bang
clown

spider
…etc.
The excerpt at right signifies that:
  • one card contains the yin/yang sign, the paint dots, tree, lightning bolt, zebra, (treble) clef, ladybug, maple leaf;
  • another card has the bomb, dragon, carrot, paint, ghost, question mark, exclamation point…
you get the idea.

Alert readers may notice that "cards.txt" has names consisting of only one "word" (by which I mean a contiguous sequence of non-whitespace characters) each, and that some of them don't exctly match my summary. The one-"word" thing makes them easier for shell scripts to process, and I translate these internal names into less-cryptic (I hope) things like "maple leaf" (Canada) or "exclamation point" (bang).

The format of "cards.txt" made for easy error checking, which as it turned out was necessary. (If you're thinking at this point, "How anal!" all I can say is "guilty as charged, yer honor.") It also made for some easy analysis. For example, we learn that although most of the symbols appear on 8 cards each, some don't:

% grep -v '^$' cards.txt | sort | uniq -c | sort -n | head -n16
   6 snowman
   7 Canada
   7 bang
   7 cactus
   7 daisy
   7 dinosaur
   7 dog
   7 eye
   7 ice
   7 ladybug
   7 light-bulb
   7 man
   7 question
   7 skull
   7 stop
   8 anchor
% grep -v '^$' cards.txt | sort | uniq -c | sort -n | tail -n3
   8 web
   8 yin-yang
   8 zebra
% 
The (American) English translation of the above is: only six cards have a snowman. Fourteen symbols (maple leaf, exclamation point, cactus, daisy, etc.) appear on only 7 cards each; the rest appear on 8 cards each. This suggests that two cards could be added to the deck. Each card would have a snowman and 7 of the other symbols. Which symbols? Well, any symbol that would appear with the maple leaf (aka "Canada") on a new card must not already appear with the maple leaf on another card. How to decide that?

It takes a few steps. First, a shell script converts "cards.txt" into another file, "cards-sorted.txt":

% L1=1; while [[ $L1 -lt 490 ]] ; do 
    ((L2=L1+7)); sed -n $L1,${L2}p cards.txt | sort | fmt -w222; 
    ((L1=L1+9)); done | 
    sort > cards-sorted.txt
% 
This takes, e.g., lines 1-8 of "cards.txt", sorts them, and combines them into a single line. Then it takes lines 10-17 (that's eight lines) and does the same with them. Then lines 19-26, and so on.

Each line in "cards-sorted.txt" thus corresponds to a single card, and contains the card's one-word symbol names in alphabetical order. Consequently, cards-sorted.txt starts off like this:

% head cards-sorted.txt 
Canada anchor carrot cheese clock knight stop web
Canada apple bang igloo moon scissors snowflake spider
Canada art balloon bomb drop fire lips skull
Canada bottle candle ghost light-bulb lock pencil sunglasses
Canada car cat clover clown dog ok sun
Canada clef ladybug lightning paint tree yin-yang zebra
Canada dolphin dragon eye hand heart key target
anchor apple art dinosaur dolphin ghost ladybug ok
anchor balloon clown lightning snowman spider sunglasses target
anchor bang car clef daisy key lips lock
% 
Now I'll use "cards-sorted.txt" to see which of those fourteen symbols appear together with "Canada" (maple leaf) on any card:
% f() { grep Canada.*$1 cards-sorted.txt; }
% C=; D=; grep -v '^$' cards.txt |sort | uniq -c | sort -n | grep 7 | 
    while read A B; do 
       echo === $B ===
       if f $B; then C="$C $B"; else D="$D $B"; fi 
       echo C=$C
       echo D=$D
    done
… [much output deleted]
C= bang dog eye ladybug light-bulb skull stop
D= Canada cactus daisy dinosaur ice man question
% 
The "C=" symbols already appear together with "Canada" (maple leaf), so we won't put them on the same card as a maple leaf again. Therefore, I think that the following cards, if added, would "complete" a set of 57 cards:
  • snowman, exclamation point, dog, eye, ladybug, light bulb, skull, STOP!
  • snowman, maple leaf, cactus, daisy, dinosaur, ice cube, man, question mark
I wasn't quite confident in this, so I wrote this brief shell script to check it.
% cat checkit.sh 
#!/bin/sh
# Ensure that for any pair of symbols within $C [or $D], no single card 
# contains both members of the pair.

C="bang dog eye ladybug light-bulb skull stop"
D="Canada cactus daisy dinosaur ice man question"

all_combos() {
    while [[ $# -ge 2 ]] ; do
        first=$1
        shift
        for it in $*; do
            echo checking $first $it ...
            if grep $first cards-sorted.txt | grep $it; then 
                echo ERROR $first $it 
            fi
        done
    done
}

all_combos $C
all_combos $D
% 
A visual inspection of the output revealed that we were indeed checking for "bang" and "dog" together, then "bang" and "eye"... and so on. It did all that without ever printing "ERROR", so I think the list is good.

That sure was fun! But let me check one more time. I'll add the above two cards to the list of sorted cards:

% diff cards-sorted.txt  cards-complete.txt 
55a56,57
> snowman bang dog eye ladybug light-bulb skull stop
> snowman Canada cactus daisy dinosaur ice man question
% 
Then let me ensure that given any pair of symbols on a given card, that that card is the only one containing that pair:
% cat check-complete.sh 
#!/bin/sh
# based on checkit.sh -- for every card in the 
# hypothetical "complete" Spot-It! deck (in "cards-complete.txt"),
# extract every pair of symbols. If both members of the pair appear
# on any other card in the complete deck, then print "ERROR"...
TEMPFILE=/tmp/spot-tmp.$$
all_combos() {
    while [[ $# -ge 2 ]] ; do
        first=$1
        shift
        for it in $*; do
            echo checking $first $it ...
            if grep " $first " $TEMPFILE | grep " $it "; then 
                echo ERROR $first $it 
            fi
        done
    done
}
C=cards-complete.txt 
cat $C | while read X; do
    grep -v "$X" $C | sed -e 's/^/ /' -e 's/$/ /' > $TEMPFILE
    all_combos $X
done
rm -f /tmp/spot-tmp*
% 
The above script, "check-complete.sh", completed without ever saying "ERROR".
% ./check-complete.sh > x
% grep -m5 ERROR x
ERROR Canada igloo
ERROR igloo question
ERROR igloo man
ERROR ice igloo
ERROR cactus igloo
% 
To make sure that it actually works, though, I changed the last card to say "igloo Canada…" rather than the correct "snowman Canada…". The script did in fact catch the error, as you can see at right.

Am I satisfied now? I refer you to the "anal" comment above. (In other words, No.) One more thing: the above check-complete.sh verified that we didn't have any PAIR of symbols in common between any pair of cards. What it didn't do is verify that there was in fact any symbol in common between any pair of cards. That's done by this script:

% cat overlaps.sh
#!/bin/sh
# Confirm that at least one symbol overlaps every card vs every other card.
TEMPFILE=/tmp/spot-tmp.$$

check() {
    # Given two cards ($1, $2), ensure exactly one word is in common.
    if [[ $# -ne 2 ]] ; then
        echo "check() called with wrong # of args"
        exit 1;
    fi
    ACARD="$1"
    BCARD="$2"
    NUM=0
    for asym in $ACARD; do
        for bsym in $BCARD; do
            if [[ $asym == $bsym ]] ; then
                ((NUM=NUM+1))
            fi
        done
    done
    if [[ $NUM -ne 1 ]] ; then
        echo ERROR: card1=$ACARD
        echo ERROR: card2=$BCARD
    fi
}

verify_one() {
    # Handle one card's syms (args) vs the rest of the deck ($TEMPFILE)
    ONE="$*"
    cat $TEMPFILE | while read IT; do
        echo checking $ONE ==vs== $IT
        check "$ONE" "$IT"
    done
}
C=cards-complete.txt 
cat $C | while read X; do
    grep -v "$X" $C | sed -e 's/^/ /' -e 's/$/ /' > $TEMPFILE
    verify_one $X
done
rm -f /tmp/spot-tmp*
% 
This completed without error, but just to make sure, I modified the last card to use nonexistent symbol "snowman2" and re-ran it, yielding:
% ./overlaps.sh > x; grep -m6 ERROR x
ERROR: card1=anchor balloon clown lightning snowman spider sunglasses target
ERROR: card2=snowman2 Canada cactus daisy dinosaur ice man question
ERROR: card1=apple bomb cat hand lock snowman tree web
ERROR: card2=snowman2 Canada cactus daisy dinosaur ice man question
ERROR: card1=art candle carrot key moon snowman sun yin-yang
ERROR: card2=snowman2 Canada cactus daisy dinosaur ice man question
% 
Of course, each pair of cards shown (there are lots more) should have the "snowman" in common. By tweaking the last card, I removed that sharing. So the script works, and the hypothetical deck is correct.