
The Command Line Murders - coolvoltage
https://github.com/veltman/clmystery
======
shabble
Good fun, I'm going to hope that using ipython still qualifies, and it made
things much easier by letting you define functions that join some ad-hoc
datasets to look for matches, or read particular files.

A similar approach to different puzzles by Peter Norvig[1]

Other useful (and perhaps less common) utilities I used were 'q'[2], and the
standard unix 'comm(1)'[3]

[1]
[http://nbviewer.jupyter.org/url/norvig.com/ipython/Fred%20Bu...](http://nbviewer.jupyter.org/url/norvig.com/ipython/Fred%20Buns.ipynb)

[http://norvig.com/sudoku.html](http://norvig.com/sudoku.html)

[2] [https://harelba.github.io/q/](https://harelba.github.io/q/) \- sqlite
text munging ended up being a bit too clunky though, and I couldn't
remember/fix the join syntax to make it worthwhile in teh end.

[3] $ comm -12 <(sort memberships/AAA) <(sort memberships/Delta_SkyMiles) >
aaa_delta_comm # intersect names in 2 files. Sadly can't handle more than 2
inputs directly though, and assumes pre-sorting.

~~~
d0m
I think the goal is teaching you unix text commands. So if you use Python it's
missing the point. Thoughts?

~~~
navls
Do what is fun, there is no point

------
Gorgor
Here is my solution for finding the tall males driving the blue honda with the
semi-known licencse plate. I found awk quite suitable.

    
    
      awk 'BEGIN {RS="\n\n"; FS="\n"} $1 ~ /L337.*9$/ && $2 ~ /Honda/ && $3 ~ /Blue/ && $5 ~ /6'\''/ {print $4}' vehicles | cut -d' ' -f2,3
    

Edit: And to find the intersection between the SkyMiles members and the
members of the Museum of Bash History, I used fgrep:

    
    
      grep -Fxf Delta_SkyMiles Museum_of_Bash_History

~~~
erostrate
Your first command uses 13 distinct non-alphanumeric characters: '{="\;}$~/.*&

It looks more like black magic than code to someone like me who doesn't know
awk. It reminds me of these APL/J/K solutions I see on projecteuler.net. I
imagine this is what happens when people completely ignore learning curve
steepness and optimize for maximum productivity. Very impressive!

~~~
derekp7
It would look better if it wasn't all on one line (could be just the way HN is
displaying it).

Essentially, awk is a C like syntax with a bit of built in parsing, and a
built in loop. It breaks up each input line into fields (by default using
whitespace as a delimiter). The fields are dereferenced using the '$'
character ($1, $2, etc).

Then, for each line of the input, the entire program gets run. The awk program
consists of a conditional (basically the body of an "if" statement -- the if
is implied). If that conditional is matched, the C-like program segment
following it (enclosed in curly braces) gets executed. And the contents of the
curly braces is basically interpreted/scripted C.

Two special conditions exist in awk -- BEGIN and END. They are evaluated (and
the contents of their matching curly braces are executed) before, and after
(respectively) any lines of input are read.

Hope that helps give you a start on awk -- it is a really powerful tool.

Edit: so as a quick walkthrough: Before any lines of the input text are read
in, RS and FS variables get set. Then, for each line where field 1 matches the
regular expression L337.*9$, and field 2 contains the word Honda, etc... it
prints (outputs) the contents of field 4 (print $4).

Ok, so in addition to normal C like syntax, this example contains regular
expression matching too. But other than that (and some other variables that
automatically get set, like NR for number of records, NF for number of fields
in the current line), most of awk programs look like C.

~~~
Gorgor
Thanks for explaining. awk is actually quite simple, once you get the hang of
it.

It’s all on one line, because I used it as “one” command in bash. After all,
this is the command line murders.

------
networked
That was fun.

Hint: The file "vehicles" will be easier to deal with using the standard POSIX
tools if you translate it into a line-oriented format. Here is a way to do it
(in rot13):

    
    
        <iruvpyrf ge '\a' '|' | frq -r 'f/Yvprafr/\aYvprafr/t' > iruvpyrf.ersbeznggrq
    

An alternative solution is to notice that "vehicles" is _almost_ a valid
Recutils [1] file. The fix is as simple as

    
    
        frq -r '1,4q;f/Yvprafr Cyngr/YvprafrCyngr:/' iruvpyrf > iruvpyrf.erp
    

You can query the result with `recsel`. (Admittedly, I didn't try this until
after solving the mystery.)

[1]
[https://www.gnu.org/software/recutils/#content](https://www.gnu.org/software/recutils/#content)

------
gergoerdi
`comm -1 -3`
([http://pubs.opengroup.org/onlinepubs/9699919799/utilities/co...](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/comm.html))
is practically made for quickly finding who is a member of all involved
groups.

~~~
bgilroy26
This is just like SQL joins!

~~~
greggyb
Nope, that would be join.

~~~
bgilroy26
Indeed you're right. Jonathan Leffler's comment on Stack Overflow discusses
the differences

\---------

There are a couple of differences between comm and join:

    
    
        comm compares whole lines; join compares fields within lines.
        comm prints whole lines; join can print selected parts of lines.
    

\---------

[http://stackoverflow.com/questions/7234028/bash-
difference-b...](http://stackoverflow.com/questions/7234028/bash-difference-
between-join-and-comm)

------
daurnimator
Great idea!

My path to solution:

Get a list of CLUEs:

    
    
       grep -C 3 CLUE crimescene
    

Get a list of the 3 possible suspects based on CLUEs:

    
    
       grep -f <(grep -f memberships/AAA  memberships/Delta_SkyMiles | grep -f memberships/Terminal_City_Library  | grep -f memberships/Museum_of_Bash_History) -C 3 vehicles  | grep -A 6 L337..9 | grep -B 1 -A 4 Honda 
    

Then read their interviews:

    
    
       grep <Owner> people 
       tail -n +<line> streets/<street> | head -n 1
       less interviews/interview-<interview number>
    

Take your guess :)

~~~
chkuendig
don't forget to check the interview of the witness, so you get the details of
the getaway car. (after checking the clues)

    
    
      grep <Witness> people
    

compare to

    
    
      grep "SEE INTERVIEW" streets/*
     
    

also, you can add

    
    
      grep -B2 -A3 Blue

based on that inverview to your second line. once you check the interviews of
the two suspects left, you won't have to guess anymore :)

~~~
daurnimator
I left out the grep by Blue because Teal is close to Blue.

------
thegenius2000
Great idea doesn't cut it -- this is freaking incredible.

------
fredley
This was great fun! However, it could be called grep murder, as that's the
only tool you really need!

------
gonyea
Murder She Grepped is the name I would've gone with.

------
noobermin
Is it considered cheating to use files to "save state"?

SPOILER:

One thing I noticed doing this was that most of the interviews that weren't
Alice in Wonderland snippets were less than 3 or 4 lines long. Rather than
typing in the same long filtering for loop again and again, I basically
outputting the list of "good interviews" to a file which was a `for i in $(cat
goodinterviews);...` away. I wasn't sure if using the disk qualifies as just
using the command line though...

Also, I think more or less (the commands) are cheating just as much as an
editor but many associate less as more of a "command line" tool, so the author
should specifically forbid it.

Also, grep -rn is a godsend in this case, might have made it too easy :) This
was quite fun!

------
deepnet
This was fun and I learned comm to intersect text files.

A nicely paced game with a very unusual and fun mechanic that is plausably a
part of a detective's skillset.

------
swirepe
I had fun with this, and even though I've been using the terminal for ages, I
still learned something new. Specifically

    
    
        $(command -v md5 || command -v md5sum)
    

Does anybody know anything like this, but for sql?

~~~
yes_or_gnome
Try this: $ type command command is a shell builtin

Respectfully-RTM (assuming bash): $ man bash

It's a big manpage, so i'll spare you.

    
    
           command [-pVv] command [arg ...]
                  Run  command  with args suppressing the normal shell function lookup. Only builtin commands or commands found in the PATH are executed.  If the -p
                  option is given, the search for command is performed using a default value for PATH that is guaranteed to find all of the standard utilities.   If
                  either  the  -V  or -v option is supplied, a description of command is printed.  The -v option causes a single word indicating the command or file
                  name used to invoke command to be displayed; the -V option produces a more verbose description.  If the -V or -v option is supplied, the exit sta-
                  tus  is  0 if command was found, and 1 if not.  If neither option is supplied and an error occurred or command cannot be found, the exit status is
                  127.  Otherwise, the exit status of the command builtin is the exit status of command.
    
    

'command -v something' just tries to run the 'something' command and fails if
it is not found. So, in this instance, try to run 'md5' then try to run
'md5sum'.

------
emeraldd
This little trick came in very handy

    
    
        cat <file1> <file2> ... | sort | uniq -c | sort -n
    

I have a variant of that which I use to count how often individual words
appear in a body of text:

    
    
        cat ${FILES}  | tr [:upper:] [:lower:] | sed -r 's/\t/ /g' | sed -r "s/'s//g" | sed -r 's/ /\n/g' | tr -d [:punct:] | sort | uniq -c | sort -n

------
xentronium
Wasn't it a bit too short though? I was kinda surprised when I found out I
have neither suspect in custody, nor any hard evidence except the one I
received initially.

------
chandraonline
This was fun. This was my path to the solution:

[http://pastebin.com/Px4cLTJV](http://pastebin.com/Px4cLTJV)

------
yogiHacks
props to the author! This is a great fun and educational game. I'd love to
find more like this. Anyone have any recommendations?

------
jmarc
CORRECT! GREAT WORK, GUMSHOE.

------
swirepe
First person to solve this on their phone wins.

------
yes_or_gnome
FYI. This whole comment is just spoilers.

I feel like a homer because I started with the second clue, then the first,
and, finally, the (scientifically-proven-unreliable) witness testimony.

Starting with Clue #2, get a list of everyone with the set of membership
cards. Do this by combining all the lists, sort them together, use uniq to
group them ' N FNAME LASTNAME', keep only those with N=4, cut off the cruft,
pull each individual's info from the people file, and keep only the males.

    
    
      cat mystery/memberships/{AAA,Delta_SkyMiles,Museum_of_Bash_History,\
      Terminal_City_Library} | sort | uniq -c | grep 4 | cut -c 6- \
      | xargs -I'{}' grep '{}' mystery/people | sed -ne '/\bM\b/p'
    

With this list of 13 names and addresses (from 5029), I tried to find each one
in the street files. HOWEVER, only four of them have existent streets.

    
    
      cat mystery/memberships/{AAA,Delta_SkyMiles,Museum_of_Bash_History,\
      Terminal_City_Library} | sort | uniq -c | grep 4 | cut -c 6- \
      | xargs -I'{}' grep '{}' mystery/people | sed -ne '/\bM\b/p' \
      | cut -f4 | cut -d, -f1 | tr ' ' '_' | xargs -I'{}' \
      ls mystery/streets/'{}' 2>/dev/null
    

They are Brian Boyer, Jeremy Bowers, Matt Waite, and Mike Bostock. Following
this flaw, I was able to get the answer with four interviews and without
consulting clues #1 nor #3.

If I were to pursue this train of thought (without the flaw), I would review
clue #1 to find suspects over 6' tall (from mystery/vehicles). The list of 13
becomes just six. The four suspects mentioned above and two others; 'Augustin
Lozano' and 'Nikolaus Milatz'.

Finally, I review Clue #3. I try the following commands because baristas are
TERRIBLE at getting people's name correct.

    
    
      grep Annabel mystery/people | sed -ne '/\bF\b'
    
      grep Anabel mystery/people | sed -ne '/\bF\b'
    

'Annabel' pulls up only two names, 'Anabel' pulls up four. 'Annabel Church'
ends up being the eyewitness. The crucial piece of info is the partial license
plate number.

    
    
      sed -ne '/L337..9/,+6p' mystery/vehicles
    

I'm not command-fu enough to write a better sed command so I do a manual
inspection. There are five cars that match the description of a 'Blue Honda';
six if you include 'Teal'. Mr. Bowers, one of my original four suspects, owns
a 'Blue Honda', and Mr. Bostock owns the 'Teal'.

Anyway. This is just to say that there's more than the prescribed way to solve
the game. You could, potentially, solve it with just three interviews
'Annabel', 'Bowers', and 'Bostock'. Or, like me, you could do things ass-
backwards.

------
mnw21cam
Wow. Lots of detail.

Zip? Seriously?

~~~
stryk
What's wrong with zip?

~~~
mnw21cam
Absolutely nothing. Just a little discordant to see grep et al being discussed
in a project packaged as a zip. I suppose people might want to play with this
in Windows, although to be honest if you have gone to the trouble to install
grep et al, you will have no trouble with tar either.

~~~
irishcoffee
I'm not usually one to post these, but I can't help it in this care:

[https://xkcd.com/1168/](https://xkcd.com/1168/)

~~~
teekert
tar zxvf! For tar.gz. Never, ever understood why the tar command can't just
read the extension and determine the required long list of options needed for
successful extraction. Whether I can remember tar xvjpf (for tar.bz2 files)
depends on many things, not sure which. To be honest, I like it when I hit a
.zip, I can just type "unzip x"...

Nice xkcd :)

~~~
pdkl95
All I ever need to remember:

    
    
        atool -x archive.{zip,jar,rar,cab,deb,rpm,tar,tar.{gz,Z,bz2,xz}}
    

(list of supported formats is _not_ complete)

With a simple alias, it isn't even necessary to remember the -x

    
    
        alias xx="atool -x"
    

This also protects you from badly made archives that explode hundreds of files
into the current directory. All decompression is done in a temporary subdir,
which is removed if there was only one file/dir at the top level.

For xkcd, see atool's --explain or --simulate options that show you the
generated tar/etc commands.

[http://www.nongnu.org/atool/](http://www.nongnu.org/atool/)

~~~
rcthompson
Atool already comes with appropriate aliases for all the common tasks:

    
    
        $ wajig list-files atool | grep bin/
        /usr/bin/atool
        /usr/bin/arepack
        /usr/bin/aunpack
        /usr/bin/apack
        /usr/bin/als
        /usr/bin/acat
        /usr/bin/adiff

