Quite often, I’ll find myself wanting to query and manipulate MySQL data entirely on the command line. I could be building up a pipeline or working on a task that I’m going to eventually automate but haven’t quite gotten to yet. Whenver I have to do something like that, I have a small pile of scripts I’ve written over time that help out:
skiphead
: Skip the first line of output, used to skip over headers in a query responseskipuntil
: Skip all lines until we see one matching a pattern, used to resume partial taskscommaify
: Take a list of single values on the command line and turn them into a comma separated list (for use inIN
clauses)csv2json
: a previously posted script for converting csv/tab delimited output to jsonjq
: not my script, but used to take the output of csv2json and query it further in ways that would be complicated to do with SQL
Admitedly, the first two of those are one liners and I could easily remember them, but the advantage of a single command that does it is tab completion. sk<tab>
, arrow to select which one I want, and off we go. I could put them as an alias, but I don’t always use the same shell (mostly fish, but sometimes Bash or Zsh).
skiphead
#!/bin/bash
awk 'NR>1'
First, skip the header. You can use this after making a MySQL query to skip over the header. Perhaps you want to combine it with commaify
(later):
$ echo 'select * from messages where user_id in (' $(echo 'select id from users where email like "%@example.com"' | mysql ... | skiphead | commaify) ')' | mysql... | ...
(I’m purposely leaving out the details of the mysql
command. I have a wrapper for that I use which I’ll post about at some point.)
This will get users matching an email address, skip over the included header, turn into a comma delimited list and pass into another query (perhaps in another database).
skipuntil
#!/bin/bash
sed -n "/$@/,\$p"
This will use a bit of sed
trickery (source) to skip lines until a line matches the given regular expression. If you have a long running process that is iterating over some sort of ID (which I often do) that crashes halfway through, you can run the next time with skipuntil {last good id}
to resume from where you left off.
If you wanted something a bit more Pythonic (and less opaquely depending on sed’s lovely syntax):
#!/usr/bin/env python3
import fileinput
import re
import sys
if len(sys.argv) <= 1:
print('Usage: skipuntil <regex> <files...>?')
exit(1)
matcher = re.compile(sys.argv[1])
matched = False
for line in fileinput.input(sys.argv[2:]):
if not matched and matcher.match(line):
matched = True
if matched:
sys.stdout.write(line)
commaify
#!/usr/bin/env python3
import fileinput
import re
values = []
for line in fileinput.input():
line = line.strip()
if line.isnumeric():
values.append(line)
else:
values.append('"{}"'.format(re.escape(line)))
print(','.join(values))
A powerful script that will take a list of items that come in newline delimted ids/numbers/strings and join them into an escaped and quoted (if necessary) list that can be passed to as a MySQL in
clause. I can’t guarantee that it’s 100% safe against untrusted inputs, but then again, what is?
csv2json
/ jq
As mentioned, this was a script I posted about a few years ago, but it’s still useful, particularly in combination with jq. You can combine data from multiple databases (which SQL generally won’t do) or local data or even process in ways that are hard to do with SQL alone. Pretty handy that.
Putting it all togther
You can do all sorts of interesting things with these commands all together:
# Take a CSV file containing multiple fields, extracxt the email column, get unique values, and turn those into user ids
$ echo 'select id from users where email in (' (cat some-user-data.csv | csv2json | jq '.email' | tr -d '"' | sort | uniq | commaify) ')' | mysql... | skiphead > user-ids
# Combine data from multiple sources on the local file system and build a query from it
$ echo '
select messages.text
from
users
join messages on (users.id = messages.user_id)
where
users.email in (' (cat emails | commaify) ')
and users.id not in (' (cat safe_ids | commaify) ')
' | mysql...
Pretty cool. Makes the Unix philosophy work for you:
Write programs that do one thing and do it well.
Write programs to work together.
Write programs to handle text streams, because that is a universal interface.
- Peter H. Salus in A Quarter-Century of Unix (1994)