Friday, March 10, 2017

RMAN Backup Shell Script for Oracle

This hassle free RMAN backup Shell script helps the DBA to take an on-demand self-contained & consistent RMAN backup for Oracle database resides on Linux OS. It will help to easily specify the number of channels, select backup options (Compression/Encryption) and then it will run in the background in nohup mode.

Script download:
https://www.dropbox.com/s/l3g17oxz6j56cdz/RMAN_full.sh?dl=0

How it works:

This script should run by the Oracle installation owner (e.g. oracle) it will do the following:
 - Will ask you to select the database number you want to backup from the list (in case multiple databases found).
 - Then it will ask for the backup location, and the number of channels to be allocated during the backup.
 - Then you will be asked to use special features such as Compression and Encryption for the backup.
 - Finally, RMAN script will be created and will run automatically in the background in nohup mode, and thus no need to bother about your session disconnect during the backup operation.


#!/bin/bash
# #####################################################################################################
# This script takes an on-demand RMAN Backup.
# [Ver 5.2]
# # # #
# Author: Mahmmoud ADEL # # # # ###
# # # # # #
# Created: 24-09-11
# Modified: 31-12-13 Customized the script to run on various environments.
# 12-03-16 Run RMAN command in the background to avoid job fail when session terminate.
# 23-08-16 Added Backup Encryption Option.
# 17-11-16 Added Channels Number feature.
# 22-01-18 Added Controlfile compressed backup option.
# 22-06-20 Restricting the user from skipping the backup location.
# 03-07-20 Changing Backup date format to DD-Mon-YY.
# 16-08-22 Check the DB Open mode and the ARCHIVELOG mode.
# 16-08 22 Check if the RECOVERY is running for a STANDBY DB to avoid inconsistency bug.
# 17-08-22 Include Parallelism option when starting the RECOVER on a STANDBY DB.
# 03-05-23 Include the controlfile autobackup within the backup set for easy restoration.
# 03-05-23 Enhanced the script readability.
# #####################################################################################################
# ###########
# Description:
# ###########
echo
echo "==================================================="
echo "This script Takes a RMAN FULL Backup of a database."
echo "==================================================="
echo
sleep 1
# ###########################
# CPU count check:
# ###########################
# Count of CPUs:
CPU_NUM=`cat /proc/cpuinfo|grep processor|wc -l`
export CPU_NUM
# #######################################
# Excluded INSTANCES:
# #######################################
# Here you can mention the instances the script will IGNORE and will NOT run against:
# Use pipe "|" as a separator between each instance name.
# e.g. Excluding: -MGMTDB, ASM instances:
EXL_DB="\-MGMTDB|ASM|APX" #Excluded INSTANCES [Will not get reported offline].
# ##############################
# SCRIPT ENGINE STARTS FROM HERE ............................................
# ##############################
# ###########################
# Listing Available Databases:
# ###########################
# Count Instance Numbers:
INS_COUNT=$( ps -ef|grep pmon|grep -v grep|egrep -v ${EXL_DB}|wc -l )
# Exit if No DBs are running:
if [ $INS_COUNT -eq 0 ]
then
echo "No Database is Running !"
echo
return
fi
# If there is ONLY one DB set it as default without prompt for selection:
if [ $INS_COUNT -eq 1 ]
then
export ORACLE_SID=$( ps -ef|grep pmon|grep -v grep|egrep -v ${EXL_DB}|awk '{print $NF}'|sed -e 's/ora_pmon_//g'|grep -v sed|grep -v "s///g" )
# If there is more than one DB ASK the user to select:
elif [ $INS_COUNT -gt 1 ]
then
echo
echo "Select the ORACLE_SID:[Enter the number]"
echo "---------------------"
select DB_ID in $( ps -ef|grep pmon|grep -v grep|egrep -v ${EXL_DB}|awk '{print $NF}'|sed -e 's/ora_pmon_//g'|grep -v sed|grep -v "s///g" )
do
integ='^[0-9]+$'
if ! [[ ${REPLY} =~ ${integ} ]] || [ ${REPLY} -gt ${INS_COUNT} ] || [ ${REPLY} -eq 0 ]
then
echo
echo "Error: Not a valid number!"
echo
echo "Enter a valid NUMBER from the displayed list !: i.e. Enter a number from [1 to ${INS_COUNT}]"
echo "----------------------------------------------"
else
export ORACLE_SID=$DB_ID
echo
printf "`echo "Selected Instance: ["` `echo -e "\033[33;5m${DB_ID}\033[0m"` `echo "]"`\n"
echo
break
fi
done
fi
# Exit if the user selected a Non Listed Number:
if [[ -z "${ORACLE_SID}" ]]
then
echo "You've Entered An INVALID ORACLE_SID"
exit
fi
# #########################
# Getting ORACLE_HOME
# #########################
ORA_USER=`ps -ef|grep ${ORACLE_SID}|grep pmon|grep -v grep|egrep -v ${EXL_DB}|grep -v "\-MGMTDB"|awk '{print $1}'|tail -1`
USR_ORA_HOME=`grep ${ORA_USER} /etc/passwd| cut -f6 -d ':'|tail -1`
# SETTING ORATAB:
if [[ -f /etc/oratab ]]
then
ORATAB=/etc/oratab
export ORATAB
## If OS is Solaris:
elif [[ -f /var/opt/oracle/oratab ]]
then
ORATAB=/var/opt/oracle/oratab
export ORATAB
fi
# ATTEMPT1: Get ORACLE_HOME using pwdx command:
export PGREP=`which pgrep`
export PWDX=`which pwdx`
if [[ -x ${PGREP} ]] && [[ -x ${PWDX} ]]
then
PMON_PID=`pgrep -lf _pmon_${ORACLE_SID}|awk '{print $1}'`
export PMON_PID
ORACLE_HOME=`pwdx ${PMON_PID} 2>/dev/null|awk '{print $NF}'|sed -e 's/\/dbs//g'`
export ORACLE_HOME
fi
# ATTEMPT2: If ORACLE_HOME not found get it from oratab file:
if [[ ! -f ${ORACLE_HOME}/bin/sqlplus ]]
then
## If OS is Linux:
if [[ -f /etc/oratab ]]
then
ORATAB=/etc/oratab
ORACLE_HOME=`grep -v '^\#' $ORATAB | grep -v '^$'| grep -i "^${ORACLE_SID}:" | perl -lpe'$_ = reverse' | cut -f3 | perl -lpe'$_ = reverse' |cut -f2 -d':'`
export ORACLE_HOME
## If OS is Solaris:
elif [[ -f /var/opt/oracle/oratab ]]
then
ORATAB=/var/opt/oracle/oratab
ORACLE_HOME=`grep -v '^\#' $ORATAB | grep -v '^$'| grep -i "^${ORACLE_SID}:" | perl -lpe'$_ = reverse' | cut -f3 | perl -lpe'$_ = reverse' |cut -f2 -d':'`
export ORACLE_HOME
fi
#echo "ORACLE_HOME from oratab is ${ORACLE_HOME}"
fi
# ATTEMPT3: If ORACLE_HOME is still not found, search for the environment variable: [Less accurate]
if [[ ! -f ${ORACLE_HOME}/bin/sqlplus ]]
then
ORACLE_HOME=`env|grep -i ORACLE_HOME|sed -e 's/ORACLE_HOME=//g'`
export ORACLE_HOME
#echo "ORACLE_HOME from environment is ${ORACLE_HOME}"
fi
# ATTEMPT4: If ORACLE_HOME is not found in the environment search user's profile: [Less accurate]
if [[ ! -f ${ORACLE_HOME}/bin/sqlplus ]]
then
ORACLE_HOME=`grep -h 'ORACLE_HOME=\/' $USR_ORA_HOME/.bash_profile $USR_ORA_HOME/.*profile | perl -lpe'$_ = reverse' |cut -f1 -d'=' | perl -lpe'$_ = reverse'|tail -1`
export ORACLE_HOME
#echo "ORACLE_HOME from User Profile is ${ORACLE_HOME}"
fi
# ATTEMPT5: If ORACLE_HOME is still not found, search for orapipe: [Least accurate]
if [[ ! -f ${ORACLE_HOME}/bin/sqlplus ]]
then
ORACLE_HOME=`locate -i orapipe|head -1|sed -e 's/\/bin\/orapipe//g'`
export ORACLE_HOME
#echo "ORACLE_HOME from orapipe search is ${ORACLE_HOME}"
fi
# TERMINATE: If all above attempts failed to get ORACLE_HOME location, EXIT the script:
if [[ ! -f ${ORACLE_HOME}/bin/sqlplus ]]
then
echo "Please export ORACLE_HOME variable in your .bash_profile file under oracle user home directory in order to get this script to run properly"
echo "e.g."
echo "export ORACLE_HOME=/u01/app/oracle/product/11.2.0/db_1"
exit
fi
export LD_LIBRARY_PATH=${ORACLE_HOME}/lib
# ########################################
# Exit if the user is not the Oracle Owner:
# ########################################
CURR_USER=`whoami`
if [ ${ORA_USER} != ${CURR_USER} ]; then
echo ""
echo "You're Running This Sctipt with User: \"${CURR_USER}\" !!!"
echo "Please Run This Script With The Right OS User: \"${ORA_USER}\""
echo "Script Terminated!"
exit
fi
# ###############################
# RMAN: Script Creation:
# ###############################
# Last RMAN Backup Info:
# #####################
export NLS_DATE_FORMAT='DD-Mon-YYYY HH24:MI:SS'
${ORACLE_HOME}/bin/sqlplus -s '/ as sysdba' << EOF
set linesize 170 pages 200
PROMPT
PROMPT LAST 14 DAYS RMAN BACKUP DETAILS:
PROMPT ---------------------------------
set linesize 160
set feedback off
col START_TIME for a15
col END_TIME for a15
col TIME_TAKEN_DISPLAY for a10
col INPUT_BYTES_DISPLAY heading "DATA SIZE" for a10
col OUTPUT_BYTES_DISPLAY heading "Backup Size" for a11
col OUTPUT_BYTES_PER_SEC_DISPLAY heading "Speed/s" for a10
col output_device_type heading "Device_TYPE" for a11
SELECT to_char (start_time,'DD-MON-YY HH24:MI') START_TIME, to_char(end_time,'DD-MON-YY HH24:MI') END_TIME, time_taken_display, status,
input_type, output_device_type,input_bytes_display, output_bytes_display, output_bytes_per_sec_display,COMPRESSION_RATIO COMPRESS_RATIO
FROM v\$rman_backup_job_details
WHERE end_time > sysdate -14;
EOF
# Variables:
export NLS_DATE_FORMAT="DD-MON-YY HH24:MI:SS"
# Check if the DB is in ARCHIVELOG mode:
# ######################################
OPENMODE_RAW=$(${ORACLE_HOME}/bin/sqlplus -S "/ as sysdba" << EOF
set pages 0 feedback off;
prompt
select STATUS from v\$instance;
exit;
EOF
)
OPENMODE=`echo ${OPENMODE_RAW}| awk '{print $NF}'`
#echo OPENMODE is $OPENMODE
case ${OPENMODE} in
STARTED)
echo
echo -e "\033[32;5mThe Instance is in NOMOUNT mode.\033[0m"
echo
echo "Please start the instance in MOUNT or OPEN mode."
echo
exit
;;
OPEN)
ARCHIVEMODE_RAW=$(${ORACLE_HOME}/bin/sqlplus -S "/ as sysdba" << EOF
set pages 0 feedback off;
prompt
select count(*) from v\$database where LOG_MODE='ARCHIVELOG';
exit;
EOF
)
ARCHIVEMODE=`echo ${ARCHIVEMODE_RAW}| awk '{print $NF}'`
export PLUS_ARCHIVELOG="PLUS ARCHIVELOG"
#echo ARCHIVEMODE is $ARCHIVEMODE
if [ ${ARCHIVEMODE} -eq 0 ]
then
echo
echo -e "\033[32;5mThe DATABASE is NOT in ARCHIVELOG mode.\033[0m"
echo
echo "Either bring the instance in the MOUNT mode, or ENABLE the ARCHIVELOG mode."
echo
exit
fi
;;
MOUNTED)
export PLUS_ARCHIVELOG=""
;;
esac
# Check if CONTORLFILE AUTO BACKUP is SET:
# ########################################
CONTROLFILEAUTORAW=$(${ORACLE_HOME}/bin/sqlplus -S "/ as sysdba" << EOF
set pages 0 feedback off;
prompt
select count(*) from v\$rman_configuration where NAME='CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE' and value like 'DISK TO%';
exit;
EOF
)
CONTROLFILEAUTO=`echo ${CONTROLFILEAUTORAW}| awk '{print $NF}'`
# If CONTORLFILE AUTO BACKUP is SET then save the current value to set it back after the completion of the backup:
if [[ ${CONTROLFILEAUTO} -eq 1 ]]
then
OLDCONTROLFILEBKPVALRAW=$(${ORACLE_HOME}/bin/sqlplus -S "/ as sysdba" << EOF
set pages 0 feedback off;
prompt
select value from v\$rman_configuration where NAME='CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE' and value like 'DISK TO%';
exit;
EOF
)
export SETOLDCONTROLFILEBKPVAL="CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE ${OLDCONTROLFILEBKPVALRAW}"
fi
# Building the RMAN BACKUP Script:
# ################################
# Prompt the user for the Backup location:
echo
echo "Enter the BACKUP Location: [e.g. /backup/RMAN]"
echo "========================="
while read BKPLOCX
do
case ${BKPLOCX} in
'') export BKPLOCX=`pwd`; echo; echo "DIRECTORY TRANSLATED TO: ${BKPLOCX}";;
'.') export BKPLOCX=`pwd`; echo; echo "DIRECTORY TRANSLATED TO: ${BKPLOCX}";;
'~') export BKPLOCX=${HOME}; echo; echo "DIRECTORY TRANSLATED TO: ${BKPLOCX}";;
esac
if [[ -d "${BKPLOCX}" ]] && [[ -r "${BKPLOCX}" ]] && [[ -w "${BKPLOCX}" ]]
then
# Create the Backup directory:
export BKPLOC=${BKPLOCX}/RMANBKP_${ORACLE_SID}/`date +%d-%b-%y`
/bin/mkdir -p ${BKPLOC}
echo
echo "RMAN Backup will be saved under: ${BKPLOC}"; break
else
echo; printf "`echo "Please make sure that oracle user has"` `echo -e "\033[33;5mREAD/WRITE\033[0m"` `echo "permissions on the provided directory."`\n"; echo;
echo "Enter the complete PATH where the RMAN Backup will be saved: [e.g. /backup/RMAN]"
echo "-----------------------------------------------------------"
fi
done
# Exit if the user press Ctrl+D:
if [[ ! -w "${BKPLOC}" ]]; then
exit
fi
# Check if the recover process is active on a STANDBY DB:
RECNUM=`ps -ef|grep mrp0_${ORACLE_SID}|grep -v grep|wc -l`
if [ ${RECNUM} -gt 0 ]
then
echo
echo -e "\033[32;5mDetected an Active RECOVERY against the DB\033[0m"
echo
echo "Do you want to PAUSE the RECOVERY during the RMAN backup and RESUME it back after the completion of the backup to maintain BACKUP CONSISTENCY? [Y|N] [N]"
echo "=============================================================================================================================================="
echo "Note: The Standby DB will not be in-sync during the backup if the RECOVERY paused but will be RESUMED after the completion of the backup."
echo ""
while read CANCEL_RECOVERY
do
case ${CANCEL_RECOVERY} in
y|Y|yes|YES|Yes)
echo
echo "RECOVERY will be CANCELED during the backup and will be RESUMED back after it gets complete."
echo
RECOVERY_STOP="sql \"alter database RECOVER MANAGED STANDBY DATABASE CANCEL\";"
RECOVERY_START="sql \"alter database RECOVER MANAGED STANDBY DATABASE USING CURRENT LOGFILE NODELAY PARALLEL ${CPU_NUM} DISCONNECT\";"
export RECOVERY_STOP
export RECOVERY_START
break ;;
""|n|N|no|NO|No) RECOVERY_STOP="";RECOVERY_START="";break ;;
*) echo "Please enter a VALID answer: [Y|N]"
echo "----------------------------";;
esac
done
fi
# Prompt the user for the number of channels:
echo
echo "How many CHANNELS do you want to allocate for this backup? [${CPU_NUM} CPUs Available On This Machine]"
echo "========================================================="
while read CHANNEL_NUM
do
integ='^[0-9]+$'
if ! [[ ${CHANNEL_NUM} =~ $integ ]] ; then
echo "Error: Not a valid number !"
echo
echo "Please Enter a VALID NUMBER:"
echo "---------------------------"
else
break
fi
done
echo
echo "Number Of Channels is: ${CHANNEL_NUM}"
echo
echo "---------------------------------------------"
echo "COMPRESSED BACKUP will allocate SMALLER space"
echo "but it's a bit SLOWER than REGULAR BACKUP."
echo "---------------------------------------------"
echo
echo "Do you want a COMPRESSED BACKUP? [Y|N]: [Y]"
echo "================================"
while read COMPRESSED
do
case $COMPRESSED in
""|y|Y|yes|YES|Yes) COMPRESSED=" AS COMPRESSED BACKUPSET "; echo "COMPRESSED BACKUP ENABLED.";break ;;
n|N|no|NO|No) COMPRESSED="";break ;;
*) echo "Please enter a VALID answer [Y|N]" ;;
esac
done
echo
echo "Do you want to ENCRYPT the BACKUP by Password? [Available in Enterprise Edition only] [Y|N]: [N]"
echo "=============================================="
while read ENCR_BY_PASS_ANS
do
case ${ENCR_BY_PASS_ANS} in
y|Y|yes|YES|Yes)
echo
echo "Please Enter the password that will be used to Encrypt the backup:"
echo "-----------------------------------------------------------------"
read ENCR_PASS
ENCR_BY_PASS="SET ENCRYPTION ON IDENTIFIED BY '${ENCR_PASS}' ONLY;"
export ENCR_BY_PASS
echo
echo "BACKUP ENCRYPTION ENABLED."
echo
echo "Later, To RESTORE this backup please use the following command to DECRYPT it, placing it just before the RESTORE Command:"
echo " e.g."
echo " SET DECRYPTION IDENTIFIED BY '${ENCR_PASS}';"
echo " restore database ...."
echo
break ;;
""|n|N|no|NO|No) ENCR_BY_PASS="";break ;;
*) echo "Please enter a VALID answer [Y|N]" ;;
esac
done
RMANSCRIPT=${BKPLOC}/RMAN_FULL_${ORACLE_SID}.rman
RMANSCRIPTRUNNER=${BKPLOC}/RMAN_FULL_nohup.sh
RMANLOG=${BKPLOC}/rmanlog.`date +%d-%b-%y`.log
echo "${ENCR_BY_PASS}" > ${RMANSCRIPT}
echo "run {" >> ${RMANSCRIPT}
CN=1
while [[ ${CN} -le ${CHANNEL_NUM} ]]
do
echo "allocate channel C${CN} type disk;" >> ${RMANSCRIPT}
((CN = CN + 1))
done
echo "CHANGE ARCHIVELOG ALL CROSSCHECK;" >> ${RMANSCRIPT}
#echo "DELETE NOPROMPT EXPIRED ARCHIVELOG ALL;" >> ${RMANSCRIPT}
echo ${RECOVERY_STOP} >> ${RMANSCRIPT}
echo "CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '${BKPLOC}/${ORACLE_SID}_controlfile%F';" >> ${RMANSCRIPT}
echo "BACKUP ${COMPRESSED} FORMAT '${BKPLOC}/%d_%I_%t_%s_%p' TAG='FULLBKP'" >> ${RMANSCRIPT}
echo "FILESPERSET 100 DATABASE include current controlfile ${PLUS_ARCHIVELOG};" >> ${RMANSCRIPT}
echo "BACKUP ${COMPRESSED} FORMAT '${BKPLOC}/CONTROLFILE_%d_%I_%t_%s_%p.bkp' REUSE TAG='CONTROL_BKP' CURRENT CONTROLFILE;" >> ${RMANSCRIPT}
echo "${SETOLDCONTROLFILEBKPVAL};" >> ${RMANSCRIPT}
echo ${RECOVERY_START} >> ${RMANSCRIPT}
echo "SQL \"ALTER DATABASE BACKUP CONTROLFILE TO TRACE AS ''${BKPLOC}/controlfile.trc'' REUSE\";" >> ${RMANSCRIPT}
echo "SQL \"CREATE PFILE=''${BKPLOC}/init${ORACLE_SID}.ora'' FROM SPFILE\";" >> ${RMANSCRIPT}
echo "}" >> ${RMANSCRIPT}
echo "RMAN BACKUP SCRIPT CREATED."
echo
sleep 1
echo "Backup Location is: ${BKPLOC}"
echo
sleep 1
echo "Starting Up RMAN Backup Job ..."
echo
sleep 1
echo "#!/bin/bash" > ${RMANSCRIPTRUNNER}
echo "nohup ${ORACLE_HOME}/bin/rman target / cmdfile=${RMANSCRIPT} | tee ${RMANLOG} 2>&1 &" >> ${RMANSCRIPTRUNNER}
chmod 740 ${RMANSCRIPTRUNNER}
source ${RMANSCRIPTRUNNER}
echo
echo " The RMAN backup job is currently running in the background. Disconnecting the current session will NOT interrupt the backup job :-)"
echo " Now, viewing the backup job log:"
echo
echo "Backup Location is: ${BKPLOC}"
echo "Check the LOGFILE: ${RMANLOG}"
echo
# #############
# END OF SCRIPT
# #############
# REPORT BUGS to: <mahmmoudadel@hotmail.com>.
# DISCLAIMER: THIS SCRIPT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY. IT IS PROVIDED "AS IS".
# DOWNLOAD THE LATEST VERSION OF DATABASE ADMINISTRATION BUNDLE FROM:
# http://dba-tips.blogspot.com/2014/02/oracle-database-administration-scripts.html