2
\$\begingroup\$

I have written this shell script to backup MySQL database to disk. It works fine but I am not well proficient in shell scripting. So what do you think can it be improved?

#!/bin/bash

#For taking backup
APPEND_CLI=$3
DIR=/media/storage/backup/db_backup/
DATESTAMP=$(date +%d-%m-%y-%H-%M)
DB_USER=backup
DB_PORT=$2
DB_PASS='readonly'
HOST=$1

if [[ `id -u` != 0 ]]; then
    echo "Must be root to run script"
    exit
fi

if test -z "$HOST"
then
  echo "HOST not passed."
  exit 1
fi

if test -z "$DB_PORT"
then
  echo "PORT not passed."
  exit 1
fi

# remove backups older than $DAYS_KEEP
DAYS_KEEP=7
find ${DIR}* -mtime +$DAYS_KEEP -exec rm -f {} \; 2> /dev/null
# create backups securely
umask 006
# list MySQL databases and dump each
DB_LIST=`$APPEND_CLI mysql -h $HOST -u $DB_USER -p"$DB_PASS" -e'show databases;'`
echo "Listing databases"
echo ${DB_LIST##Database}

DB_LIST=${DB_LIST##Database}
for DB in $DB_LIST;
do
 if [[ "${DB}" == "performance_schema" ]]
 then
  echo "Skipping database ${DB}."
  continue
 fi
 FILENAME=${DIR}${DB}-${DATESTAMP}.msql.${DB_PORT}.gz
 echo "Initiating backup of $DB for ${HOST} on port ${DB_PORT}"
 $APPEND_CLI mysqldump -h $HOST -P $DB_PORT -u $DB_USER -p"$DB_PASS" $DB --single-transaction | gzip > $FILENAME
 echo "Done backing up ${FILENAME}"
done
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Please use lower-case for Bash variables - upper-case is normally used for environment variables that communicate with child processes.


if [[ `id -u` != 0 ]]; then
    echo "Must be root to run script"
    exit
fi

Error messages should go to the standard error stream (>&2). And I think we should be exiting with a failure status in this case (since we didn't perform the requested backup).

We can make life easier for ourselves with a short function to print an error message and exit with failure. That looks like

die() {
    echo >&2 "$@"
    exit 1
}

Then our three tests become simpler:

[ "$(id -u)" = 0 ] || die "Must be root to run script"
[ -n "$HOST" ] || die "HOST not passed."
[ -n "$DB_PORT" ] || die "PORT not passed."

Instead of the continue to skip over the performance_schema database, we could exclude it from our results:

mysql -e 'show databases where `Database` not like "performance_schema"'

And we can avoid the need to remove the Database column header by passing --skip-column-names.


Since we're using Bash, which has arrays, we should use an array for the list of databases, particularly in case they might contain spaces in their names.

We can use read -a to assign input lines to an array:

IFS=$'\n' read -d '' -r -a db_list \
  < <(mysql --batch --skip-column-names -e 'show databases')

Then we can loop over the names:

for db in "${db_list[@]}"
do
  filename=${dir}${db}-${datestamp}.msql.${db_port}.gz
  echo "Initiating backup of $db for ${host} on port ${db_port}"
  $append_cli mysqldump \
      -h "$host" -P "$db_port" -u "$db_user" -p"$db_pass" "$db"
      --single-transaction \
    | gzip >$filename
  echo "Done backing up ${filename}"
done
\$\endgroup\$
2
  • \$\begingroup\$ thanks I am wondering how can not delete the old back if its the last and only one in that backup directory. \$\endgroup\$
    – Chang Zhao
    Commented Feb 18, 2022 at 22:04
  • \$\begingroup\$ You might be better leaving that logic out of your script and using something external (logrotate) to deal with that for you. It's probably easier to configure that than to write and test your own version. I was going to mention that, but got distracted. \$\endgroup\$ Commented Feb 18, 2022 at 22:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.