#!/bin/bash
# Fred Denis -- October 28th 2021 -- fred.denis3@gmail.com -- http://unknowndba.blogspot.com
# oraenv++ - a step forward towards an easy and powerful tool to set up your Oracle environment (https://bit.ly/3GHJMFv)
# Copyright (C) 2021 Fred Denis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
#
# More info and git repo: https://bit.ly/3GHJMFv -- https://github.com/freddenis/oracle-scripts
#
# The current script version is 20220428
#
# History :
#
# 20220428 - Fred Denis - Small bug fixzed with orabase
# 20211111 - Fred Denis - GPLv3 licence
# 20211109 - Fred Denis - Use -attr which speeds up crsctl (and then oraenv++) in a significant way
# 20211108 - Fred Denis - Fixed a bug with Oracle restart, removed crsctl -v which is not needed
# OPP_HOSTNAME for when I debug with external files
# 20211103 - Fred Denis - Initial release
#
set -o pipefail
#
# Variables
#
ME="oraenv++" # For the usage function
TS="date "+%Y-%m-%d_%H%M%S"" # A timestamp for a nice outut in a logfile
TMP=$(mktemp -u) # A tempfile
OLR="/etc/oracle/olr.loc" # olr file to set up crs env
if [[ -z "${OPP_HOSTNAME}" ]]; then # For debug mainly
HOSTNAME=$(hostname -s) # Local hostname
else
HOSTNAME="${OPP_HOSTNAME}"
fi
CRSFILTER="(TYPE = ora.database.type" # crsctl stat res command filter (no closing parenthesis)
ASMFILTER="(TYPE = ora.asm.type)" # crsctl stat res command filter for ASM
nb=0 # A counter
COL_DB=6
COL_SID=8
COL_OH=20
COL_ID=3
COL_SEP=""
OLD_SILENT="${SILENT}"
SILENT="${OPP_SILENT}" # Silent output with env variable OPP_SILENT="True"
GREP="." # -g/--grep option
UNGREP="donotgrepme$$" # -v/--ungrep option
unset DB # -d/--db option
unset PDB # -p/--pdb option
unset IN # -f option
unset OUT # -o option
unset tab # An aray
#
# oraenv++ needs to be sourced (. oraenv++) and not executed (./oraenv++) as it sets environment variable in the current shell
#
if [[ "${0}" == "${BASH_SOURCE[0]}" ]]; then
printf "\033[1;31m%s\033[m\n" "$($TS) [ERROR] oraenv++ needs to be sourced (. oraenv++) and not executed (./oraenv++) as it sets environment variable in the current shell; cannot continue." >&2
exit 123
fi
#
# To make it easy, DB can be the first parameter but we need to check as more options will also be possible
#
DB=$1
if [[ "${DB:0:1}" == "-" ]]; then DB=""; fi
if [[ $(echo "${DB}" | tr '[:upper:]' '[:lower:]') == "asm" ]]; then DB="+ASM"; PDB=""; fi # Useful alias
#
# Usage function
#
usage() {
printf "\n\033[1;37m%-8s\033[m\n" "NAME" ;
cat << END
${ME} - a step forward towards an easy and powerful tool to set up your Oracle environment (https://bit.ly/3GHJMFv)
END
printf "\n\033[1;37m%-8s\033[m\n" "SYNOPSIS" ;
cat << END
. ${ME}
. ${ME} [-d] [-p] [-g] [-v] [-s] [-S] [-h]
. ${ME} [--db] [--pdb] [--grep] [--ungrep] [--silent] [--help]
END
printf "\n\033[1;37m%-8s\033[m\n" "DESCRIPTION" ;
cat << END
${ME} is meant to set up your Oracle environment without the limitations of other tools
${ME} is NOT based on oratab but gets the environment information from a GI/Oracle restart
so you do not have to hardcode any configuration file nor maintain oratab
${ME} is then able to set up the correct SID of a RAC database for example
If ${ME} finds more than 1 database matching your grep/ungrep combinaison, it will show you a menu to choose from
If no parameters are given, ${ME} will show you all the databases resgistered in the GI and a menu to choose from
As ${ME} sets environment variable in a parent shell, it needs to be invocated with "." or "source"
oraenv++ does not list all the PDBs per CDB (yet) but can setup ORACLE_PDB_SID with the --pdb option
As CRS/GI saves the databases names in lowercase regardless of their real case, --db, --grep and --ungrep are non key sensitive
END
printf "\n\033[1;37m%-8s\033[m\n" "OPTIONS" ;
cat << END
-d | --db ) Database to set the environment (this is the DB_NAME registered into the GI/CRS)
to make is easy, this option can also not be specified if grep and ungrep are not used (see examples below)
-p | --pdb ) PDB to set the ORACLE_PDB_SID variable to; requires DB to be set
-g | --grep ) grep one or multiple comma separated patterns in the database list registered in the GI/CRS
-v | --ungrep ) ungrep (grep -v) one or multiple comma separated patterns in the database list registered in the GI/CRS
-s | -S | --silent) Silent output; the same can be achieved by setting the env variable OPP_SILENT="True"
-h | --help ) Shows this help
END
printf "\n\033[1;37m%-8s\033[m\n" "EXAMPLES" ;
cat << END
. ${ME} # Show a menu with all the databases registered in the cluster
. ${ME} PROD # Set the env for the PROD database
. ${ME} prod # Same as above
. ${ME} --db PROD # Same as above
. ${ME} PROD --pdb REPORTING # Set env to PROD CDB and REPORTING CDB
. ${ME} +ASM1 # Set the ASM environment
. ${ME} asm # Set the ASM environment (a useful alias for the asm instances, just enter "asm")
. ${ME} --grep PROD --ungrep PS # All the "PROD" databases not containing "PS"
. ${ME} -g PROD,DEV -v ab.[1-9],^xyz # grep and ungrep also support regexp; try it out :)
END
}
#
# Just print a "-" line
#
print_a_line() {
local l_i=$1
for x in $(seq 1 ${l_i}); do
printf "%s" "-"
done
printf "\n"
}
#
# Make a menu with a list of DB/Instances to choose from
#
makemenu() {
local l_i
# First, we check the length to adapt the table for a nice output
for l_i in $(seq 1 ${#tab[@]}); do
DB=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $1}')
OH=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $2}')
SID=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $3}')
if [[ "${#DB}" -gt "${COL_DB}" ]]; then COL_DB="${#DB}" ; fi
if [[ "${#OH}" -gt "${COL_OH}" ]]; then COL_OH="${#OH}" ; fi
if [[ "${#SID}" -gt "${COL_SID}" ]]; then COL_SID="${#SID}"; fi
done
# Add a few spaces to have a better looking table
(( COL_DB+=3 ))
(( COL_OH+=3 ))
(( COL_SID+=3 ))
LINESIZE=$((COL_DB+COL_OH+COL_SID+COL_ID))
printf "\n" ;
printf "%${COL_ID}s " "" ;
printf "%-${COL_DB}s${COL_SEP}" " Database" ; # DB
printf "%-${COL_SID}s${COL_SEP}" " SID" ; # SID
printf "%-${COL_OH}s${COL_SEP}" " ORACLE_HOME" ; # OH
printf "\n" ;
print_a_line "${LINESIZE}"
for l_i in $(seq 1 ${#tab[@]}); do
DB=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $1}')
OH=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $2}')
SID=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $3}')
printf "%${COL_ID}s/" " ${l_i}" ;
printf "%-${COL_DB}s${COL_SEP}" " ${DB}" ; # DB
printf "%-${COL_SID}s${COL_SEP}" " ${SID}" ; # SID
printf "%-${COL_OH}s${COL_SEP}" " ${OH}" ; # OH
printf "\n" ;
done
print_a_line "${LINESIZE}"
printf "\n" ;
}
#
# Just show the current env
#
showenv() {
if [[ "${SILENT}" != "True" ]]; then
printf "\n"
printf "%-20s: %-s\n" "Database" "${DB}"
printf "%-20s: %-s\n" "ORACLE_HOME" "${ORACLE_HOME}"
printf "%-20s: %-s\n" "ORACLE_BASE" "${ORACLE_BASE}"
printf "%-20s: %-s\n" "ORACLE_SID" "${ORACLE_SID}"
printf "%-20s: %-s\n" "ORACLE_PDB_SID" "${ORACLE_PDB_SID}"
printf "%-20s: %-s\n" "sqlplus is" $(type sqlplus 2>/dev/null | awk '{print $3}')
printf "\n"
fi
}
#
# Get an indice as a parameter and export the env variables from the tab array for this indice
#
exportvar() {
local l_i=$1
local DB=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $1}')
local OH=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $2}')
local SID=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $3}')
local PDB=$(echo "${tab[${l_i}]}" | awk -F ":" '{print $4}')
export ORACLE_HOME="${OH}"
export ORACLE_SID="${SID}"
export ORACLE_BASE=$(${ORACLE_HOME}/bin/orabase)
export PATH="${ORACLE_HOME}/bin:${PATH}"
export ORACLE_PDB_SID="${PDB}"
showenv
}
#
# Setup env
#
setupenv() {
if [[ ${#tab[@]} == 0 ]]; then # nothing to do !
printf "\033[1;34m%s\033[m\n" "$($TS) [INFO] Found no DB/Instance, nothing to do !"
return 123
fi
if [[ ${#tab[@]} -gt 1 ]]; then # Many DB/Instances, lets make a menu to choose from
makemenu
printf "\033[1;36m%s\033[m\n" "Which environment you want to set up ? (CTRL+C for exit)"
read answer
if ! [[ "${answer}" =~ ^[0-9]+$ ]] || [[ "${answer}" -eq 0 ]] || [[ "${answer}" -gt ${#tab[@]} ]] ; then
printf "\033[1;31m%s\033[m\n" "$($TS) [ERROR] This does not look like a valid choice, exiting."
return 124
else
exportvar "${answer}"
fi
else # 1 DB/Instance, we set up the env
exportvar 1
fi
}
#
# Options -- Long and Short, options needs to be separa
# Options are comma separated list, options requiring a parameter need to be followed by a ":"
#
SHORT="d:,g:,v:,p:,o:,f:,s,S,h"
LONG="db:,grep:,ungrep:,pdb:,silent,help"
# Check if the specified options are good
options=$(getopt -a --longoptions "${LONG}" --options "${SHORT}" -n "$0" -- "$@")
# If not, show the usage and exit
if [[ $? -ne 0 ]]; then
printf "\033[1;31m%s\033[m\n" "$($TS) [ERROR] Invalid options provided: $*; use -h for help; cannot continue." >&2
return 864
fi
#
eval set -- "${options}"
# Option management, not the "shift 2" when an option requires a parameter and "shift" when no parameter needed
while true; do
case "$1" in
-d | --db ) DB="$2" ; shift 2 ;;
-g | --grep ) GREP="$2" ; shift 2 ;;
-v | --ungrep ) UNGREP="$2" ; shift 2 ;;
-p | --pdb ) PDB="$2" ; shift 2 ;;
-s | -S | --silent) SILENT="True" ; shift ;;
-o ) OUT="$2" ; shift 2 ;;
-f ) IN="$2" ; shift 2 ;;
-h | --help ) usage && return 999 ; shift ;;
-- ) shift ; break ;;
esac
done
#
# Options verification
#
# CRS has all the databases in lowercase regardless of their real case
# I do not do that in the getopt to keep it short and neat
#
DB=$(echo "${DB}" | tr '[:upper:]' '[:lower:]')
GREP=$(echo "${GREP}" | tr '[:upper:]' '[:lower:]')
UNGREP=$(echo "${UNGREP}" | tr '[:upper:]' '[:lower:]')
#
# A database name is specified, we update the CRS filter to get info of only this DB
#
if [[ -n "${DB}" ]]; then
if [[ "${DB}" != "+"* ]]; then # Not ASM
CRSFILTER="${CRSFILTER} AND (NAME = ora.${DB}.db)"
else # ASM
PDB="" # ASM has no PDB
CRSFILTER="${ASMFILTER}"
fi
else # No database specified
CRSFILTER="${CRSFILTER}) OR ${ASMFILTER}"
fi
#
# Do things :)
#
if [[ -n "${IN}" ]]; then # An input file is specified
if [[ ! -f "${IN}" ]]; then # File does not exist
printf "\033[1;31m%s\033[m\n" "$($TS) [ERROR] Cannot find file ${IN} specified with -o option; cannot continue." >&2
return 16
else
cp -f "${IN}" "${TMP}"
# cat "${TMP}" # Useful when I debug with an input file
fi
else # No input file specified, we get the info ourselves
if [[ -f "${OLR}" ]]; then
export ORACLE_HOME=$(cat "${OLR}" | grep "^crs_home" | awk -F "=" '{print $2}')
export ORACLE_BASE=$(${ORACLE_HOME}/bin/orabase)
export PATH="${PATH}:${ORACLE_HOME}/bin"
else
printf "\033[1;31m%s\033[m\n" "$($TS) [ERROR] Cannot find ${OLR} file to set the ASM env; cannot continue." >&2
return 17
fi
printf "\033[1;36m%-60s\033[m" "Reading the system configuration . . ."
# Generate the GEN_USR_ORA_INST_NAME@SERVERNAME(servername) for all the nodes for the -attr option
GENATSERVERNAME=$(for N in $(olsnodes); do printf "GEN_USR_ORA_INST_NAME@SERVERNAME(%s)," "${N}"; done)
crsctl stat res -p -w "${CRSFILTER}" -attr "NAME,TYPE,GEN_USR_ORA_INST_NAME,ORACLE_HOME,${GENATSERVERNAME}" > "${TMP}"
echo -en "\033[2K"
tput hpa 0
fi
if [[ -n "${OUT}" ]]; then # We save the info into an output file and exit (-o)
cp "${TMP}" "${OUT}"
printf "\033[1;36m%s\033[m\n" "$($TS) [INFO] Info saved in ${OUT}."
rm -f "${TMP}"
return 0
fi
for X in $(cat "${TMP}" | \
awk -F "=" -v I_GREP="${GREP}" -v I_UNGREP="${UNGREP}" -v I_HOSTNAME="${HOSTNAME}" -v I_CRS_HOME="${ORACLE_HOME}" -v I_PDB="${PDB}" ' \
BEGIN {
split(I_GREP , tab_grep , ",") ; to grep multiple patterns
split(I_UNGREP, tab_ungrep, ",") ; to ungrep multiple patterns
}
{ if ($1 == "NAME") {
sub(/\(.*$/, "", $2) ; # Cleaning up: asm(ora.asmgroup)
sub("^ora.", "", $2) ;
l_name = $2 ; # Name.type
getline ; # Next line is the TYPE
if (($2 == "ora.database.type") || ($2 == "ora.asm.type")){ # This is a database
sub(".db$", "", l_name) ; # Remove the consumer group
for (i=1; i<=length(tab_ungrep); i++) {
if (l_name ~ tab_ungrep[i]) {
next ;
}
}
for (i=1; i<=length(tab_grep); i++) {
if (l_name ~ tab_grep[i]) {
sub(".db$", "", l_name) ; # Remove the consumer group
if (tab_db[l_name] == l_name) {
next ;
} else {
tab_db[l_name] = l_name ; # List of databases
}
}
}
} # End if ($2 == "ora.database.type")
if ($2 == "ora.asm.type") { # GI does not explicitly give CRS HOME path so I set it here
tab_oh[l_name] = I_CRS_HOME ; # CRS HOME (OH for ASM)
}
while(getline)
{
if ($1 == "ORACLE_HOME") {
tab_oh[l_name] = $2 ; # List of OH
}
if ($1 == "GEN_USR_ORA_INST_NAME") {
sid_restart = $2 ; # In case it is Oracle Restart (SINGLE database_type)
}
if ($1 ~ /^GEN_USR_ORA_INST_NAME@SERVERNAME/) { # RAC and RACOneNode database_type only
sid_restart = "" ;
sub("GEN_USR_ORA_INST_NAME@SERVERNAME[(]", "", $1);
sub(")", "", $1) ;
if ($1 == I_HOSTNAME) {
sid_hostname = $2 ; # SID for the host
}
}
if ($0 ~ /^$/){
if (sid_hostname != "") {
tab_sid[l_name] = sid_hostname ; # RAC and RACOneNode
} else {
tab_sid[l_name] = sid_restart ; # Oracle Restart only
}
sid_hostname = "" ;
sid_restart = "" ;
break ;
}
} # End while(getline)
} # End if ($1 == "NAME")
} END {
for (x in tab_db) {
if (tab_sid[x] != "") {
printf("%s:", tab_db[x]) ; # DB
printf("%s:", tab_oh[x]) ; # OH
printf("%s:", tab_sid[x]) ; # SID
printf("%s:", I_PDB) ; # PDB
printf("\n") ;
}
}
}' | sort); do
((nb++))
tab[${nb}]="${X}"
done
setupenv
rm -f "${TMP}"
SILENT="${OLD_SILENT}"
return 0
#************************************************************************#
#* E N D O F S O U R C E *#
#************************************************************************#