Platinum Partner
server,devops,mysql,languages,tutorial,bash

Staying DRY with Bash Indirect References

I should start this post by saying I don’t recommend this method for all situations due to potential security issues, as well as some readability tradeoffs. If that didn’t scare you off, keep reading.

I was faced recently with a situation where I needed to update a MySQL clone script written in bash to pull data from multiple MySQL servers and write to a single server. We often do this to quickly copy data from environment to another for testing. The original script was written with the assumption of only one source server and looked roughly like this:

source_mysql_host="not.the.best.idea"
source_mysql_user="xxx"
source_mysql_passwd="yyy"
 
echo ; echo " ==>  Cloning databases. Source: $source_mysql_host, Destination: $dest_mysql_host"
 
DATABASES=`mysql -u${source_mysql_user} "-p${source_mysql_passwd}" -h${source_mysql_host} \
            -NBe 'show databases' | egrep -v '^information_schema|performance_schema|mysql|test|backups$'`
for db in $DATABASES; do
    echo "   ==> Cloning database '$db' .. "
 
    $mysqldump_cmd -u${source_mysql_user} "-p${source_mysql_passwd}" -h${source_mysql_host} --add-drop-database --database ${db} | \
    mysql -u${dest_mysql_user} "-p${dest_mysql_passwd}" -h${dest_mysql_host}
done

(The full block of code was 26 lines. Much of it has been left out since it is not relevant to this article)

In order to update this script to clone data from multiple servers we could simply duplicate the entire 26 line block of code and change the variables so that the first block uses $OLAP_mysql_host and the other uses $OLTP_mysql_host, and so on, but that would not be very DRY.

What I ended up doing was wrapping the block of code into a new for loop and using bash’s indirect references to switch between the source MySQL servers. Note the ugly bash indirect references. It works and we stay DRY. Was it worth it? Maybe, Maybe not. =)

OLTP_source_mysql_host="not.the.best.idea"
OLTP_source_mysql_user="xxx"
OLTP_source_mysql_passwd="yyy"
 
OLAP_source_mysql_host="not.the.best.idea.2"
OLAP_source_mysql_user="xxx"
OLAP_source_mysql_passwd="yyy"
 
for type in "OLTP" "OLAP"; do
 
    # use bash's indirect references. very ugly, but helps us stay DRY
    _mysql_source_host=$(eval "echo \$$(echo ${type}_source_mysql_host)")
    _mysql_source_user=$(eval "echo \$$(echo ${type}_source_mysql_user)")
    _mysql_source_passwd=$(eval "echo \$$(echo ${type}_source_mysql_passwd)")
    _schema_only_tables=$(eval "echo \$$(echo ${type}_SCHEMA_ONLY_TABLES)")
 
    echo ; echo " ==> Cloning ${type} databases. Source: $_mysql_source_host, Destination: $dest_mysql_host"
 
    # get a list of databases, excluding the mysql system db's (ie: mysql, test, ...)
    DATABASES=`mysql -u${_mysql_source_user} "-p${_mysql_source_passwd}" -h${_mysql_source_host} \
                -NBe 'show databases' | egrep -v '^information_schema|performance_schema|mysql|test|backups$'`
    for db in $DATABASES; do
        echo "   ==> Cloning database '$db' .. "
 
        $mysqldump_cmd -u${_mysql_source_user} "-p${_mysql_source_passwd}" -h${_mysql_source_host} --add-drop-database --database ${db} | \
        mysql -u${dest_mysql_user} "-p${dest_mysql_passwd}" -h${dest_mysql_host}
    done
done

Published at DZone with permission of {{ articles[0].authors[0].realName }}, DZone MVB. (source)

Opinions expressed by DZone contributors are their own.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}