#!/bin/sh

: <<SCRIPTHEADER
Description: MySQL/MariaDB database administration wizard
Version: 0.6.3
Copyright: GNU GPL (2011-2026) Narcis Garcia
Homepage: https://www.actiu.net/admidb/
License: GNU GPL
 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 <http://www.gnu.org/licenses/>.
SCRIPTHEADER

# Program development at programacio/projectes_publics/admidb/admidb
# Software releases can be downloaded from: https://...##

# Script template version: 1.5.9
# Copyright 2017-2023 (GNU GPL) Narcis Garcia
# Compatible with package managers integration: Dpkg/deb

# Program development ToDo:
#	- Check that "data replace" it works with quotes in strings.
#	- "db export" does not seem to export stored procedures. CMS affected:
#	  https://github.com/jmueller17/Aixada
#	- Compatibilize "db import" with adminer.org exports
#	- Standarize exitcodes
#	- Si no se guarda la contraseña, que se pida una sola vez
#	- Move "db list" to "db report" and "db list" to be only a simple names list
#	- "db report" should account size from /var/lib/mysql/ for InnoDB databases.
#	- "db report" should move Engine, DCharSet and Def.Collation. to --tech alternative
#	  and instead it should show tables number, total columns number and total rows number, by default
#	- Db_export debería usar EmpaquetarContenido() y la posibilidad de encriptación
#	- Db_import debería usar DesempaquetarContenido()
#	- Comprobar espacio de disco disponible antes de cada operación (exportar, importar, reparar)
#	- Informar del volumen de la base de datos antes de empaquetarla (exportar)
#	- Función de reparación de 1 BD y de todas las BD.
#	  Para MyISAM:
#	  	mysqlcheck --auto-repair --all-databases
#	  	mysqlcheck --optimize --all-databases
#	  Para InnoDB (exportar e importar):
#		[1] mariadb-dump -u usuario -p -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction --add-drop-database nombrebasedatos > fichero.sql
#		[todas] mariadb-dump -u root -p --compatible=mysql40 --force --hex-blob --opt --quick --single-transaction --add-drop-database --all-databases > fichero.sql
#		mysql -u root -p < fichero.sql
#	- Gestionar PostgreSQL -> Entonces considerar como versión 1.x
#	- Funciones find y replace para campo, tabla y bd
#	- Parámetro --rotate para la copia de seguridad
#	- Exportar/importar usuarios y permisos
#	- Envio de informe de resultado por correo-e (--mailinfo para todo --mailerror para sólo en caso de error)
#	- Ampliar la funcionalidad a diversas operaciones que tambien hace BASAP (construir BD)
#	- Desactivación, eliminación y papelera de BD
#	- Service_instalar(), Service_desinstalar()
#	- Poner a prueba toda la funcionalidad con MySQL 5.0, MySQL 5.6 y MySQL 4
#	- At leas warn when overwriting a database on import operation!
#	- Support .zip packaging (Many ERP software use it for their backups)
#	- Support Db_import for a package with multiple .sql files in it
#	- Detect database service name to say specific one instead of "MySQL/MariaDB"
#	- User_create debería soportar la especificación de host-cliente: usuario@% para crear «usuario» con Host «%»

# - List stored procedures and functions:
#	SELECT Db, Name, type FROM mysql.proc;

# ==========
# Template is used by projects: ctcreate gesdonis matromu pressum remcage sesele vinca vinca-viewer xhash odtmerge crt-distribute grutisos aukups admidb opennic-update amoreos hyperfiles asterisk-sendmail update-skelruns odt-mailing dobok instant-router acontrolpanel profsito ipbackend-update blog-mirror odf-barcode-odf tryton-notheme gesvipen refafit dispeed lsmedia linkingtag
# ----------
# Template's development at: utilitats/instals.gnu/utilitats_sh/00_plantilles/script.sh
# See software sections at https://packages.debian.org/oldstable/
# Template's ToDo:
#	- Fix speed of script launch (maybe due to configuration tasks)
#	- Setup logrotate rules at system
#	- Sure log autolimitation is effective
#	- Use (never write to) a /etc/default/$ProgramName that can come with package manager.
#	- Create UserConfigFile with only a comment explaining default options are at SystemConfigFile
#	- Installation on Android
#	  mount -o remount,rw /system
#	  cp myProgramName /system/bin/
#	  chmod 755 /system/bin/myProgramName
#	- Header parameter SecureConfig=1 to not give permissions to "others" over configuration files and logs
#	- Header parameter ReadonlyConfig=1 to don't write to /etc or User's profile.
#	- Implement --config to specify alternate configuration file.
# ==========

# ProgramName: Brief and compact code name that needs to be unique in the software world. Will be used for filenames and some directories.
ProgramName="admidb"
# DependsOnSoftware: Space separated list of required programs. Each item's syntaxes: executable/package ExecutableOption1:ExecutableOption2/PackageOption1|PackageOption2...
DependsOnSoftware="tr:cat/coreutils|debianutils grep sed perl/perl-base mysql:mariadb/default-mysql-client|mysql-client xxd find/findutils"
RecommendedSoftware="apg"
SuggestedSoftware=""
SystemConfigDir="/etc/${ProgramName}"
# ProgramInstaller: If integrated install/uninstall functions will be available (1) or not (0)
ProgramInstaller=1
# InstalledExpected: If help information drives user to install program before using it (1) or not (0)
InstalledExpected=0
# RootRequired: Prevent to run without superuser permissions? (1=Yes 0=No). This also determines program FHS location (sbin/ or bin/).
RootRequired=0


##### TEMPLATE FUNCTIONS from @-funcions 0:2024.01.07 #####

ParO='(' ; ParC=')' ; Tab="$(printf '\t')"
if [ "$TERM" != "" ] ; then
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"
	RedirectedStdout=$?
fi
if [ "$TERM" != "" ] && [ $RedirectedStdout -eq 0 ] ; then
	# TO DO: Replace all tput calls by printf. Many documentation tell how to escape through "echo -e"
	# https://stackoverflow.com/questions/2924697/how-does-one-output-bold-text-in-bash
	# https://gist.github.com/izabera/9903f9d942e2667ef2cb
	cbDGRAY="$(tput setab 0)"
	cbRED="$(tput setab 196)"
	cbGREEN="$(tput setab 46)"
	cbCBROWN="$(tput setab 3)"
	cbVIOLET="$(tput setab 5)"
	cbBLUEGREEN="$(tput setab 6)"
	cbCGRAY="$(tput setab 7)"
	cbGRAY="$(tput setab 8)"
	cbCGREEN="$(tput setab 10)"
	cbCYELLOW="$(tput setab 11)"  # Darker
	cbYELLOW="$(tput setab 226)"
	cbCBLUE="$(tput setab 12)"
	cbCVIOLET="$(tput setab 13)"
	cbCYAN="$(tput setab 51)"
	cbWHITE="$(tput setab 15)"
	cbBLACK="$(tput setab 16)"
	cbBLUE="$(tput setab 21)"
	cbPINK="$(tput setab 26)"
	
	cfDGRAY="$(tput setaf 0)"
	cfRED="$(tput setaf 196)"
	cfGREEN="$(tput setaf 46)"
	cfCBROWN="$(tput setaf 3)"
	cfVIOLET="$(tput setaf 5)"
	cfBLUEGREEN="$(tput setaf 6)"
	cfCGRAY="$(tput setaf 250)"
	cfGRAY="$(tput setaf 8)"
	cfCGREEN="$(tput setaf 10)"
	cfCYELLOW="$(tput setaf 11)"  # Darker
	cfYELLOW="$(tput setaf 226)"
	cfCBLUE="$(tput setaf 12)"
	cfCVIOLET="$(tput setaf 13)"
	cfCYAN="$(tput setaf 51)"
	cfWHITE="$(tput setaf 15)"
	cfBLACK="$(tput setaf 16)"
	cfBLUE="$(tput setaf 21)"
	cfPINK="$(tput setaf 26)"
	
	fBOLD="$(tput bold)"
	fBOLD_="$(printf '\033[22m')"  # not bold	https://stackoverflow.com/questions/15579739/in-an-xterm-can-i-turn-off-bold-or-underline-without-resetting-the-current-colo
	fREVERSEC="$(tput rev)"
	fLOW="$(tput dim)"
	fLOW_="$(printf '\033[22m')"  # not dark
	fITALIC="$(tput sitm)"
	fITALIC_="$(printf '\033[23m')"  # not italic
	fUNDERL="$(tput smul)"
	fUNDERL_="$(printf '\033[24m')"  # not underline
	fUNDERLx="$(tput rmul)"  # not underline
#	fNOSTANDOUT="$(tput rmso)"  # No bold, no lines  # Not working in GNU/Linux
#	fNOSTANDOUT="$(echo -e '\033[0m' | sed -e 's|-e||')"  # No bold, no lines, no italic, etc. # Dash echo is buggy with escaped sequences.
#	fNOSTANDOUT="$(/bin/echo -e '\033[0m')"  # Workaround to use Bash if installed
	fNOSTANDOUT="$(printf '\033[0m')"  # No bold, no lines, no italic, etc. # Dash echo is buggy with escaped sequences.
	fSTRIKETHROUGH="$(printf '\033[9m')"
	fSTRIKETHROUGH_="$(printf '\033[29m')"  # not strikethrough
	fBELL="$(tput bel)"
	fRESET="$(tput sgr0)"
	
	sPROMPT="${fBELL}${cfWHITE}${cbDGRAY}${fBOLD}"
	sHEAD0="${cfCYAN}${cbBLACK}${fBOLD}${fUNDERL}"
	sHEAD1="${cfCYAN}${cbDGRAY}${fBOLD}"
	sWARN="${cfYELLOW}${cbDGRAY}${fBOLD}"
	sERROR="${fBELL}${cfRED}${cbBLACK}${fBOLD}"
	sINFO="${cfWHITE}${cbDGRAY}"
	sPROGRESS="${cfCGREEN}${cbDGRAY}"
	sVALUE="${cfVIOLET}${cbDGRAY}${fBOLD}"
	sGOOD="${cfWHITE}${cbBLUEGREEN}${fBOLD}"
	sGOODEM="$sGOOD"
	sGOODNORMAL="${cbBLACK}${cfGREEN}${fBOLD}"
	sDISABLED="${fLOW}"
else
	cbDGRAY=''
	cbRED=''
	cbGREEN=''
	cbCBROWN=''
	cbVIOLET=''
	cbBLUEGREEN=''
	cbCGRAY=''
	cbGRAY=''
	cbCGREEN=''
	cbCYELLOW=''
	cbYELLOW=''
	cbCBLUE=''
	cbCVIOLET=''
	cbCYAN=''
	cbWHITE=''
	cbBLACK=''
	cbBLUE=''
	cbPINK=''
	
	cfDGRAY=''
	cfRED=''
	cfGREEN=''
	cfCBROWN=''
	cfVIOLET=''
	cfBLUEGREEN=''
	cfCGRAY=''
	cfGRAY=''
	cfCGREEN=''
	cfCYELLOW=''
	cfYELLOW=''
	cfCBLUE=''
	cfCVIOLET=''
	cfCYAN=''
	cfWHITE=''
	cfBLACK=''
	cfBLUE=''
	cfPINK=''
	
	fBOLD=''
	fBOLD_=''
	fREVERSEC=''
	fLOW=''
	fLOW_=''
	fITALIC=''
	fITALIC_=''
	fUNDERL=''
	fUNDERL_=''
	fUNDERLx=''
	fNOSTANDOUT=''
	fSTRIKETHROUGH=''
	fSTRIKETHROUGH_=''
	fBELL=''
	fRESET=''
	
	sPROMPT=''
	sHEAD0=''
	sHEAD1=''
	sWARN=''
	sERROR=''
	sINFO=''
	sPROGRESS=''
	sVALUE=''
	sGOOD=''
	sGOODEM=''
	sGOODNORMAL=''
	sDISABLED=''
fi

Is_Executable ()
# Syntax as a function: Is_Executable $Command
# Description: Returns (exitcode 0) TRUE if specified command's argument is a directly available executable file, internal program or function; or FALSE otherwise.
# Use example (without brackets []):
#	if Is_Executable nano ; then echo "Program exists." ; fi
# Depends on functions: (none)
# Depends on software packages: (none)
{
	ie__Program="$1"
	ie__TrueCode=254  # 254=FALSE
	ie__TestValue=''
	
	ie__TestValue="$(command -v "$ie__Program" 2>/dev/null)"
	IsEmptyString () { WordsNr () { printf '%s' $#; }; return $(WordsNr $*); }
	if ! IsEmptyString "$ie__TestValue" ; then
		# Some scenarios "command" returns directories as executables.
		if [ ! -d "$ie__TestValue" ] || [ "$(printf '%s' "$ie__TestValue" | tr '/' ' ')" = "$ie__TestValue"  ] ; then
			ie__TrueCode=0
		fi
	fi
	return $ie__TrueCode
}

Is_IntegerNr ()
# Syntax as a function: Is_IntegerNr $Valor
# Description: Returns (exitcode 0) TRUE if specified value is an integer number, or FALSE otherwise. (IsNumeric/EsNumerico)
# Use example (without brackets []):
#	if Is_IntegerNr $Respuesta ; then echo "Correcto." ; fi
# Use example verifying also there is only 1 word:
#	if Is_IntegerNr "$Respuesta" ; then echo "Correcto." ; fi
# Notes:
#	- Only first parameter is evaluated. No matter next parameters contain.
# Depends on functions: (none)
# Depends on software packages: (none)
{
	iin__TestValue="$1"
	iin__TrueCode=254  # 254=FALSE
	iin__LastStatus=0
	
	iin__TestValue="$(expr "$iin__TestValue" : '[ ]*\(.*[^ ]\)[ ]*$')"	# Trim spaces
	if [ "$iin__TestValue" = "" ] ; then iin__TestValue='.' ; fi
	[ "$iin__TestValue" -eq "$iin__TestValue" ] > /dev/null 2>&1
	iin__LastStatus=$?
	if [ $iin__LastStatus -eq 0 ] ; then	iin__TrueCode=0 ; fi
	return $iin__TrueCode
}

Dirname ()
# Independent version of GNU dirname
# Useful for Busybox and other minimalistic environments
# Notes:
#	- Only allows 1 path specified
#	- Does not allow any option (such as -z or --version)
# Depends on functions: (none)
# Depends on software packages: (none)
{
	local Path="$1"
	local NrElement=0
	local TotalElements=0
	local CurrentElement=''
	local PreviousElement=''
	local Value=''
	
	if [ "$Path" != "" ] ; then
		if [ "$Path" != "/" ] ; then
			IFS="/" ; for CurrentElement in $Path ; do
				NrElement=$((NrElement + 1))
				if [ $NrElement -gt 1 ] ; then
					if [ $NrElement -gt 2 ] ; then
						if [ "$Value" != "/" ] ; then Value="${Value}/" ; fi
					fi
					Value="${Value}${PreviousElement}"
				else
					if [ "$CurrentElement" = "" ] ; then Value="/" ; fi
				fi
				PreviousElement="$CurrentElement"
			done
			unset IFS
			if [ $NrElement -eq 1 ] && [ "$Value" = "" ] ; then Value="." ; fi
		else
			Value="/"
		fi
	else
		Value="."
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ReadlinkF ()
# Old compatibility replacement for modern "readlink -f"
# "canonicalize by following every symlink in every component of the given name recursively; all but the last component must exist"
# Depends on functions: Dirname
# Depends on software packages: grep, sed
{
	local Element="$1"
	local PreviousDir=''
	local CurrentElement=''
	local CurrentPath=''
	local TmpElement=''
	local BaseElement=''
	local ReadlinkStatus=0
	local LastPath=''
	local CurStep=''
	local Value=''

	if [ "$Element" != "" ] ; then
		PreviousDir="$(pwd)"
		CurrentRelElement="$Element"
		if [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] && [ -d "$(Dirname "$CurrentRelElement")" ] ; then
			# Same as "readlink -f" behaviour: If element doesn't exist (but path yes), return as if existed.
			cd "$(Dirname "$CurrentRelElement")"
			Value="$(pwd | sed -e 's|/$||g')"
			Value="${Value}/$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"
		fi
		while [ "$(ls "$CurrentRelElement" 2>/dev/null)" != "" ] ; do	# A broken link is not detected with -e
			if [ -d "$(Dirname "$CurrentRelElement")" ] ; then
				cd "$(Dirname "$CurrentRelElement")"
				CurrentPath="$(pwd)"
			else
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
			fi
			BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
			if [ "$CurrentPath" = "/" ] ; then CurrentPath="" ; fi
			Value="${CurrentPath}/${BaseElement}"
			NextRelElement="$(readlink "$BaseElement" 2>/dev/null)"
			ReadlinkStatus=$?
			if [ "$NextRelElement" != "" ] ; then
				CurrentRelElement="$NextRelElement"
			else
				if [ $ReadlinkStatus -eq 0 ] || [ $ReadlinkStatus -eq 1 ] ; then
					# Old readlink (such as v1.13.3 in Potato debianutils) returns exitcode 1 if element is not a link.
					CurrentRelElement=""
				else
					NextRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					if [ "$NextRelElement" != "" ] ; then
						CurrentRelElement="$NextRelElement"
					else
						CurrentRelElement="$(stat "$BaseElement" | grep -ie 'File:' | cut -f 4 -d '"')"	#'
					fi
				fi
			fi
			if [ "$CurrentRelElement" != "" ] && [ "$(ls "$CurrentRelElement" 2>/dev/null)" = "" ] ; then
				# Broken link
				BaseElement="$(printf '%s\n' "$CurrentRelElement" | tr -s '/' '\n' | tail -n 1)"	# Problems with a path begun with "-" in old GNU basename versions
				CurrentPath="$(Dirname "$CurrentRelElement")"
				if [ "$(printf '%s\n' "$CurrentPath" | grep -e '^/')" = "" ] ; then
					# Relative path
					LastPath="$(pwd)"
					IFS="/" ; for CurStep in $CurrentPath ; do unset IFS
						if [ "$CurStep" = ".." ] ; then
							LastPath="$(Dirname "$LastPath")"
						else
							if [ "$CurStep" = "." ] ; then
								LastPath="$LastPath"
							else
								if [ "$CurStep" = "" ] ; then
									LastPath="$LastPath"
								else
									LastPath="$(printf '%s\n' "$LastPath" | sed -e 's|/$||g')/${CurStep}"
								fi
							fi
						fi
					done
					CurrentPath="$LastPath"
				fi
				CurrentPath="$(printf '%s\n' "$CurrentPath" | sed -e 's|/$||g')"
				Value="${CurrentPath}/${BaseElement}"
			fi
		done
		cd "$PreviousDir"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

Lowercase ()
# Description: Converts specified string to lower letter case, using found tools.
# Depends on functions: Is_Executable
# Depends on software packages: grep, tr|awk|sed
{
	local OriginalString="$1"
	
	if [ "$OriginalString" != "" ] ; then
		# Busybox tr has no upper/lower conversion
		if Is_Executable tr && [ "$(tr --help 2>&1 | grep -e ':lower:')" != "" ] ; then
			printf '%s\n' "$OriginalString" | tr '[:upper:]' '[:lower:]'
		else
			if Is_Executable awk ; then
				printf '%s\n' "$OriginalString" | awk '{print tolower($0)}'
			else
				if Is_Executable sed && [ "$(sed --help 2>&1 | grep -e 'GNU')" != "" ] ; then
					printf '%s\n' "$OriginalString" | sed -re 's/([[:upper:]]?)/\L\1/g'
				fi
			fi
		fi
	fi
}

PrepareExitcodeToFile ()
# Syntax as a sentence: PrepareExitcodeToFile
# Description: Prepares environment variables to catch exitcode from a file. Useful when pipelining functions output. Supports cascade calls and recursion.
# NOTES:
#	- Use this function just before desired execution. It's expected that execution runs some function that saves exitcode to $NozeroStatusToFile file.
#	- If called execution does not generate any file. Exitcode 0 will be assumed.
#	- Exports done in a subshell $(...) do not survive out of it, but most of times this technique works to catch exitcode from subshell executions.
{
	petf__LastStatus=0
	petf__LegatedIndex=0
	petf__CurLegatedName=''
	if [ "$NozeroStatusToFile" != "" ] ; then
		petf__CurLegatedName="NozeroStatusToFile_Legated_${petf__LegatedIndex}"
		while [ "$(eval "printf '%s' \"\$$petf__CurLegatedName\"")" != "" ] ; do
			petf__LegatedIndex=$((petf__LegatedIndex + 1))
			petf__CurLegatedName="NozeroStatusToFile_Legated_${petf__LegatedIndex}"
		done
		eval "$petf__CurLegatedName=\"$NozeroStatusToFile\""
		NozeroStatusToFile=''
	fi
	if [ -d "/run/shm" ] ; then
		NozeroStatusToFile="$(mktemp -u -q -p /run/shm 2>/dev/null)"
		petf__LastStatus=$?
	else
		NozeroStatusToFile="$(mktemp -u 2>/dev/null)"
		petf__LastStatus=$?
	fi
	if [ $petf__LastStatus -ne 0 ] ; then
		NozeroStatusToFile="$(mktemp 2>/dev/null)"
		petf__LastStatus=$?
	fi
	if [ $petf__LastStatus -ne 0 ] || [ "$NozeroStatusToFile" = "" ] ; then
		NozeroStatusToFile=/tmp/exitcode.$$
	fi
	export NozeroStatusToFile="$NozeroStatusToFile"
}

GetFiledExitcode ()
# Syntax as a sentence: GetFiledExitcode $?
# Description: Prepares environment variables to catch exitcode from a file. Useful when pipelining functions output. Supports cascade calls and recursion.
# NOTES:
#	- Use this function just after desired execution. It's expected that execution runs some function that saves exitcode to $NozeroStatusToFile file.
#	- If called execution does not generate any file. Exitcode 0 is assumed.
#	- This also accumulates pipelining exitcode.
#	- Exports done in a subshell $(...) do not survive out of it, but most of times this technique works to catch exitcode from subshell executions.
# Depends on functions: Is_IntegerNr
# Depends on software packages: (none)
{
	gfe__LastStatus="$1"
	gfe__StatusCode=0
	gfe__LegatedIndex=0
	gfe__CurLegatedName=''
	gfe__LastLegatedName=''
	gfe__LastLegatedValue=''
	
	# Get value
	gfe__StatusCode=$(cat "$NozeroStatusToFile" 2>/dev/null)
	if ! Is_IntegerNr "$gfe__StatusCode" ; then gfe__StatusCode=0 ; fi
	rm -f "$NozeroStatusToFile"
	if [ $gfe__StatusCode -eq 0 ] && Is_IntegerNr "$gfe__LastStatus" ; then gfe__StatusCode=$gfe__LastStatus ; fi
	# Pop legacies array stack
	gfe__CurLegatedName="NozeroStatusToFile_Legated_${gfe__LegatedIndex}"
	while [ "$(eval "printf '%s' \"\$$gfe__CurLegatedName\"")" != "" ] ; do
		gfe__LastLegatedName="$gfe__CurLegatedName"
		gfe__LegatedIndex=$((gfe__LegatedIndex + 1))
		gfe__CurLegatedName="NozeroStatusToFile_Legated_${gfe__LegatedIndex}"
	done
	if [ "$gfe__LastLegatedName" != "" ] ; then
		eval "gfe__LastLegatedValue=\"\$$gfe__LastLegatedName\""
	fi
	if [ "$gfe__LastLegatedValue" != "" ] ; then
		export NozeroStatusToFile="$gfe__LastLegatedValue"
	else
		if [ "$(help export 2>/dev/null)" ] ; then
			# Bashism to remove variable
			export -n NozeroStatusToFile
		else
			# Other POSIX store empty variables
			export NozeroStatusToFile=
		fi
	fi
	return $gfe__StatusCode
}

DependenciasFaltan ()
# Sintaxis como función: $(DependenciasFaltan "$ListaDependencias")
# Descripción:
#	Comprueba la disponibilidad de los programas especificados y
#	devuelve (stdout) una lista de los que faltan. Si se encuentra todo
#	no devuelve nada.
# Parámetros esperados:
#	$1	Lista separada por espacios de cada ejecutable y paquete respectivo,
#		separados entre si por "/"
#		Para varias opciones de ejecutable, separarlos entre ":"
#	$2	(opcional) Especifique "0" para evitar que a cada mención de paquete se [añada] el nombre del ejecutable que falta.
# Ejemplo1:	$(DependenciasFaltan "cat/coreutils grep/grep dpkg-deb/dpkg")
#		puede devolver "grep dpkg[dpkg-deb]" si faltan grep y dpkg-deb
# Ejemplo2:	$(DependenciasFaltan "insserv:update-rc.d/sysv-rc cat/coreutils")
#		puede devolver "sysv-rc[update-rc.d]" si tanto falta insserv como update-rc.d
# ToDo:
#	- Cambiar la sintaxis para permitir rutas de ejecutable: /bin/gzip
#	  Alternativas a la barra /: ? ! ;
#	- Para un ejecutable que condiciona requerir otro, separarlos entre "¡"
#	  Por ejemplo: postfix¡policyd-spf/postfix-policyd-spf-python
#	  significaría: SI existe "postfix" entonces debe existir "policyd-spf" y ello depende del paquete postfix-policyd-spf-python
#	- Para un ejecutable que es incompatible con otro, separarlos entre "!"
#	  Por ejemplo: postfix!exigrep/exim4-base
#	  significaría: SI existe "postfix" entonces NO debe existir "exigrep" y ello se resuelve desinstalando el paquete exim4-base
# Depends on functions: Is_Executable
# Depends on software packages: grep, sed
{
	local ListaDependencias="$1"
	local SenyalarEjecutable="$2"
	local DependenciaActual=''
	local EjecutablesActuales=''
	local EjecutableActual=''
	local EncontradoActual=''
	local PaqueteActual=''
	local Value=''
	
	for DependenciaActual in $ListaDependencias ; do
		EjecutablesActuales="$(printf '%s' "$DependenciaActual" | cut -f 1 -d '/')"
		PaqueteActual="$(printf '%s' "$DependenciaActual" | cut -f 2 -d '/')"
		EjecutablesActuales="$(printf '%s' "$EjecutablesActuales" | tr -s ':' ' ')"
		EncontradoActual=''
		for EjecutableActual in $EjecutablesActuales ; do
			if Is_Executable "$EjecutableActual" ; then
				EncontradoActual="1"
			fi
		done
		if [ "$EncontradoActual" = "" ] ; then
			if [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual\[")" = "" ] || [ "$(printf '%s' " $Value " | grep -e " $PaqueteActual ")" = "" ] ; then
				if [ "$EjecutableActual" = "$PaqueteActual" ] || [ "$EjecutableActual" != "$EjecutablesActuales" ] || [ "$SenyalarEjecutable" = "0" ] ; then
					Value="$Value $PaqueteActual"
				else
					Value="$Value $PaqueteActual[$EjecutableActual]"
				fi
			fi
		fi
	done
	Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

NuevoTemporalSeguro ()
# Sintaxis como función: $(NuevoTemporalSeguro "$NombreBase" "$Extension" "$DirTemp")
# Descripción:
#	Crea y devuelve (stdout) la ruta de un fichero de tipo
#	/run/shm/usuario/NombreBase/20120821-091624.qBG8.ext con permisos rwX------
#	/tmp/usuario/NombreBase/20120821-091624.qBG8.ext con permisos rwX------
# Parámetros esperados:
#	$1	(opcional) Etiqueta ilustrativa
#	$2	(opcional) Extensión a añadir al nombre del fichero
#	$3	(opcional) Directorio-madre preferido en lugar de /run/shm o /tmp
# Notas:
#	- Si necesita ejecutar el fichero, debe evitar su creación en puntos como /run (noexec)
#	  especificando DirTemp
#	- Tambien es útil porque salva la falta de opciones --xx de versiones antiguas de mktemp
#	- Para directorios es mejor usar: TempName="${DirTemp}/${ProgramName}.$(id -un).$$.funcion" ; MkdirPP "$TempName" '' u=rw,g=r,o=
{
	local NombreBase="$1"
	local Extension="$2"
	local DirTemp="$3"
	local RutaDir=''
	local Tiempo=''
	local Valor=''
	local ModernTool=''
	local LastStatus=0
	local StatusCode=0
	
	RutaDir="${DirTemp}/$(id -un)"
	if [ "$NombreBase" != "" ] ; then
		RutaDir="${RutaDir}/${NombreBase}"
	fi
	mkdir -p "$RutaDir"
	chmod u=rwX,go= "${DirTemp}/$(id -un)"
	Tiempo="$(date +'%Y%m%d-%H%M%S')"
#	Tiempo="$(printf '%(%Y%m%d-%H%M%S)T')"  BASHISM
	if [ "$Extension" = "" ] ; then
		Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		ModernTool="$(mktemp --help 2>&1 | grep -e "--suffix")"
		if [ "$ModernTool" != "" ] ; then
			Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX --suffix=.${Extension})"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			Valor="$(env TMPDIR="$RutaDir" mktemp -t ${Tiempo}.XXXX)"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				mv "$Valor" "${Valor}.${Extension}"
				Valor="${Valor}.${Extension}"
			fi
		fi
	fi
	if [ "$Valor" != "" ] ; then
		if [ -f "$Valor" ] ; then
			printf '%s\n' "$Valor"
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ExecuteString ()
# Description: Writes specified parameters to a shell script, and runs the script.
# Notes: In some rare case, neither eval/command/exec may work well
# Depends on functions: NuevoTemporalSeguro
# Depends on software packages: (none)
{
	local ScriptFile=""
	local ArgumentNr=0
	local CurrentArgument=""
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	ScriptFile="$(NuevoTemporalSeguro '' sh "$DirTempX")"
	printf '%s\n' '#!/bin/sh' > "$ScriptFile"
	while [ $# -gt 0 ] ; do
		CurrentArgument="$1"
		ArgumentNr=$((ArgumentNr + 1))
		if [ $ArgumentNr -gt 1 ] ; then
			printf ' ' >> "$ScriptFile"
		fi
		if [ "$CurrentArgument" = "" ] ; then CurrentArgument="''" ; fi
		if [ "$CurrentArgument" != "\"\"" ] && [ "$CurrentArgument" != "''" ] ; then
			EvalArgument="$(eval printf '%s' "$CurrentArgument" 2>/dev/null)"
			LastStatus=$?
			if [ $LastStatus -eq 0 ] ; then CurrentArgument="$EvalArgument" ; fi
		fi
		printf '%s' "$CurrentArgument" >> "$ScriptFile"
		shift
	done
	printf '\n' >> "$ScriptFile"
	printf '%s\n' 'exit $?' >> "$ScriptFile"
	chmod u+x "$ScriptFile"
	"$ScriptFile"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm "$ScriptFile"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

IniSectionContent ()
# Syntax as a function: $(IniSectionContent "$FileOrContent" "$SectionName")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	[section name] without brackets []. Example: "global"
#		specifying "[]" returns file content before any section declaration.
# Notes:
#	- Includes section header 
# Depends on functions:	(none)
# Depends on software packages: grep, sed
{
	local FileOrContent="$1"
	local SectionName="$2"
	local LinesNr=0
	local Value=''
	
	if [ "$FileOrContent" != "" ] && [ "$SectionName" != "" ] ; then
		if [ -f "$FileOrContent" ] ; then
			Value="$(cat "$FileOrContent")"
		else
			Value="$FileOrContent"
		fi
		if [ "$SectionName" != "[]" ] ; then
			Value="$(printf '%s\n' "$Value" | sed -rne "/^[[:blank:]]*[[]${SectionName}[]]/,//p")"
			if [ "$Value" != "" ] ; then
				printf '%s\n' "$Value" | head -n 1	# Print before being filtered
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				# Omit current section header
				Value="$(printf '%s\n' "$Value" | tail -n $((LinesNr - 1)))"
				Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q')"
				LinesNr="$(printf '%s\n' "$Value" | wc -l)"
				if [ "$(printf '%s\n' "$Value" | tail -n 1 | grep -e '^ [[].*[]]' -e '^[[].*[]]')" != "" ] && [ $LinesNr -ge 2 ] ; then
					# Omit next section header
					Value="$(printf '%s\n' "$Value" | head -n $((LinesNr - 1)))"
				else
					# Omit when desired section is empty and first line is next section's header
					Value="$(printf '%s\n' "$Value" | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
				fi
			fi
		else
			Value="$(printf '%s\n' "$Value" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^ [[].*[]]' -ve '^[[].*[]]')"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

IniVarValue ()
# Syntax as a function: $(IniVarValue "$FileOrContent" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Default (empty): no section consideration. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. Default; empty string.
#	$5	(optional or empty) Separator between variable name and assigned value. Default: "=". Examples: "=" ":="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value. Default (empty): No end mark. Example: ";"
#	$7	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# To Do:
#	- This (or another function) to load more than one variable at once.
# Notes:
#	- if section is not specified or empty, returns last match from whole file content.
#	- if section is "[]", queries only file content before any [section] declaration.
# Depends on functions: IniSectionContent
# Depends on software packages: grep, sed
{
	local FileOrContent="$1"
	local VariableName="$2"
	local SectionName="$3"
	local NotFoundValue="$4"
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local DefaultsFile="$7"
	local LinesNr=0
	local SectionContent=''
	local SeparatorMask='IVVtmpbHckF2LMB4tmpWz2coasdb3tmpX7LuyGTvrW'
	local NotFoundKey='IVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	
	if [ "$FileOrContent" != "" ] ; then
		if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator='=' ; fi
		if [ "$SectionName" = "" ] ; then
			if [ -f "$FileOrContent" ] ; then
				SectionContent="$(cat "$FileOrContent")"
			else
				SectionContent="$FileOrContent"
			fi
		else
			SectionContent="$(IniSectionContent "$FileOrContent" "$SectionName")"
		fi
		Value="$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}" | tail -n 1 | sed -e "s|${NameValueSeparator}|${SeparatorMask}|")"
		if [ "$Value" != "" ] ; then
			# Variable found; let's separate value.
			Value="$(printf '%s\n' "$Value" | sed -e "s|.*${SeparatorMask}||")"
			Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			if [ "$EndVariableSymbol" != "" ] ; then
				Value="$(printf '%s\n' "$Value" | sed -e "s|${EndVariableSymbol}$||")"
				Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"	# Trim
			fi
			if [ "$(printf '%s\n' "$Value" | grep -e '^"' | grep -e '"$')" != "" ] ; then
				# Double quotes
				Value="$(printf '%s\n' "$Value" | cut -f 2- -d '"' | sed -e 's|"$||')"
			else
				if [ "$(printf '%s\n' "$Value" | grep -e "^'" | grep -e "'$")" != "" ] ; then
					# Single quotes
					Value="$(printf '%s\n' "$Value" | cut -f 2- -d "'" | sed -e "s|'$||")"
				fi
			fi
		else
			Value="$NotFoundKey"
		fi
	else
		Value="$NotFoundKey"
	fi
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ "$DefaultsFile" != "$FileOrContent" ] && [ -f "$DefaultsFile" ] ; then
			Value="$(IniVarValue "$DefaultsFile" "$VariableName" "$SectionName" "$NotFoundValue" "$NameValueSeparator" "$EndVariableSymbol" '')"
		else
			Value="$NotFoundValue"
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

SetIniVarValue ()
# Syntax as a sentence: SetIniVarValue "$File" "$VariableName" "$SectionName" "$NewValue" "$NameValueSeparator" "$PreComment"
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to write for the variable entry.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#		PreComment can contain escaped \n to be converted into line breaks.
# To Do:
#	- Process content separated by section (treat different variable as in different section)
#	- This (or another function) to set more than one variable at once.
# Notes:
#	- if section is not specified or empty, sets matches in the whole file.
#	- if section is "[]", sets variable only before any [section] declaration in the file.
#	- Some blank lines can be lost in resulting file content
# Depends on functions: Dirname IniSectionContent
# Depends on software packages: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"	# Optional or empty (examples: "global" "")
	local NewValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"	# Optional or empty (example: ":=") Default is "="
	local PreComment="$6"	# Optional line to precede variable's line if it must be added. Comment mark should be included.
	local OldContent=''
	local Part1=''
	local SectionContent=''
	local Part2=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$File" != "" ] && [ "$VariableName" != "" ] ; then
		if [ ! -f "$File" ] ; then
			mkdir -p "$(Dirname "$File")"
			touch "$File"
		fi
		if [ -f "$File" ] ; then
			if [ "$NameValueSeparator" = "" ] ; then NameValueSeparator="=" ; fi
			OldContent="$(cat "$File")"
			if [ "$SectionName" != "" ] ; then
				OldContentLinesNr=$(printf '%s\n' "$OldContent" | wc -l)
				if [ "$SectionName" = "[]" ] ; then
					SectionContent="$(printf '%s\n' "$OldContent" | sed -re '/^[[:blank:]]*[[].*[]]/q' | grep -ve '^[[].*[]]' -ve '^ [[].*[]]')"
					Part1LinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
					cat /dev/null > "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' ' | grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
						if [ "$SectionContent" != "" ] ; then
							printf '%s\n' "$SectionContent" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$PreComment" != "" ] ; then
							if [ "$(cat "$File" 2>/dev/null)" != "" ] && [ "$(cat "$File" | tail -n 1)" != "" ] ; then
								printf '%s\n' "" >> "$File"
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
							printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
					printf '%s\n' "$OldContent" | tail -n $((OldContentLinesNr - Part1LinesNr)) >> "$File"
				else
					if [ "$(printf '%s\n' "$OldContent" | grep -e "^[[]${SectionName}[]]" -e "^ [[]${SectionName}[]]")" != "" ] ; then
						# Case sensitive to match sed that cannot be insensitive with q/p commands
						Part1="$(printf '%s\n' "$OldContent" | sed -re "/^[[:blank:]]*[[]${SectionName}[]]/q")"
						Part1LinesNr=$(printf '%s\n' "$Part1" | wc -l)
						Part1LinesNr=$((Part1LinesNr - 1))
						Part1="$(printf '%s\n' "$Part1" | grep -ive "^[[]${SectionName}[]]" -ive "^ [[]${SectionName}[]]")"
						SectionContent="$(IniSectionContent "$File" "$SectionName")"
						SectionLinesNr=$(printf '%s\n' "$SectionContent" | wc -l)
						Part2="$(printf '%s\n' "$OldContent" | tail -n $((OldContentLinesNr - Part1LinesNr - SectionLinesNr)))"
						cat /dev/null > "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$Part1" != "" ] ; then
							printf '%s\n' "$Part1" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$(printf '%s\n' "$SectionContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
							printf '%s\n' "$SectionContent" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							if [ "$PreComment" != "" ] ; then
								printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
								LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
							fi
							printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						else
							printf '%s\n' "$SectionContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$Part2" != "" ] ; then
							printf '%s\n' "$Part2" >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
					else
						printf '%s\n' "[${SectionName}]" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						if [ "$PreComment" != "" ] ; then
							printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			else
				if [ "$(printf '%s\n' "$OldContent" | tr -s '\t' ' ' | tr -s ' '| grep -ie "^${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName}${NameValueSeparator}" -ie "^ ${VariableName} ${NameValueSeparator}")" = "" ] ; then
					if [ "$PreComment" != "" ] ; then
						printf '%s\n' "$PreComment" | sed -e 's|\\n|\n|g' >> "$File"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
					printf '%s\n' "${VariableName}${NameValueSeparator}${NewValue}" >> "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					printf '%s\n' "$OldContent" | sed -re "s|^([[:blank:]]*)(${VariableName})([[:blank:]]*)(${NameValueSeparator})([[:blank:]]*).*|\1\2\3\4\5${NewValue}|i" > "$File"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: SetIniVarValue: File and/or VariableName not specified.${fRESET}" 1>&2
		LastStatus=81 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

GetOrSetIniVarValue ()
# Syntax as a function: $(GetOrSetIniVarValue "$File" "$VariableName" "$SectionName" "$DefaultValue" "$NameValueSeparator" "$EndVariableSymbol" "$PreComment" "$ReadOnly" "$DefaultsFile")
# Expected parameters:
#	$1	Path/name of file to query
#	$2	Variable name to query (case insensitive)
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Separator between variable name and assigned value. Examples: "=" ":=". Default is "="
#	$6	(optional or empty) End variable/assignation mark, to be omited from value and to be written when needed. Example: ";"
#	$7	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
#	$8	(optional or empty) "1" or "ro" if $File must not be modified. Assumed no by default (0)
#	$9	(optional or empty) File path where to get DefaultValue from, if found. A found value prevails over 4th parameter (DefaultValue)
# Notes:
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- PreComment can contain escaped \n to be converted into line breaks.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on software packages: grep, sed
{
	local File="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local NameValueSeparator="$5"
	local EndVariableSymbol="$6"
	local PreComment="$7"
	local ReadOnly="$8"
	local DefaultsFile="$9"
	local NotFoundKey='GOSIVVtmpStZrypNMzntmpgKqLEd5E5ttmpIWW5wemyCW'
	local Value=''
	local LastStatus=0
	local StatusCode=0
	
	Value="$(IniVarValue "$File" "$VariableName" "$SectionName" "$NotFoundKey" "$NameValueSeparator" "$EndVariableSymbol" "$DefaultsFile")"
	if [ "$Value" = "$NotFoundKey" ] ; then
		if [ "$ReadOnly" != "1" ] && [ "$ReadOnly" != "ro" ] ; then
			SetIniVarValue "$File" "$VariableName" "$SectionName" "${DefaultValue}${EndVariableSymbol}" "$NameValueSeparator" "$PreComment"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		Value="$DefaultValue"
		# EndVariableSymbol does not come with DefaultValue
		if [ "$(printf '%s\n' "$Value" | grep -e '^"' | grep -e '"$')" != "" ] ; then
			# Double quotes
			Value="$(printf '%s\n' "$Value" | cut -f 2- -d '"' | sed -e 's|"$||')"
		else
			if [ "$(printf '%s\n' "$Value" | grep -e "^'" | grep -e "'$")" != "" ] ; then
				# Single quotes
				Value="$(printf '%s\n' "$Value" | cut -f 2- -d "'" | sed -e "s|'$||")"
			fi
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

LoadVarsValues ()
# Syntax as a sentence: LoadVarsValues $FileOrContent $VariablesNames $VarsRequired $DefaultsFileOrContent
# Descripcion: Runs global variables assignation same as specified in $FileOrContent
# Expected parameters:
#	$1	Path/name of file to query. If not exists as a file, string will be treated as content to look into.
#	$3	Variable names to filter (case sensitive, space separated). If empty "", all file variables will be loaded.
#	$2	0 = No error on variables lack. 1 = Only error if all variables lack. 2 = Result error on any variable lack (it only returns exitcode 104; no messages)
#	$4	(optional or empty) File path (or vars string) to do a previous load from, if found.
# To Do:
#	- 
# Notes:
#	- Indentation are supported (initial tabs and/or spaces).
# Depends on functions: (none)
# Depends on software packages: grep, sed
{
	lvv__FileOrContent="$1"
	lvv__VariablesNames="$2"
	lvv__VarsRequired="$3"
	lvv__DefaultsFileOrContent="$4"
	lvv__CurVarName=''
	lvv__VarsFilter=''
	lvv__LoadedContent=''
	lvv__LastStatus=0
	lvv__StatusCode=0
	
	if [ "$lvv__VariablesNames" != "" ] ; then
		for lvv__CurVarName in $lvv__VariablesNames ; do
			lvv__VarsFilter="$lvv__VarsFilter -e ^${lvv__CurVarName}="
		done
	else
		lvv__VarsFilter=" -ie ^[a-z].*="
	fi
	if [ "$lvv__DefaultsFileOrContent" != "" ] ; then
		if [ -f "$lvv__DefaultsFileOrContent" ] ; then
			lvv__DefaultsFileOrContent="$(cat "$lvv__DefaultsFileOrContent")"
			lvv__LastStatus=$? ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
		fi
		lvv__LoadedContent="$(printf '%s\n' "$lvv__DefaultsFileOrContent" | sed -e 's|^[ \t]*||g' | grep $lvv__VarsFilter)"
		if [ "$lvv__LoadedContent" != "" ] ; then
			eval $lvv__LoadedContent
		fi
	fi
	if [ "$lvv__FileOrContent" != "" ] ; then
		if [ -f "$lvv__FileOrContent" ] ; then
			lvv__FileOrContent="$(cat "$lvv__FileOrContent")"
			lvv__LastStatus=$? ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
		fi
		lvv__LoadedContent="$(printf '%s\n' "$lvv__FileOrContent" | sed -e 's|^[ \t]*||g' | grep $lvv__VarsFilter)"
		if [ "$lvv__LoadedContent" != "" ] ; then
			eval $lvv__LoadedContent
			if [ "$lvv__VarsRequired" = "2" ] && [ "$lvv__VariablesNames" != "" ] ; then
				for lvv__CurVarName in $lvv__VariablesNames ; do
					if [ "$(printf '%s' "$lvv__LoadedContent" | grep -e "^${lvv__CurVarName}=")" = "" ] ; then
						lvv__LastStatus=104 ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
					fi
				done
			fi
		else
			if [ "$lvv__VarsRequired" = "1" ] || [ "$lvv__VarsRequired" = "2" ] ; then
				lvv__LastStatus=104 ; if [ $lvv__StatusCode -eq 0 ] ; then lvv__StatusCode=$lvv__LastStatus ; fi
			fi
		fi
	fi
	return $lvv__StatusCode
}

LogToFile ()
# Syntax as a sentence: LogToFile $LogFile $DatetimeStamp $MaxLogLines $Message
# Description: Appends message to text file
# Expected parameters:
#	$1	File path to write to
#	$2	"0" to not prepend line with date-time stamp. Any other value (1) to yes write it.
#	$3	"0" to infinite log lines or a greater number to leave limited log tail
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
# Notes:
#	- This function can be called without message to only cut file to last $MaxLogLines
# Depends on functions: Is_IntegerNr Dirname
# Depends on software packages: sed
{
	local LogFile="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DatetimeStamp="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local MaxLogLines="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local AskedText=''
	local TextLinesNr=0
	local CurString=''
	local LoggingLog=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$LogFile" != "" ] ; then
		if [ "$DatetimeStamp" = "0" ] ; then
			DatetimeStamp=''
		else
#			DatetimeStamp="$(printf '%(%Y-%m-%dT%T%z)T')"  BASHISM
			DatetimeStamp="$(date '+%Y-%m-%dT%T%z') "
		fi
		for CurString in "$@" ; do	# Simple $* sometimes results in comma-separated words
			if [ "$AskedText" != "" ] ; then AskedText="${AskedText} " ; fi
			AskedText="${AskedText}$(printf '%s\n' "$CurString" | sed -e 's|\\n|\n|g')"
		done
		if [ "$AskedText" != "" ] ; then
			TextLinesNr=$(printf '%s\n' "$AskedText" | wc -l)
		else
			TextLinesNr=0
		fi
		mkdir -p "$(Dirname "$LogFile")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if Is_IntegerNr "$MaxLogLines" && [ $MaxLogLines -gt 0 ] && [ $(cat "$LogFile" 2>&1 | wc -l) -gt $((MaxLogLines+$TextLinesNr)) ] ; then
				LoggingLog="$(cp --attributes-only -a "$LogFile" "${LogFile}.tmp" 2>&1)"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] ; then
					cat "$LogFile" | tail -n $((MaxLogLines-TextLinesNr)) > "${LogFile}.tmp"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				if [ $StatusCode -eq 0 ] ; then
					mv "${LogFile}.tmp" "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				rm -f "${LogFile}.tmp"
				if [ $StatusCode -eq 0 ] ; then
					if [ $TextLinesNr -gt 0 ] ; then
						printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					# If it's out of space, then try to overwrite previous log.
					printf '%s\n' "$LoggingLog" > "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $TextLinesNr -gt 0 ] ; then
						printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
			else
				if [ $TextLinesNr -gt 0 ] ; then
					printf '%s\n' "${DatetimeStamp}${AskedText}" >> "$LogFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: Log file not specified${fRESET}" 1>&2
		LastStatus=97 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

LogProgram ()
# Syntax as a sentence: LogProgram $ThisLevel $Message
# Description: Writes message to configured log file, depending on configured LogLevel vs ThisLevel for message.
# Expected parameters:
#	$1	Level number of the message:
#		0=NothingToSay 1=Error 2=Warning 3=NormalInfo 4=DebugInfo
#		(same numbers in negative sign to not write date-time stamp)
#	(rest)	String for stdout/stderr/log
#		Escaped \n are replaced by line breaks.
# Depends on functions: Is_IntegerNr Dirname LogToFile
# Depends on software packages: perl-base sed
# Supports environment variables: LogLevel MainLog MainControllerLog INIT_SCRIPT_InitCall MaxLogLines
{
	local ThisLevel="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local CallType=''
	local LogFile=''
	local DatetimeStamp=0
	local AskedText=''
	local CurString=''
	local ProgramLogLevel=3
	local RedirectedStdout=''
	local LoggingLog=''
	local LastStatus=0
	local StatusCode=0
	
	perl -e "exit${ParO}-p STDOUT ? 1 : 0${ParC};"  #'
	RedirectedStdout=$?
	if [ $RedirectedStdout -ne 0 ] ; then
		sWARN=''
		fRESET=''
		sERROR=''
		sGOOD=''
	fi
	if [ $ThisLevel -ge 0 ] ; then
		DatetimeStamp=1
	fi
	ThisLevel="$(printf '%s\n' "$ThisLevel" | sed -e 's|^-||g')"
	if Is_IntegerNr "$LogLevel" ; then ProgramLogLevel=$LogLevel ; fi
	if [ "$*" != "" ] && [ $ProgramLogLevel -ge $ThisLevel ] && [ $ThisLevel -gt 0 ] ; then
		for CurString in "$@" ; do	# Simple $* sometimes results in comma-separated words
			if [ "$AskedText" != "" ] ; then AskedText="${AskedText} " ; fi
			AskedText="${AskedText}$(printf '%s\n' "$CurString" | sed -e 's|\\n|\n|g')"
		done
		if [ $ThisLevel -eq 3 ] ; then
			printf '%s\n' "$AskedText"
		else
			if [ $ThisLevel -eq 2 ] ; then
				printf '%s\n' "${sWARN}${AskedText}${fRESET}" 1>&2
			else
				if [ $ThisLevel -eq 1 ] ; then
					printf '%s\n' "${sERROR}${AskedText}${fRESET}" 1>&2
				else
					printf '%s\n' "$AskedText" 1>&2
				fi
			fi
		fi
		LogFile="$MainLog"
		if [ "$LogFile" = "" ] && [ "$MainControllerLog" != "" ] ; then
			LogFile="$MainControllerLog"
			if [ "$INIT_SCRIPT_InitCall" != "1" ] ; then
				CallType=" ${ParO} Manual ${ParC} "
			fi
		fi
		if [ "$LogFile" != "" ] ; then
			LoggingLog="$(LogToFile "$LogFile" "$DatetimeStamp" "$MaxLogLines" "${CallType}${AskedText}")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $LastStatus -ne 0 ] ; then
				printf '%s\n' "${sERROR}${LoggingLog}${fRESET}" 1>&2
			fi
		fi
	fi
	return $StatusCode
}


##### PROGRAM TEMPLATE BASE FUNCTIONS #####

BreakingControls ()
{
	if [ $(id -g) -ne 0 ] && [ "$RootRequired" != "0" ] ; then
		printf '%s\n' "${sERROR}E: This program needs to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
		exit 45
	fi
}

DeleteIfNotMe ()
{
	local FileToDelete="$1"
	local LastStatus=0
	
	if [ "$FileToDelete" != "" ] && [ "$FileToDelete" != "$MeCallFile" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeCallFile" ] && [ "$FileToDelete" != "$MeExecutable" ] && [ "$(ReadlinkF "$FileToDelete")" != "$MeExecutable" ] ; then
		rm -fr "$FileToDelete"
		LastStatus=$?
	fi
	return $LastStatus
}

FoundProgramScript ()
{
	local Value=""
	if [ -x "/bin/${ProgramName}" ] ; then Value="/bin/${ProgramName}" ; fi
	if [ -x "/sbin/${ProgramName}" ] ; then Value="/sbin/${ProgramName}" ; fi
	if [ -x "/usr/bin/${ProgramName}" ] ; then Value="/usr/bin/${ProgramName}" ; fi
	if [ -x "/usr/sbin/${ProgramName}" ] ; then Value="/usr/sbin/${ProgramName}" ; fi
	if [ -x "/usr/local/bin/${ProgramName}" ] ; then Value="/usr/local/bin/${ProgramName}" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}" ] ; then Value="/usr/local/sbin/${ProgramName}" ; fi
	if [ -x "/bin/${ProgramName}.sh" ] ; then Value="/bin/${ProgramName}.sh" ; fi
	if [ -x "/sbin/${ProgramName}.sh" ] ; then Value="/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/bin/${ProgramName}.sh" ] ; then Value="/usr/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/sbin/${ProgramName}.sh" ] ; then Value="/usr/sbin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/bin/${ProgramName}.sh" ] ; then Value="/usr/local/bin/${ProgramName}.sh" ; fi
	if [ -x "/usr/local/sbin/${ProgramName}.sh" ] ; then Value="/usr/local/sbin/${ProgramName}.sh" ; fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

ScriptHeaderValue ()
# Note: it's absolutely case-sensitive
{
	local HeaderKey="$1"
	local VariableKey=""
	local Value=""
	
	if [ "$(cat "$MeExecutable" | grep -e '^:.*SCRIPTHEADER$')" != "" ] && [ "$(cat "$MeExecutable" | grep -e '^SCRIPTHEADER$')" != "" ] ; then
		Value="$(echo aaa$(cat "$MeExecutable" | sed -ne '/^:.*SCRIPTHEADER$/,//p' | sed -e '/^SCRIPTHEADER$/q' | grep -e "^${HeaderKey}:" | head -n 1 | cut -f 2- -d ':') | sed -e 's|^aaa||g' | sed -e 's|^ ||g')"
		if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
	fi
}

MeInstalled ()
# Returns 1 if this script is placed as system installed path.
{
	local Value=0
	
	if [ "$(printf '%s\n' "$MeExecutable" | grep -e 'bin/')" != "" ] ; then
		Value=1
	fi
	printf '%s\n' "$Value"
}

Install_precp ()
# WARNING: Package manager cannot invoke this action before this script file is installed. Install_postcp() calls this with argument "postcp" if didn't run.
{
	local Mode="$1"
	local Mode2="$2"
	local TheFoundProgramScript=''
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] ; then
		Install_precp_Pre "$(Dirname "$MeExecutable")" "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	TheFoundProgramScript="$(FoundProgramScript)"
	if [ $StatusCode -eq 0 ] && [ -x "$TheFoundProgramScript" ] ; then
		if [ "$Mode" != "postcp" ] ; then
			if [ "$PackageManager_Call" = "" ] && [ "$Mode" != "force" ] && [ "$Mode2" != "force" ] ; then
#				if [ "$(IniVarValue "$(cat "$TheFoundProgramScript" | grep -e '^ProgramInstaller=' | head -n 1)" ProgramInstaller '' '' = '' '')" = "0" ] ; then
				if [ "$(cat "$TheFoundProgramScript" | grep -e '^ProgramInstaller=' | head -n 1 | grep -e '^ProgramInstaller=0')" != "" ] ; then
					printf '%s\n' "${sERROR}E: Program already installed. Use your package manager to un/install $(printf '%s\n' "$TheFoundProgramScript" | tr -s '/' '\n' | tail -n 1).${fRESET}" 1>&2
					LastStatus=99 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				printf '%s\n' "Trying to uninstall previous $ProgramName"
				"$TheFoundProgramScript" uninstall "$@"
				LastStatus=$?
				if [ $LastStatus -eq 87 ] || [ $LastStatus -eq 127 ] ; then # Unknown action
					rm "$TheFoundProgramScript"
					LastStatus=$?
				fi
				if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ -x "$TheFoundProgramScript" ] ; then
					printf '%s\n' "${sWARN}W: old executable $TheFoundProgramScript not removed.${fRESET}" 1>&2
					printf '%s\n' "Actions recommended:"
					printf '%s\n' "	1. Retry un/installation"
					printf '%s\n' "	2. Remove old program manually if necessary"
				fi
				if [ $StatusCode -eq 0 ] ; then
					# Important to do this after uninstalling previous version.
					Configuration "$@"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ -d "$SystemConfigDir" ] ; then
						printf '%s\n' "System level configurations directory is: $SystemConfigDir"
					fi
					printf '%s\n' "System level parameters can be at: $SystemConfigFile"
				fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		Install_precp_Post "$(Dirname "$MeExecutable")" "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		# Old executables were a duplicity of program script.
		DeleteIfNotMe "/bin/${ProgramName}"
		DeleteIfNotMe "/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/bin/${ProgramName}"
		DeleteIfNotMe "/usr/sbin/${ProgramName}"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
		DeleteIfNotMe "/bin/${ProgramName}.sh"
		DeleteIfNotMe "/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/sbin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/bin/${ProgramName}.sh"
		DeleteIfNotMe "/usr/local/sbin/${ProgramName}.sh"
		DeleteIfNotMe "$InitExecutableLink"
		DeleteIfNotMe "$ProgramExecutablePath"
	fi
	touch "${DirTemp}/${ProgramName}.install-precp.done"
	return $StatusCode
}

Install_cp ()
{
	local SourceScript="$1"
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] ; then
		if [ "$SourceScript" != "$ProgramExecutablePath" ] && [ "$SourceScript" != "$InitExecutableLink" ] ; then
			PreviousUmask="$(umask)"
			umask u=rwx,go=rx
			mkdir -p "$(Dirname "$ProgramExecutablePath")"
			umask "$PreviousUmask"
			cp "$SourceScript" "$ProgramExecutablePath"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			chown root:root "$ProgramExecutablePath"
			chmod u=rwx,go=rx "$ProgramExecutablePath"
		else
			printf '%s\n' "${sERROR}E: Cannot install from destination path.${fRESET}" 1>&2
			LastStatus=94 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

Install_postcp ()
{
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -f "${DirTemp}/${ProgramName}.install-precp.done" ] ; then
		Install_precp postcp
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		rm -f "${DirTemp}/${ProgramName}.install-precp.done"
	fi
	if [ $StatusCode -eq 0 ] ; then
		Install_postcp_More "$(Dirname "$MeExecutable")" "$@"	#"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		# Writing system-wide defaults
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Uninstall_predel ()
{
	local LastStatus=0
	local StatusCode=0
	
	Uninstall_predel_More "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Uninstall_del ()
{
	local MeVersion=''
	MeVersion="$(ScriptHeaderValue Version)"
#	DeleteIfNotMe "$ProgramExecutablePath"
#	DeleteIfNotMe "/bin/${ProgramName}"
#	DeleteIfNotMe "/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/sbin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/bin/${ProgramName}"
#	DeleteIfNotMe "/usr/local/sbin/${ProgramName}"
	rm -f "/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/bin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "/usr/local/sbin/${ProgramName}.sh"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	rm -f "$ProgramExecutablePath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
		printf '%s\n' "$ProgramName $MeVersion : Program files removed."
	fi
}

Uninstall_postdel ()
{
	local LastStatus=0
	local StatusCode=0
	
	Uninstall_Postdeletion "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Uninstall_purge ()
{
	local LastStatus=0
	local StatusCode=0
	
	rm -f "${DirTempX}/${ProgramName}.uninstall.tmp" "/var/tmp/${ProgramName}.uninstall.tmp"
	Uninstall_purge_MorePre "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ -f "$SystemConfigFile" ] ; then
		printf '%s\n' "Removing: $SystemConfigFile"
		rm "$SystemConfigFile"
	fi
	if [ -d "$SystemConfigDir" ] ; then
		printf '%s\n' "Removing: $SystemConfigDir"
		rm -r "$SystemConfigDir"
	fi
	if [ "$ChildsPluralName" != "" ] ; then
		rm -fr "$Childs_logs"
	fi
	rm -f "$MainLog"
	rm -f "/var/log/upstart/${ProgramName}."*
	Uninstall_purge_MorePost "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $LastStatus -eq 0 ] ; then
		printf '%s\n' "Configurations and logs cleaned."
	fi
	return $StatusCode
}

GetSetLocalConfig ()
# Syntax as a function: $(GetSetLocalConfig "$ReadOnly" "$VariableName" "$SectionName" "$DefaultValue" "$PreComment")

# Function taken from script.sh template.

# Description: Gets the most local value for a configuration (1:user 2:system 3:filedefault 4:specifieddefault) and optionally writes if nowhere found.
# Expected parameters:
#	$1	"1" to not write anything at files; "0" to save variable when not found.
#	$2	Variable name to query
#	$3	(optional or empty) [section name] without brackets []. Examples: "global" ""
#	$4	(optional or empty) Value to return in case of not finding the variable in any file. It's used to write the new variable entry too.
#	$5	(optional or empty) Line to precede variable's line if it must be added. Comment mark should be included.
# Notes:
#	- NOT USEFUL when default value can be invalid for unprivileged user, such as file/dir paths to write: [system.conf=]/etc/custom.list vs [user.conf=]~/custom.list
#	  NOT USEFUL for other config files but SystemConfigFile & UserConfigFile
#	  In these cases, it's convenient to use GetOrSetIniVarValue()
#	- USEFUL when need to use system-level value on user-level lack.
#	- $SystemDefaults is used as DefaultsFile. A found value here prevails over 4th parameter (DefaultValue)
#	- If DefaultValue is to be written (because no value found anywhere and ReadOnly==0), root user will write to $SystemConfigFile and others will write to $UserConfigFile
#	- if section is not specified or empty, treats whole file content.
#	- if section is "[]", treats only file content before any [section] declaration.
#	- Separator between variable name and assigned value is assumed to be '='
#	- No end variable/assignation mark assumed.
# Depends on functions: IniVarValue SetIniVarValue
# Depends on other software: grep
{
	local ReadOnly="$1"
	local VariableName="$2"
	local SectionName="$3"
	local DefaultValue="$4"	# If quotes are needed ("value") must be already contained in the supplied value ("\"value\""). Same with EndVariableSymbol.
	local PreComment="$5"
	local DefaultsFile="$SystemDefaults"
	local NotFoundKey=''
	local Value=''
	local LastStatus=0
	local StatusCode=0
	
	NotFoundKey="#GOSIVV$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d ' ')#"
	Value="$(IniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' '')"
	if [ "$Value" = "$NotFoundKey" ] ; then
		Value="$(IniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$NotFoundKey" = '' "$DefaultsFile")"
		if [ "$Value" = "$NotFoundKey" ] ; then
			if [ $ReadOnly -eq 0 ] ; then
				if [ $(id -u) -eq 0 ] ; then
					SetIniVarValue "$SystemConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					SetIniVarValue "$UserConfigFile" "$VariableName" "$SectionName" "$DefaultValue" '=' "$PreComment"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
			Value="$DefaultValue"
			if [ "$(printf '%s\n' "$Value" | grep -e '^".*"$')" != "" ] ; then Value="$(printf '%s\n' "$Value" | cut -f 2 -d '"')" ; fi	#'
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s' "$Value" ; fi
	return $StatusCode
}

Configuration ()
{
	local ReadOnly=0
	local BaseName=''
	local LastStatus=0
	local StatusCode=0

	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
	MeExecutable="$(ReadlinkF "$MeCallFile")"
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	BreakingControls
	if [ "$1" = "pmc" ] ; then
		PackageManager_Call="$2"
		PackageManager_Mode="$3"
		PackageManager_ForVersion="$4"
	else
		PackageManager_Call=''
	fi
	if [ "$RootRequired" = "0" ] ; then
		ProgramExecutablePath="/bin/${ProgramName}"
	else
		ProgramExecutablePath="/sbin/${ProgramName}"
	fi
#	if [ "$EssentialAtBoot" != "1" ] ; then  # FHS /usr Merge
		ProgramExecutablePath="/usr${ProgramExecutablePath}"
#	fi
	Configuration_Saved_Pre "$@"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ ! -d "$SystemConfigDir" ] && [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigDir")" ] ; then
		mkdir -p "$SystemConfigDir"
		chmod u=rwX,go=rX "$SystemConfigDir"
	fi
	SystemDefaults="${SystemConfigDir}/system.defaults"	# This file is never written by program; only provided by package manager and updates
	SystemConfigFile="${SystemConfigDir}/system.conf"
	if [ -f "${SystemConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] && [ $(id -u) -eq 0 ] ; then
			mv "${SystemConfigDir}/default.conf" "$SystemConfigFile"
		else
			SystemConfigFile="${SystemConfigDir}/default.conf"
		fi
	fi
	BaseName="$(printf '%s\n' "$SystemConfigDir" | tr -s '/' '\n' | tail -n 1)"
	UserDir="$HOME"
	if [ "$(printf '%s\n' "$BaseName" | grep -e '^\.')" != "" ] ; then
		UserConfigDir="${UserDir}/${BaseName}"
	else
		UserConfigDir="${UserDir}/.${BaseName}"
	fi
	if [ ! -d "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
	if [ ! -w "$UserDir" ] ; then
		UserDir="$(pwd)"
		UserConfigDir="${UserDir}/${BaseName}"
	fi
	UserDefaults="${UserConfigDir}/user.defaults"	# This file is never written by program; only provided by package manager/skel
	UserConfigFile="${UserConfigDir}/user.conf"
	if [ $ReadOnly -eq 0 ] && [ ! -d "$UserConfigDir" ] ; then
		mkdir -p "$UserConfigDir"
	fi
	if [ -f "${UserConfigDir}/default.conf" ] ; then
		# Files structure upgrade
		if [ $ReadOnly -eq 0 ] ; then
			mv "${UserConfigDir}/default.conf" "$UserConfigFile"
		else
			UserConfigFile="${UserConfigDir}/default.conf"
		fi
	fi
	if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$SystemConfigFile")" ] ; then
		if [ ! -f "$SystemConfigFile" ] ; then
			printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$SystemConfigFile"
			if [ "$RootRequired" = "0" ] ; then
				printf '%s\n' "# $ProgramName system-wide and user-default working parameters" >> "$SystemConfigFile"
			else
				printf '%s\n' "# $ProgramName working parameters" >> "$SystemConfigFile"
			fi
			printf '%s\n' "" >> "$SystemConfigFile"
		fi
	fi
	CurrentConfigDir="$SystemConfigDir"
	CurrentConfigFile="$SystemConfigFile"
	if [ $(id -u) -ne 0 ] && [ "$RootRequired" = "0" ] ; then
		CurrentConfigDir="$UserConfigDir"
		CurrentConfigFile="$UserConfigFile"
		if [ $ReadOnly -eq 0 ] && [ -w "$(Dirname "$UserConfigFile")" ] ; then
			if [ ! -f "$UserConfigFile" ] ; then
				printf '%s\n' "# $(ScriptHeaderValue Description)" >> "$UserConfigFile"
				printf '%s\n' "# $ProgramName user working parameters. This overrides configurations over $SystemConfigFile" >> "$UserConfigFile"
				printf '%s\n' "" >> "$UserConfigFile"
			fi
		fi
	fi
	MainLog="/var/log/${ProgramName}.log"
	if [ $(id -u) -ne 0 ] ; then
		MainLog="${UserConfigDir}/main.log"
	fi
	if [ "$LogLevel" != "4" ] ; then	# Debugging probably a nested call (exported LogLevel)
		LoadVarsValues "$UserConfigFile" 'LogLevel MaxLogLines' 2 "$SystemConfigFile"
		LastStatus=$?
		if [ $LastStatus -eq 104 ] ; then
			LogLevel="$(GetSetLocalConfig "$ReadOnly" LogLevel '' 3 '# LogLevel: 0=Nothing 1=Errors 2=Warnings+E 3=Info+W+E 4=Debug')"
			MaxLogLines="$(GetSetLocalConfig "$ReadOnly" MaxLogLines '' 0 '# MaxLogLines: 0=Unlimited')"
		else
			if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if ! Is_IntegerNr "$LogLevel" ; then LogLevel=3 ; fi
	fi
	if [ $ReadOnly -eq 0 ] ; then
		Configuration_Saved_Post "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		Configuration_Saved_Post ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}


##### PROGRAM FUNCTIONS from @-funcions 0:2024.01.12 #####

#[admidb]
DfOutputValue ()
# Description: Parser for DF command, that emulates --output option for old versions without support for it. Only returns (stdout) the one asked output value.
# ToDo:
#	- Implement "no Path" to list all volumes for specified OutputField
#	- Implement alternative method to get fstype when using BusyBox (eg. in Maemo)
#	- Try to not depend on bc
# Notes:
#	- If no third parameter is specified (-b -k -m -g), sizes are returned in bytes.
#	- As of df v8, some filesystems (eg: btrfs) don't report inodes information and returned value can be "-"
# Depends on functions: Is_Executable ReadlinkF
# Depends on software packages: perl-base, grep, bc, sed
{
	local OutputField="$1"
	local Path="$2"
	local Unit="$3"
	local BlockSize=1
	local BlockFactor=1
	local LineInodes=''
	local LineSpace=''
	local WordsInodes=''
	local WordsSpace=''
	local ConvertSize=0
	local LastStatus=0
	local Posix='P'
	local PrintType='T'
	local Value=''
	
	case "$Unit" in
		"-b" ) BlockSize=1 ;;
		"-k" ) BlockSize=1024 ;;
		"-m" ) BlockSize=$((1024*1024)) ;;
		"-g" ) BlockSize=$((1024*1024*1024)) ;;
		* ) BlockSize=1 ;;
	esac
	df "$Path" >/dev/null 2>&1
	LastStatus=$?
	if [ $LastStatus -ne 0 ] ; then
		# /dev/simfs (VPS) doesn't work; let's detect mountpoint
		Path="$(df | grep -e "^${Path} " | tr -s '%' '\n' | sed -e 's|^ ||g' | tail -n 1)"
	fi
	if [ "$(df --help 2>&1 | grep -e '--output')" != "" ] ; then
		df "--output=${OutputField}" "$Path" >/dev/null 2>&1
		LastStatus=$?
		if [ $LastStatus -eq 0 ] ; then
			Value="$(env LANG=en df --block-size=$BlockSize "--output=${OutputField}" "$Path" 2>/dev/null | tail -n 1)"
		fi
	else
		df -P "$Path" >/dev/null 2>&1
		LastStatus=$?
		if [ $LastStatus -ne 0 ] ; then
			Posix=''
			df "$Path" >/dev/null 2>&1
			LastStatus=$?
		fi
		if [ $LastStatus -eq 0 ] ; then
			# Note: Different GNU distributions have complete different alignment policy.
			#       Most of fields is better to get counting words backwards since the common % symbol.
			#	This method is incompatible with devices with % symbol in path.
			df -T "$Path" >/dev/null 2>&1
			LastStatus=$?
			if [ $LastStatus -ne 0 ] ; then
				PrintType=''
			fi
			df -i "$Path" >/dev/null 2>&1
			LastStatus=$?
			if [ $LastStatus -eq 0 ] ; then
				LineInodes="$(env LANG=en df -${Posix}${PrintType}i "$Path" | tail -n 1)"
			fi
			if [ "${Posix}${PrintType}" != "" ] ; then
				LineSpace="$(env LANG=en df -${Posix}${PrintType} --block-size=$BlockSize "$Path" 2>/dev/null)"
			else
				LineSpace="$(env LANG=en df --block-size=$BlockSize "$Path" 2>/dev/null)"
			fi
			LastStatus=$?
			if [ $LastStatus -ne 0 ] ; then
				if [ "${Posix}${PrintType}" != "" ] ; then
					LineSpace="$(env LANG=en df -${Posix}${PrintType} -k "$Path")"
				else
					LineSpace="$(env LANG=en df -k "$Path")"
				fi
				BlockFactor=1024
				if [ $BlockSize -ge $BlockFactor ] ; then
					BlockFactor=$((BlockSize / BlockFactor))
				else
					BlockFactor=-1024
				fi
			fi
			LineSpace="$(printf '%s\n' "$LineSpace" | tail -n 1)"
			if [ "$(printf '%s' "$LineInodes" | grep -e '%')" = "" ] ; then
				# Optical media
				LineInodes="$(printf '%s' "$LineInodes" | sed -e 's| - | 100% |g')"
			fi
			WordsInodes="$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 1 -d '%' | tr -s ' ' '\n')"
			WordsSpace="$(echo TrimAndSingle $LineSpace | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 1 -d '%' | tr -s ' ' '\n')"
			case "$OutputField" in
				"source" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 5 | head -n 1)	# Get fstype to use as field splitter
					Value="$(printf '%s' "$LineInodes" | perl -pe "s| ${Value} |\n|g" | head -n 1)"
					;;
				"fstype" )
					if [ "$PrintType" != "" ] ; then
						Value=$(printf '%s' "$WordsInodes" | tail -n 5 | head -n 1)
					fi
					;;
				"itotal" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 4 | head -n 1)
					;;
				"iused" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 3 | head -n 1)
					;;
				"iavail" )
					Value=$(printf '%s' "$WordsInodes" | tail -n 2 | head -n 1)
					;;
				"ipcent" )
					Value=$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | tr -s ' ' '\n' | grep -e '%' | head -n 1)
					;;
				"size" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 4 | head -n 1)
					ConvertSize=1
					;;
				"used" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 3 | head -n 1)
					ConvertSize=1
					;;
				"avail" )
					Value=$(printf '%s' "$WordsSpace" | tail -n 2 | head -n 1)
					ConvertSize=1
					;;
				"pcent" )
					Value=$(echo TrimAndSingle $LineSpace | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | tr -s ' ' '\n' | grep -e '%' | head -n 1)
					;;
				"target" )
					Value=$(echo TrimAndSingle $LineInodes | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g' | cut -f 2- -d ' ' | cut -f 2- -d '%')
					;;
			esac
		fi
		if [ $ConvertSize -eq 1 ] && [ $BlockFactor -ne 1 ] ; then
			if Is_Executable bc ; then
				if [ $BlockFactor -ge 0 ] ; then
					Value=$(printf '%s\n' "$Value / $BlockFactor" | bc)
				else
					Value=$(printf '%s\n' "$Value * $BlockFactor" | bc)
				fi
			else
				if [ $BlockFactor -ge 0 ] ; then
					Value=$((Value / BlockFactor))
				else
					Value=$((Value * BlockFactor))
				fi
			fi
		fi
	fi
	if [ "$Value" != "" ] ; then
		if [ "$OutputField" = "source" ] && [ "$(printf '%s' "$Value" | grep -e '^/')" != "" ] && [ "$(ReadlinkF "$Value" 2>/dev/null)" != "" ] ; then
			Value="$(ReadlinkF "$Value")"
		fi
		# Trim:
		Value="$(expr "$Value" : "[ ]*\(.*[^ ]\)[ ]*$")"
		printf '%s\n' "$Value"
	fi
}

StatFormatValue ()
# Description: Parser for STAT command, that emulates --format option for old versions without support for it. Only returns (stdout) the one asked output value.
# Notes:
#	--file-system is not used.
# ToDo:
#	- Try to not depend on bc
# Depends on functions: DfOutputValue Lowercase
# Depends on software packages: grep, sed, bc
{
	local FormatField="$1"
	local Path="$2"
	local Value=''
	
	if [ -e "$Path" ] ; then
		if [ "$(stat --help 2>&1 | grep -e '--format')" != "" ] ; then
			stat "--format=${FormatField}" "$Path"
		else
			FormatField="$(printf '%s\n' "$FormatField" | sed -e 's|^%||g')"
			case "$FormatField" in
				"a" )	# access rights in octal
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:.*(.*/.*)' | cut -f 2 -d '(' | cut -f 1 -d '/')"
					;;
				"A" )	# access rights in human readable form
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:.*(.*/.*)' | cut -f 1 -d ')' | cut -f 2 -d '/')"
					;;
				"b" )	# number of blocks allocated (see %B)
					Value="$(env LANG=en stat "$Path" | grep -e 'Blocks:' | sed -e 's|.*Blocks:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"B" )	# the size in bytes of each block reported by %b
					Value=''
					;;
				"C" )	# SELinux security context string
					Value='?'
					;;
				"d" )	# device number in decimal
					Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					Value="$(printf '%s\n' "$Value" | sed -e 's|.*/||g' | sed -e 's|d$||g')"
					;;
				"D" )	# device number in hex
					Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					Value="$(printf '%s\n' "$Value" | sed -e 's|/.*||g')"	# Only when stat presents it
					Value="$(printf '%s\n' "$Value" | grep -ie 'h$' | sed -e 's|h$||g')"
					if [ "$Value" = "" ] ; then
						Value="$(env LANG=en stat "$Path" | grep -e 'Device:' | sed -e 's|.*Device:||g' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#						Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
						Value="$(printf '%s\n' "$Value" | sed -e 's|.*/||g' | sed -e 's|d$||g')"
						if [ "$Value" != "" ] ; then
							Value="$(printf '%s\n' "obase=16; $Value" | bc 2>/dev/null)"
							if [ "$Value" != "" ] ; then Value="$(Lowercase "$Value")" ; fi
						fi
					fi
					;;
				"f" )	# raw mode in hex
					Value=''
					;;
				"F" )	# file type
					Value="$(env LANG=en stat "$Path" | grep -e 'Blocks:' | sed -e 's|.*:||g')"
					Value=$(NextWords () { shift ; printf '%s' "$*"; }; NextWords $Value)
					;;
				"g" )	# group ID of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:.*(.*/.*)' | perl -pe 's|.*Uid:||gi' | cut -f 2 -d '(' | cut -f 1 -d '/' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"G" )	# group name of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:.*(.*/.*)' | perl -pe 's|.*Uid:||gi' | cut -f 1 -d ')' | cut -f 2 -d '/')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"h" )	# number of hard links
					Value="$(env LANG=en stat "$Path" | grep -ie 'Links:..*' | perl -pe 's|.*Links:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"i" )	# inode number
					Value="$(env LANG=en stat "$Path" | grep -ie 'Inode:..*' | perl -pe 's|.*Inode:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"m" )	# mount point
					Value="$(DfOutputValue target "$Path" | grep -ve '^/dev$')"
					;;
				"n" )	# file name
					Value="$(env LANG=en stat "$Path" | grep -ie 'File:.*"' | cut -f 2 -d '"')"
					;;
				"N" )	# quoted file name with dereference if symbolic link
					Value="'$(env LANG=en stat "$Path" | grep -ie 'File:.*"' | cut -f 2 -d '"')'"
					if [ "$(ls -l "$Path" | grep -e "$Path -> ")" != "" ] ; then
						Value="${Value} -> '$(ls -l "$Path" | sed -e "s|.*${Path} -> ||g")'"	#'
					fi
					;;
				"o" )	# optimal I/O transfer size hint
					Value=''
					;;
				"s" )	# total size, in bytes
					Value="$(env LANG=en stat "$Path" | grep -ie 'Size:..*' | perl -pe 's|.*Size:||gi' | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"t" )	# major device type in hex
					Value=''
					;;
				"T" )	# minor device type in hex
					Value=''
					;;
				"u" )	# user ID of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:..*' | perl -pe 's|.*Uid:||gi' | tr -s '\t' ' ')"
					Value="$(printf '%s\n' "$Value" | cut -f 2 -d '(' | cut -f 1 -d '/' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
#					Value=$(SomeWord () { printf '%s' $1; }; SomeWord $Value)
					;;
				"U" )	# user name of owner
					Value="$(env LANG=en stat "$Path" | grep -ie 'Uid:..*' | perl -pe 's|.*Uid:||gi')"
					Value="$(printf '%s\n' "$Value" | cut -f 1 -d ')' | cut -f 2 -d '/')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"w" )	# time of file birth, human-readable; - if unknown
					Value="$(env LANG=en stat "$Path" | grep -ie 'Birth:..*' | perl -pe 's|.*Birth:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ "$Value" = "" ] ; then Value="-" ; fi
					;;
				"W" )	# time of file birth, seconds since Epoch; 0 if unknown
					Value="$(env LANG=en stat "$Path" | grep -ie 'Birth:..*' | perl -pe 's|.*Birth:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"x" )	# time of last access, human-readable [file content read, except if 'noatime' in effect]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:..*'  | grep -ive 'Access:.*(.*/.*)' | perl -pe 's|.*Access:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"X" )	# time of last access, seconds since Epoch [file content read, except if 'noatime' in effect]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Access:..*'  | grep -ive 'Access:.*(.*/.*)' | perl -pe 's|.*Access:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"y" )	# time of last modification, human-readable [file content changes]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"Y" )	# time of last modification, seconds since Epoch [file content changes]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
				"z" )	# time of last change, human-readable [both file content changes and metadata only changes: owner, time, permissions, etc.]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Change:..*' | perl -pe 's|.*Change:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					;;
				"Z" )	# time of last change, seconds since Epoch [both file content changes and metadata only changes: owner, time, permissions, etc.]
					Value="$(env LANG=en stat "$Path" | grep -ie 'Modify:..*' | perl -pe 's|.*Modify:||gi')"
					Value="$(echo TrimAndSingle $Value | sed -e 's|^TrimAndSingle||g' -e 's|^ ||g')"
					if [ ${#Value} -gt 4 ] ; then
						Value="$(date -d "$Value" '+%s')"
					else
						Value=0
					fi
					;;
			esac
		fi
	fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

MkdirPP ()
# Syntax as a sentence: MkdirPP $NewPath [$OwningForNewElements] [$PermissionsForNewElements] [$MorePermissions]
# Description: Same job as "mkdir -p" but setting specified owner and permissions to each new subdirectory
# Expected parameters:
#	$1	Directory path to create as necessary
#	$2	(optional) User, :Group or User:Group specification as allowed by chown. If no specified, will take setting from existing tree
#	$3	(optional) Permissions specification as allowed by chmod. If no specified, will take setting from existing tree
#	$4	(optional) Additional permissions specification to set (useful for g+s not being applied by MkfilePP)
# Notes:
#	- Owners/permissions are only set when creating subdirectories.
# Depends on functions: StatFormatValue
# Depends on software packages: (none)
{
	local NewPath="$1"
	local OwningForNewElements="$2"
	local PermissionsForNewElements="$3"
	local MorePermissions="$4"
	local CurrentElement=''
	local PreviousDir=''
	local LastStatus=0
	local StatusCode=0
	
	PreviousDir="$(pwd)"
	IFS="$(printf "/")" ; for CurrentElement in $NewPath ; do unset IFS
		if [ $StatusCode -eq 0 ] ; then
			if [ "$CurrentElement" = "" ] ; then
				cd /
			else
				if [ -e "$CurrentElement" ] ; then
					cd "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					if [ "$PermissionsForNewElements" = "" ] || [ "$PermissionsForNewElements" = "." ] ; then
						PermissionsForNewElements="$(StatFormatValue %a .)"
					fi
					if [ "$OwningForNewElements" = "" ] || [ "$OwningForNewElements" = "." ] ; then
						OwningForNewElements="$(StatFormatValue %U .)"
						OwningForNewElements="${OwningForNewElements}:$(StatFormatValue %G .)"
					fi
					mkdir "$CurrentElement"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ $StatusCode -eq 0 ] ; then
						if [ "$OwningForNewElements" != "" ] && [ "$OwningForNewElements" != "." ] ; then
							chown "$OwningForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$PermissionsForNewElements" != "" ] && [ "$PermissionsForNewElements" != "." ] ; then
							chmod "$PermissionsForNewElements" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						if [ "$MorePermissions" != "" ] && [ "$MorePermissions" != "." ] ; then
							chmod "$MorePermissions" "$CurrentElement"
							LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
						fi
						cd "$CurrentElement"
					fi
				fi
			fi
		fi
	done
	cd "$PreviousDir"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

MkfilePP ()
# Syntax as a sentence: MkfilePP $NewFile [$OwningForNewElements] [$PermissionsForNewElements] [$MorePermissions]
# Description: Creates a file only if not exists, and then sets it specified owner and permissions
# Expected parameters:
#	$1	Path to create as necessary
#	$2	(optional) User, :Group or User:Group specification as allowed by chown. If no specified, will take setting from existing tree
#	$3	(optional) Permissions specification as allowed by chmod. If no specified, will take setting from existing tree
#	$4	(optional) Additional permissions specification to set (useful for g+s only being to directories)
# Notes:
#	- Owners/permissions are only set when creating the file.
#	- If directory path does not exist, it's created with MkdirPP using same parameters.
# Depends on functions: MkdirPP Dirname StatFormatValue
# Depends on software packages: (none)
{
	local NewFile="$1"
	local OwningForNewElements="$2"
	local PermissionsForNewElements="$3"
	local MorePermissions="$4"
	local TheDirname=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -e "$NewFile" ] && [ ! -L "$NewFile" ] ; then
		TheDirname="$(Dirname "$NewFile")"
		MkdirPP "$TheDirname" "$OwningForNewElements" "$PermissionsForNewElements" "$MorePermissions" 2>/dev/null
		touch "$NewFile"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$OwningForNewElements" = "" ] || [ "$OwningForNewElements" = "." ] ; then
			OwningForNewElements="$(StatFormatValue %U "$TheDirname")"
			OwningForNewElements="${OwningForNewElements}:$(StatFormatValue %G "$TheDirname")"
		fi
		if [ "$OwningForNewElements" != "" ] && [ "$OwningForNewElements" != "." ] ; then
			chown "$OwningForNewElements" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$PermissionsForNewElements" = "" ] || [ "$PermissionsForNewElements" = "." ] ; then
			PermissionsForNewElements="$(StatFormatValue %a "$TheDirname")"
		fi
		if [ "$PermissionsForNewElements" != "" ] && [ "$PermissionsForNewElements" != "." ] ; then
			chmod "$PermissionsForNewElements" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ "$MorePermissions" != "" ] && [ "$MorePermissions" != "." ] && [ "$MorePermissions" != "g+s" ] ; then
			chmod "$MorePermissions" "$NewFile"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

DesempaquetarContenido ()
# Extrae un fichero comprimido en el directorio actual
# Depends on functions: Is_Executable Is_IntegerNr
# Depends on software packages: sed, tar
# Recommends other software: cstream (>= 2.0), gzip (>= 1.0), bzip2 (>= 1.0), lzma | xz-utils, p7zip | p7zip-full
{
	local PackageFilePath="$1"
	local DecryptionPassword="$2"
	local ExtensionPaquete=''
	local SubExtensionPaquete=''
	local BandwidthLimit="$bwlimit"
	local CompressBinary=''
	local ComposedSpecs=''
	local OutputFile=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	if [ ! -d "$DirTempX" ] ; then DirTempX="/tmp" ; fi
	if ! Is_IntegerNr "$BandwidthLimit" ; then BandwidthLimit=0 ; fi
	if [ $BandwidthLimit -gt 0 ] && ! Is_Executable cstream ; then BandwidthLimit=0 ; fi
	ExtensionPaquete="$(printf '%s' "$PackageFilePath" | sed -e "s/\./\n/g" | tail -n 1)"
	SubExtensionPaquete="$(printf '%s' "$PackageFilePath" | sed -e "s/\./\n/g" | tail -n 2 | head -n 1)"
	if [ "$SubExtensionPaquete" = "tar" ] && [ "$ExtensionPaquete" != "tar" ] ; then ExtensionPaquete="${SubExtensionPaquete}.${ExtensionPaquete}" ; fi
	case "$ExtensionPaquete" in
		"tar" )
			if ! Is_Executable tar ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de empaquetado 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.gz" )
			if ! Is_Executable tar || ! Is_Executable gzip ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'gzip' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.bz2" )
			if ! Is_Executable tar || ! Is_Executable bzip2 ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'bzip2' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.lzma" )
			if ! Is_Executable tar || ! Is_Executable lzma ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'lzma' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.7z" )
			# 7-zip does not store the owner/group of the file; tar needed.
			if ! Is_Executable tar ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de empaquetado 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if ! Is_Executable 7z  && ! Is_Executable 7zr && ! Is_Executable 7za ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión '7z' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"gz" )
			# 7-zip does not store the owner/group of the file.
			if ! Is_Executable gzip ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'gzip'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"bz2" )
			# 7-zip does not store the owner/group of the file.
			if ! Is_Executable bzip2 ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'bzip2'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"7z" )
			# 7-zip does not store the owner/group of the file.
			if ! Is_Executable 7z  && ! Is_Executable 7zr && ! Is_Executable 7za ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión '7z' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"tar.xz" )
			if ! Is_Executable tar || ! Is_Executable xz ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'xz' o 'tar'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		"zip" )
			# 7-zip does not store the owner/group of the file; tar needed.
			if ! Is_Executable unzip ; then
				printf '%s\n' "${sERROR}E: No se encuentra el programa de compresión 'unzip'${fRESET}" 1>&2
				LastStatus=54 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			;;
		* )
			printf '%s\n' "${sERROR}E: Extension de fichero \"$ExtensionPaquete\" no reconocida." 1>&2
			printf '%s\n' "SOLUCION: Especifique un nombre de paquete con una de las extensiones:" 1>&2
			printf '%s\n' "          .tar" 1>&2
			printf '%s\n' "          .tar.gz" 1>&2
			printf '%s\n' "          .tar.bz2" 1>&2
			printf '%s\n' "          .tar.lzma" 1>&2
			printf '%s\n' "          .tar.7z" 1>&2
			printf '%s\n' "          .7z" 1>&2
			printf '%s\n' "          .tar.xz" 1>&2
			printf '%s\n' "          .zip${fRESET}" 1>&2
			LastStatus=47 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	if [ $StatusCode -eq 0 ] ; then
		case "$ExtensionPaquete" in
			"tar" )
				if [ $BandwidthLimit -le 0 ] ; then
					tar --same-owner -xf "$PackageFilePath" ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					cat "$PackageFilePath" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.gz" )
				if [ $BandwidthLimit -le 0 ] ; then
					ComposedSpecs="--same-owner -xzf"
					if Is_Executable pigz ; then ComposedSpecs="-I pigz --same-owner -xf" ; fi
					# Syntax is right but some environment condition can result in error:
					# tar: You must specify one of the '-Acdtrux', '--delete' or '--test-label' options
					# Workaround is to execute in a subshell (sh)
					sh -c "tar $ComposedSpecs "$PackageFilePath" ${MoreTarParameters}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					ComposedSpecs="gzip"
					if Is_Executable pigz ; then ComposedSpecs="pigz" ; fi
					$ComposedSpecs -dcq "$PackageFilePath" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.bz2" )
				if [ $BandwidthLimit -le 0 ] ; then
					ComposedSpecs="--same-owner -xjf"
					if Is_Executable pbzip2 ; then ComposedSpecs="-I pbzip2 --same-owner -xf" ; fi
					# Syntax is right but some environment condition can result in error:
					# tar: You must specify one of the '-Acdtrux', '--delete' or '--test-label' options
					# Workaround is to execute in a subshell (sh)
					sh -c "tar $ComposedSpecs "$PackageFilePath" ${MoreTarParameters}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					ComposedSpecs="bzip2"
					if Is_Executable pbzip2 ; then ComposedSpecs="pbzip2" ; fi
					$ComposedSpecs -dcq "$PackageFilePath" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.lzma" )
				if [ $BandwidthLimit -le 0 ] ; then
					lzma -dcq "$PackageFilePath" | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					lzma -dcq "$PackageFilePath" 2>"${DirTemp}/$$.log" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					if [ "$(cat "${DirTemp}/$$.log")" != "" ] ; then StatusCode=222 ; fi ; rm "${DirTemp}/$$.log"  #"
				fi
				;;
			"tar.7z" )
				CompressBinary='7zr'
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7z' ; fi  #"
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7za' ; fi  #"
				if [ "$DecryptionPassword" = "" ] ; then
					if [ $BandwidthLimit -le 0 ] ; then
						$CompressBinary x -so "$PackageFilePath" | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						$CompressBinary x -so "$PackageFilePath" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				else
					if [ $BandwidthLimit -le 0 ] ; then
						$CompressBinary x -so "$PackageFilePath" "-p${DecryptionPassword}" | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					else
						$CompressBinary x -so "$PackageFilePath" "-p${DecryptionPassword}" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				fi
				;;
			"gz" )
				CompressBinary='gzip'
				OutputFile="$(printf '%s' "$PackageFilePath" | sed -e 's|.*/||g' -e "s|\.${ExtensionPaquete}$||")"
				if [ $BandwidthLimit -le 0 ] ; then
					$CompressBinary -dkc "$PackageFilePath" > "$OutputFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					$CompressBinary -dkc "$PackageFilePath" | cstream -t $((BandwidthLimit * 1024)) -c 0 > "$OutputFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"bz2" )
				CompressBinary='bzip2'
				OutputFile="$(printf '%s' "$PackageFilePath" | sed -e 's|.*/||g' -e "s|\.${ExtensionPaquete}$||")"
				if [ $BandwidthLimit -le 0 ] ; then
					$CompressBinary -dkc "$PackageFilePath" > "$OutputFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					$CompressBinary -dkc "$PackageFilePath" | cstream -t $((BandwidthLimit * 1024)) -c 0 > "$OutputFile"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"7z" )
				CompressBinary='7zr'
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7z' ; fi  #"
				if ! Is_Executable "$CompressBinary" ; then CompressBinary='7za' ; fi  #"
				if [ "$DecryptionPassword" = "" ] ; then
					$CompressBinary x "$PackageFilePath"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					$CompressBinary x "$PackageFilePath" "-p${DecryptionPassword}"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"tar.xz" )
				if [ $BandwidthLimit -le 0 ] ; then
					xz --decompress --stdout "$PackageFilePath" | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					xz --decompress --stdout "$PackageFilePath" | cstream -t $((BandwidthLimit * 1024)) -c 0 | tar --same-owner -x ${MoreTarParameters}
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"zip" )
				if [ "$DecryptionPassword" = "" ] ; then
					unzip -q -o -X "$PackageFilePath"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					unzip -q -o -X "$PackageFilePath" -P "$DecryptionPassword"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
		esac
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ListadoEncabezados ()
# Sintaxis como instrucción: ListadoEncabezados "$ListId" "$KeyType" $Column1Label $Column2Label $Column3Label $ColumnLabel...
# Descripción:
#	Almacena los encabezados para las columnas de datos para ListadoMostrar
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que crear los datos temporales. Se recomienda algo como Nombre.$$
#	$2	Tipo ordenación de la clave: "0" o "A" según si es numérico o alfanumérico respectivamente.
#	$3...	Etiqueta de encabezado. Algo como "_NOMBRE" implica alineación a la derecha.
# PENDIENTE: Poder especificar que se recorten columnas a una longitud maxima.
# Notes:
#	- Tab characters in data are replaced by spaces
# Depends on functions: (none)
# Depends on software packages: grep sed
{
	local ListId="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local KeyType="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DataPath=''
	local CurLabel=''
	local CurColumnNr=0
	local CurColumnLength=0
	local DataRow=''
	local CurColAlign=
	local ColsAligns=''
	local SomeHeader=0
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] || [ -f "${DataPath}/heading.txt" ] ; then
		printf '%s\n' "${sWARN}W: Removing previous list with identifier: ${ListId}${fRESET}" 1>&2
	fi
	rm -fr "$DataPath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	mkdir -p "$DataPath"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	chmod u=rwX,g=rX,o= "$DataPath"
	printf '%s' "$KeyType" > "${DataPath}/key.typ"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	for CurLabel in "$@" ; do
		CurColumnNr=$((CurColumnNr + 1))
		CurLabel="$(printf '%s' "$CurLabel" | tr '\t' ' ')"
		CurColAlign="$(printf '%c' "$CurLabel")"
		if [ "$CurColAlign" = "_" ] ; then
			CurLabel="$(printf '%s' "$CurLabel" | cut -c 2-)"
		else
			CurColAlign='/'
		fi
		ColsAligns="$ColsAligns	$CurColAlign"
		DataRow="$DataRow	$CurLabel"
		if [ "$CurLabel" != "" ] ; then SomeHeader=1 ; fi
		CurColumnLength=${#CurLabel}
		printf '%3s\n' "$CurColumnLength" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	done
	printf '%s' "$ColsAligns" | cut -f 2- > "${DataPath}/aligns.tab"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $SomeHeader -eq 1 ] ; then
		printf '%s' "$DataRow" | cut -f 2- > "${DataPath}/heading.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		cat /dev/null > "${DataPath}/heading.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ListadoNuevoRegistro ()
# Sintaxis como instrucción: ListadoNuevoRegistro "$ListId" "$OrderKey" $Column1Data $Column2Data $Column3Data $Data...
# Descripción:
#	Almacena los datos especificados para ser mostrados por la función ListadoMostrar
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que se almacenan los datos temporales.
#	$2	Dato a usar para ordenar los registros alfabéticamente. Para no reordenar, especificar cadena en blanco ""
#	$3...	Campos a mostrar en el listado
# Notes:
#	- Tab characters in data are replaced by spaces
# Depends on functions: (none)
# Depends on software packages: sed
{
	local ListId="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local OrderKey="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local DataPath=''
	local CurColumnData=''
	local CurColumnNr=0
	local ColumnsLengthsFiles=''
	local CurColumnLengthsFile=''
	local DataRow=''
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] && [ -f "${DataPath}/heading.txt" ] ; then
		KeyType="$(cat "${DataPath}/key.typ")"
		OrderKey="$(printf '%s' "$OrderKey" | tr '\t' ' ')"
		DataRow="$OrderKey"
		ColumnsLengthsFiles="$(ls -1 "${DataPath}"/*.len)"
		IFS="$(printf '\n\b')" ; for CurColumnLengthsFile in $ColumnsLengthsFiles ; do unset IFS
			CurColumnNr=$((CurColumnNr + 1))
			CurColumnData="$(printf '%s' "$1" | tr '\t' ' ')"
			DataRow="$DataRow	$CurColumnData"
			printf '%3s\n' "${#CurColumnData}" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
			if [ $# -gt 0 ] ; then shift ; fi
		done
		# This is for more data than headed:
		for CurColumnData in "$@" ; do
			CurColumnNr=$((CurColumnNr + 1))
			CurColumnData="$(printf '%s' "$CurColumnData" | tr '\t' ' ')"
			DataRow="$DataRow	$CurColumnData"
			printf '%4s\n' "${#CurColumnData}" | tr ' ' '0' >> "${DataPath}/${CurColumnNr}.len"
		done
		printf '%s\n' "$DataRow" >> "${DataPath}/data.txt"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		printf '%s\n' "${sERROR}E: List data not found with identifier: ${ListId}${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

ListadoMostrar ()
# Sintaxis como instrucción: ListadoMostrar "$ListId" "$ColumnSeparator" "$SortOrientation"
# Descripción:
#	Muestra los registros preparados anteriormente en forma de listado de columnas rectas, a base
#	de la inserción de espacios.
# Parámetros esperados:
#	$1	Identificador exclusivo para este listado, por el que hay los datos preparados.
#	$2	Cadena de separación AÑADIDA entre columnas, como por ejemplo un espacio " " o una línea "|"
#	$3	"a" para ordenar la clave en sentido 0-9 A-Z , o "r" para ordenarlas en sentido 9-0 Z-A
#	$4	(optional) It can contain letter keys:
#			"p" = Show list building progress (stderr)
# ToDo:
#	- Omitir las marcas de formato al contar la longitud de los valores, para que no queden movidas las columnas.
# Notas:
#	- Una vez usada la función, se borran los datos temporales y se pierde la información del listado.
#	- TAB can not be used as ColumnSeparator
# Depends on functions: Is_Executable
# Depends on software packages: grep sed
# Recommends other software: column/bsdmainutils
{
	local ListId="$1"
	local ColumnSeparator="$2"
	local SortOrientation="$3"
	local MoreOptions="$4"
	local DataPath=''
	local KeyType=''
	local Heading=''
	local CurHeader=''
	local SortParms=''
	local SortedRecords=''
	local CurRecord=''
	local ColumnsNr=0
	local CurColumnData=''
	local FirstRecord=0
	local ColsLengths=''
	local CurColLength=0
	local HeaderSeparator=''
	local ColsAligns=''
	local CurLine=''
	local HeaderLine=''
	local ShowProgress=0
	local LastStatus=0
	local StatusCode=0
	
	if [ ! -d "$DirTemp" ] ; then DirTemp="/run/shm" ; fi
	if [ ! -d "$DirTemp" ] ; then DirTemp="/tmp" ; fi
	DataPath="${DirTemp}/list.$(printf '%s' "$ListId" | sed -e 's|.*/||g')"
	if [ -f "${DataPath}/key.typ" ] && [ -f "${DataPath}/heading.txt" ] ; then
		if [ "$(printf '%s' "$MoreOptions" | grep -ie 'p')" ] ; then ShowProgress=1 ; fi
		KeyType="$(cat "${DataPath}/key.typ")"
		ColsAligns="$(cat "${DataPath}/aligns.tab")"
		Heading="$(cat "${DataPath}/heading.txt")"
		HeaderSeparator="$(printf '%s' "$ColumnSeparator" | sed -e 's|.| |g')"
		FirstRecord=1
		ColumnsNr="$(ls -1 "${DataPath}"/*.len | wc -l)"
		if [ -f "${DataPath}/data.txt" ] ; then
			if [ "$KeyType" = "0" ] ; then
				SortParms="$SortParms -n"
			fi
			if [ "$SortOrientation" = "r" ] ||  [ "$SortOrientation" = "R" ] ; then
				SortParms="$SortParms -r"
			fi
			SortedRecords="$(cat "${DataPath}/data.txt" | sort $SortParms | cut -f 2-)"
			IFS="$(printf '\n\b')" ; for CurRecord in $SortedRecords ; do unset IFS
				if [ $ShowProgress -eq 1 ] ; then printf '.' 1>&2 ; fi
				CurColumnNr=0
				CurLine=''
				while [ $CurColumnNr -lt $ColumnsNr ]; do
					CurColumnNr=$((CurColumnNr + 1))
					CurColumnData="$(printf '%s' "$CurRecord" | cut -sf $CurColumnNr)"
					if [ $FirstRecord -eq 1 ]; then
						CurColLength="$(cat "${DataPath}/${CurColumnNr}.len" | sort | tail -n 1)"
						ColsLengths="$ColsLengths	$CurColLength"
					else
						CurColLength="$(printf '%s' "$ColsLengths" | cut -sf $CurColumnNr)"
					fi
					if [ $CurColumnNr -gt 1 ] ; then
						CurLine="$(printf '%s' "${CurLine}$(printf '\t')${ColumnSeparator}")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							HeaderLine="$(printf '%s' "${HeaderLine}$(printf '\t')${HeaderSeparator}")"
						fi
					fi
					if [ "$(printf '%s' "$ColsAligns" | cut -sf $CurColumnNr)" = "_" ] ; then
						CurLine="${CurLine}$(printf "%${CurColLength}s" "$CurColumnData")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							CurHeader="$(printf "%s" "$Heading" | cut -sf $CurColumnNr)"
							HeaderLine="${HeaderLine}$(printf "%${CurColLength}s" "$CurHeader")"
						fi
					else
						CurLine="${CurLine}$(printf "%-${CurColLength}s" "$CurColumnData")"
						if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
							CurHeader="$(printf "%s" "$Heading" | cut -sf $CurColumnNr)"
							HeaderLine="${HeaderLine}$(printf "%-${CurColLength}s" "$CurHeader")"
						fi
					fi
				done
				if [ $FirstRecord -eq 1 ]; then
					ColsLengths="$(printf '%s' "$ColsLengths" | cut -sf 2-)"
					FirstRecord=0
					if [ "$Heading" != "" ]; then
						printf '%s\n' "$HeaderLine" >> "${DataPath}/columns.tab"
					fi
				fi
				printf '%s\n' "$CurLine" >> "${DataPath}/columns.tab"
			done
			if [ $ShowProgress -eq 1 ] ; then printf '\n' 1>&2 ; fi
		fi
		if [ $FirstRecord -eq 1 ] && [ "$Heading" != "" ]; then
			CurColumnNr=0
			CurLine=''
			while [ $CurColumnNr -lt $ColumnsNr ]; do
				CurColumnNr=$((CurColumnNr + 1))
				CurColumnData="$(printf '%s' "$Heading" | cut -sf $CurColumnNr)"
				if [ $CurColumnNr -gt 1 ] ; then CurLine="$(printf '%s' "${CurLine}$(printf '\t')${HeaderSeparator}")" ; fi
				CurLine="${CurLine}${CurColumnData}"
			done
			printf '%s\n' "$CurLine" >> "${DataPath}/columns.tab"
		fi
		if [ -f "${DataPath}/columns.tab" ] ; then
			if Is_Executable column; then
				cat "${DataPath}/columns.tab" | column -t -s "$(printf '\t')"
			else
				cat "${DataPath}/columns.tab" | sed -e 's|\t||g'
			fi
		fi
	else
		printf '%s\n' "${sERROR}E: List data not found with identifier: ${ListId}${fRESET}" 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	rm -fr "$DataPath"
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

RespostaLletra ()
# Sintaxi com a funcio: $(RespostaLletra $JaEspecificada)
# Descripció: Reads a keyboard-typed answer (ENTER must be pressed) and returns first answer letter (lower case if possible - not in Maemo)
# Paràmetres esperats:
#	$1	(opcional) Tractar aquesta cadena en comptes de demanar-la de teclat.
# Depends on functions: Lowercase
# Depends on software packages: (none)
{
	JaEspecificada="$1"
	if [ "$JaEspecificada" = "" ] ; then read JaEspecificada ; fi
	JaEspecificada="$(printf '%s' "$JaEspecificada" | cut -c 1)"
	JaEspecificada="$(Lowercase "$JaEspecificada")"
	if [ "$JaEspecificada" != "" ] ; then printf '%s\n' "$JaEspecificada" ; fi
}

EliminarTemporalSeguro ()
# Sintaxis como instrucción: EliminarTemporalSeguro "$RutaFichero"
# Descripción:
#	Borra el fichero o directorio especificado, y los directorios de su ruta que no estén vacíos.
#	Por ejemplo, de "/run/shm/usuario/Rutinas/20120821-091624.qBG8.ext"
#	borra 20120821-091624.qBG8.ext , Rutinas y usuario.
# Nota: El borrado de fichero es irrecuperable
# Parámetros esperados:
#	$1	Ruta de un fichero existente
# Depends on functions: Dirname
{
	local RutaFichero="$1"
	local DirActual=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$RutaFichero" != "" ] ; then
		if [ -e "$RutaFichero" ] ; then
			cat /dev/null > "$RutaFichero"
			rm -r "$RutaFichero"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		DirActual="$(Dirname "$RutaFichero")"
#		while [ "$DirActual" != "" ] && [ "$DirActual" != "/" ] && [ "$(basename "$DirActual")" != "tmp" ] ; do	Problems with a path begun with "-" in old basename versions
		while [ "$DirActual" != "" ] && [ "$DirActual" != "/" ] && [ "$(printf '%s\n' "$DirActual" | tr -s '/' '\n' | tail -n 1)" != "tmp" ] ; do
			if [ -d "$DirActual" ] ; then
				rmdir --ignore-fail-on-non-empty "$DirActual"
			fi
			DirActual="$(Dirname "$DirActual")"
		done
	else
		printf '%s\n' "PROBLEMA (EliminarTemporalSeguro): Fichero no especificado." 1>&2
		LastStatus=95 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	
	if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
	return $StatusCode
}

PathSize ()
# Syntax as a function: $(PathSize "$FilePath")
# Description: Returns (stdout) content size in bytes of specified file or whole tree of specified path including directories and files
# Notes:
#	- If file/path is not found, nothing is returned.
#	- Each directory size can be the typical 4K per used block
#	- Each file size is only the apparent content size; not from used blocks.
#	- Sparse holes are included: This apparent size is summarized!
#	- Does not follow symbolic links
#	- Includes multiple file systems if mounted
# Depends on functions: (none)
# Depends on software packages: findutils, sed
{
	local FilePath="$1"
	local Value=''
	
	if [ -f "$FilePath" ] || [ -d "$FilePath" ] || [ "$(find "$FilePath" -maxdepth 0 -type l)" != "" ] ; then
		Value="$(env LANG=en du -sb "$FilePath" | tr -s '\t' ' ' | sed -e 's|^ ||g' | cut -f 1 -d ' ')"
		printf '%s\n' "$Value"
	fi
}

NiceNumber ()
# Syntax as a function: $(NiceNumber "$Number" $FillDecimals [ForceStyle])
# Description: When numfmt, printf and awk fail, it's time to put thousand separators digit-per-digit
# Expected parameters:
#	$1	Original number string
#	$2	Number of decimal digits to enlarge to.
#		* If a minus sign "-" is appended, length will be also shortened to force this fixed width.
#		* Specify "0" to have no effect
#		* Specify "0-" to delete complete decimal part
#		Examples (using comma):
#		Number=123 FillDecimals=0 : 123
#		Number=123,0 FillDecimals=0 : 123,0
#		Number=123,456 FillDecimals=0 : 123,456
#		Number=123,456 FillDecimals=5 : 123,45600
#		Number=123,456 FillDecimals=1- : 123,4
#	$3	(optional) A comma ",", dot "." or apostrophe "'" to force it as decimal separator
#		Dot implies comma to be thousands separator; Others imply dot.
#		* Include a "0" to force a zero as integer part in case of lack of integer value.
# Notes:
#	- Either "123.456" or "123,456" or "123'456" will be got as integer 123 + the rest 456 as decimals.
#	  Last separator will not be interpreted as thousand separator.
#	- If style is not forced, tries to detect and use $Number format, and at last environment locale for decimal/thousand separators.
#	- $Number "123,456" is detected as using comma as decimal separator
# Depends on functions: (none)
# Depends on software packages: grep, sed
{
	local Number="$1"
	local FillDecimals="$2"
	local ForceStyle="$3"
	local DecimalSeparator=''
	local ThousandSeparator=''
	local CurLocale=''
	local CurLanguage=''
	local OriginalDecimals=''
	local OriginalInteger=''
	local OriginalDecimalSeparator=''
	local OriginalThousandSeparator=''
	local ResultIntegerPart=''
	local ResultDecimalPart=''
	local CurDigit=''
	local ShortenFillDecimals=0
	local DigitsMoved=''
	local Value=''
	
	if [ "$(printf '%s' "$FillDecimals" | grep -e '-$')" != "" ] ; then ShortenFillDecimals=1 ; fi
	FillDecimals="$(printf '%s' "$FillDecimals" | sed -e 's|[^0-9]||g')"
	if [ "$FillDecimals" = "" ] ; then FillDecimals=0 ; fi
	OriginalDecimals="$(printf '%s' "$Number" | tr -s ",.'" "\n" | tail -n 1)"	#'
	OriginalInteger="$Number"
	if [ "$OriginalDecimals" != "" ] && [ "$OriginalDecimals" != "$OriginalInteger" ] ; then
		CurDigit=${#OriginalDecimals}
		while [ $CurDigit -gt 0 ] ; do
			OriginalInteger="$(printf '%s' "$OriginalInteger" | sed -e 's|.$||')"
			CurDigit=$((CurDigit - 1))
		done
#		OriginalDecimalSeparator="$(printf '%s' "$OriginalInteger" | rev | cut -c 1)"
		OriginalDecimalSeparator="$(printf '%c' "$OriginalInteger" | rev)"
		OriginalInteger="$(printf '%s' "$OriginalInteger" | sed -e 's|.$||')"
	else
		OriginalDecimals=''
	fi
	if [ "$OriginalDecimalSeparator" = "." ] ; then
		if [ "$(printf '%s' "$OriginalInteger" | grep -e '\.')" != "" ] ; then
			# Separator appears more times: This was not for decimals
			if [ ${#OriginalDecimals} -eq 3 ] ; then
				# It leaves 3 digits at its right: This was a thousand separator
				OriginalThousandSeparator="$OriginalDecimalSeparator"
				if [ "$(printf '%s' "$Number" | grep -e ',')" = "" ] ; then
					# If comma does'nt appear means no inadequate use in other positions
					OriginalDecimalSeparator=','
				fi
			else
				# It doesn't leave 3 digits at its right: This was bad format
				OriginalThousandSeparator=''
				OriginalDecimalSeparator=''
			fi
			OriginalDecimals=''
			OriginalInteger="$Number"
		fi
	else
		if [ "$OriginalDecimalSeparator" = "," ] ; then
			if [ "$(printf '%s' "$OriginalInteger" | grep -e ',')" != "" ] ; then
				# Separator appears more times: This was not for decimals
				if [ ${#OriginalDecimals} -eq 3 ] ; then
					# It leaves 3 digits at its right: This was a thousand separator
					OriginalThousandSeparator="$OriginalDecimalSeparator"
					if [ "$(printf '%s' "$Number" | grep -e '\.')" = "" ] ; then
						# If dot does'nt appear means no inadequate use in other positions
						OriginalDecimalSeparator='.'
					fi
				else
					# It doesn't leave 3 digits at its right: This was bad format
					OriginalThousandSeparator=''
					OriginalDecimalSeparator=''
				fi
				OriginalDecimals=''
				OriginalInteger="$Number"
			fi
		else
			if [ "$OriginalDecimalSeparator" = "'" ] ; then
				if [ "$(printf '%s' "$OriginalInteger" | grep -e "'")" != "" ] ; then	#'
					# Separator appears more times: This was not for decimals
					if [ ${#OriginalDecimals} -eq 3 ] ; then
						# It leaves 3 digits at its right: This was a thousand separator
						OriginalThousandSeparator="$OriginalDecimalSeparator"
						if [ "$(printf '%s' "$Number" | grep -e '\.')" = "" ] ; then
							# If dot does'nt appear means no inadequate use in other positions
							OriginalDecimalSeparator='.'
						fi
					else
						# It doesn't leave 3 digits at its right: This was bad format
						OriginalThousandSeparator=''
						OriginalDecimalSeparator=''
					fi
					OriginalDecimals=''
					OriginalInteger="$Number"
				fi
			fi
		fi
	fi
	OriginalInteger="$(printf '%s' "$OriginalInteger" | sed -e 's|[^0-9]||g')"
	OriginalDecimals="$(printf '%s' "$OriginalDecimals" | sed -e 's|[^0-9]||g')"
	if [ "$(printf '%s' "$ForceStyle" | grep -e ",")" != "" ] ; then
		DecimalSeparator=','
		ThousandSeparator='.'
	else
		if [ "$(printf '%s' "$ForceStyle" | grep -e "\.")" != "" ] ; then
			DecimalSeparator='.'
			ThousandSeparator=','
		else
			if [ "$(printf '%s' "$ForceStyle" | grep -e "'")" != "" ] ; then	#'
				DecimalSeparator="'"
				ThousandSeparator='.'
			else
				# Autodetection by original format
				if [ "$DecimalSeparator" = "" ] ; then DecimalSeparator="$OriginalDecimalSeparator" ; fi
				if [ "$ThousandSeparator" = "" ] ; then ThousandSeparator="$OriginalThousandSeparator" ; fi
				if [ "$DecimalSeparator" = "" ] ; then
					if  [ "$ThousandSeparator" != "" ] ; then
						if [ "$ThousandSeparator" = "." ] ; then
							DecimalSeparator=','
						else
							DecimalSeparator='.'
						fi
					fi
				else
					if  [ "$ThousandSeparator" = "" ] ; then
						if [ "$DecimalSeparator" = "." ] ; then
							ThousandSeparator=','
						else
							ThousandSeparator='.'
						fi
					fi
				fi
				if [ "$DecimalSeparator" = "" ] || [ "$ThousandSeparator" = "" ] ; then
					# On english locales, assume typical format
					for CurLocale in "$LC_NUMERIC" "$LC_ALL" "$LANG" "$LANGUAGE" ; do
						CurLanguage="$(printf '%s' "$LC_NUMERIC" | cut -f 1 -d '_' | cut -f 1 -d '-')"
						if [ "$CurLanguage" = "en" ] || [ "$CurLanguage" = "EN" ] ; then
							DecimalSeparator='.'
							ThousandSeparator=','
						fi
					done
				fi
				if [ "$DecimalSeparator" = "" ] && [ "$ThousandSeparator" = "" ] ; then
					# At last use latin format
					DecimalSeparator=','
					ThousandSeparator='.'
				fi
			fi
		fi
	fi
	# Thousand separation
	while [ ${#OriginalInteger} -gt 0 ] ; do
		if [ "$ResultIntegerPart" != "" ] && [ $((${#DigitsMoved} % 3)) -eq 0 ] ; then
			ResultIntegerPart="${ThousandSeparator}${ResultIntegerPart}"
		fi
#		CurDigit="$(printf '%s' "$OriginalInteger" | rev | cut -c 1)"
		CurDigit="$(printf '%s' "$OriginalInteger" | rev)"
		CurDigit="$(printf '%c' "$CurDigit")"
		DigitsMoved="${CurDigit}${DigitsMoved}"
		ResultIntegerPart="${CurDigit}${ResultIntegerPart}"
		OriginalInteger="$(printf '%s' "$OriginalInteger" | sed -e 's|.$||')"
	done
	if [ "$ResultIntegerPart" = "" ] && [ "$(printf '%s' "$ForceStyle" | grep -e "0")" != "" ] ; then ResultIntegerPart="0" ; fi
	# Fill and/or shorten decimals
	ResultDecimalPart="$OriginalDecimals"
	while [ ${#ResultDecimalPart} -lt $FillDecimals ] ; do
		ResultDecimalPart="${ResultDecimalPart}0"
	done
	if [ $ShortenFillDecimals -eq 1 ] && [ ${#ResultDecimalPart} -gt $FillDecimals ] ; then
		if [ $FillDecimals -gt 0 ] ; then
			ResultDecimalPart="$(printf '%s' "$ResultDecimalPart" | cut -c 1-${FillDecimals})"
		else
			ResultDecimalPart=''
		fi
	fi
	Value="$ResultIntegerPart"
	if [ "$ResultIntegerPart" != "" ] && [ "$ResultDecimalPart" != "" ] ; then Value="${Value}${DecimalSeparator}${ResultDecimalPart}" ; fi
	if [ "$Value" != "" ] ; then printf '%s\n' "$Value" ; fi
}

NumeroResumit ()
# Syntax as a function: $(NumeroResumit $Numero $Estil)
# Retorna el número humanament llegible, segons $Estil:
# 	"2" :	Amb factors de 1024 (10,5b; 10,5k; 10,5m; 10,5g)
# 	"2L" :	Amb factors de 1024 i notació llarga (10,5B; 10,5KiB; 10,5MiB; 10,5GiB)
# 	"1" :	Amb factors de 1000 (10,5B; 10,5K; 10,5M; 10,5G) (SI)
# 	"." :	Utilitzar el separador decimal "." en comptes del predeterminat ","
#	Si s'afegeix un espai, se separa el número de la notació amb un espai:
#		"2 "   10,5 g
#		"2L "  9,5 MiB
#		"1 "   8300,5 B
#	Si s'afegeix una "A", aleshores s'alineen les dades:
#		"A2"     10,5g
#		"A2L"     9,5MiB
#		"A1"   8300,5B
#	Si s'afegeix un "0", aleshores no s'expressen decimals (només l'enter arrodonit a la baixa):
#		"02"  10g
#		"02L" 9MiB
#		"01"  8300B
#	Tot es pot combinar:
#		"A02L "     9 MiB
#		"A01L "  8300 B
#		"2L."     9.5MiB
# Nota: el $Numero s'espera en bytes (octets)
# ToDo:
#	- Try to not depend on bc
# Depends on functions: (none)
# Depends on software packages: grep, bc
{
	local Numero=$1
	local Estil="$2"
	local LlindarResum=2000
	local DecimalSeparator=''
	local CurLocale=''
	local EnterVisible=''
	local DecimalVisible=''
	local Sufix=''
	local Factor=0
	
	if [ "$(printf '%s' "$Estil" | grep "\.")" != "" ] ; then DecimalSeparator="." ; fi
	if [ "$(printf '%s' "$Estil" | grep ",")" != "" ] ; then DecimalSeparator="," ; fi
	if [ "$(printf '%s' "$Estil" | grep "'")" != "" ] ; then DecimalSeparator="'" ; fi	#"
	if [ "$DecimalSeparator" = "" ] ; then
		DecimalSeparator=','
		for CurLocale in "$LC_NUMERIC" "$LC_ALL" "$LANG" "$LANGUAGE" ; do
			CurLanguage="$(printf '%s' "$LC_NUMERIC" | cut -f 1 -d '_' | cut -f 1 -d '-')"
			if [ "$CurLanguage" = "en" ] || [ "$CurLanguage" = "EN" ] ; then
				DecimalSeparator='.'
			fi
		done
	fi
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then
		Factor=1024
	else
		if [ "$(printf '%s' "$Estil" | grep "1")" != "" ] ; then
			Factor=1000
		else
			return 1
		fi
	fi
	EnterVisible="$Numero"
	Sufix="B"
	# Resum
	if [ $Numero -ge $LlindarResum ] || [ $(printf '%s\n' "$Numero" | sed -e 's|.....$||g') -ge $LlindarResum ] ; then
		EnterVisible="$(echo "$Numero / $Factor" | bc)"
		DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
		Numero=$(echo "$Numero / $Factor" | bc)
		Sufix="K"
		if [ $Numero -ge $LlindarResum ] ; then
			EnterVisible="$(echo "$Numero / $Factor" | bc)"
			DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
			Numero=$(echo "$Numero / $Factor" | bc)
			Sufix="M"
			if [ $Numero -ge $LlindarResum ] ; then
				EnterVisible="$(echo "$Numero / $Factor" | bc)"
				DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
				Numero=$(echo "$Numero / $Factor" | bc)
				Sufix="G"
				if [ $Numero -ge $LlindarResum ] ; then
					EnterVisible="$(echo "$Numero / $Factor" | bc)"
					DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
					Numero=$(echo "$Numero / $Factor" | bc)
					Sufix="T"
					if [ $Numero -ge $LlindarResum ] ; then
						EnterVisible="$(echo "$Numero / $Factor" | bc)"
						DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
						Numero=$(echo "$Numero / $Factor" | bc)
						Sufix="P"
						if [ $Numero -ge $LlindarResum ] ; then
							EnterVisible="$(echo "$Numero / $Factor" | bc)"
							DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
							Numero=$(echo "$Numero / $Factor" | bc)
							Sufix="E"
							if [ $Numero -ge $LlindarResum ] ; then
								EnterVisible="$(echo "$Numero / $Factor" | bc)"
								DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
								Numero=$(echo "$Numero / $Factor" | bc)
								Sufix="Z"
								if [ $Numero -ge $LlindarResum ] ; then
									EnterVisible="$(echo "$Numero / $Factor" | bc)"
									DecimalVisible="$(echo "$(echo "$(echo "$Numero * 10" | bc) / $Factor" | bc) % 10" | bc)"
									Numero=$(echo "$Numero / $Factor" | bc)
									Sufix="Y"
								fi
							fi
						fi
					fi
				fi
			fi
		fi
	fi
	# Decimal invisible
	if [ "$(printf '%s' "$Estil" | grep "0")" != "" ] ; then
		DecimalVisible=''
	else
		if [ "$DecimalVisible" = "" ] ; then DecimalVisible="0" ; fi
		DecimalVisible="$DecimalSeparator$DecimalVisible"
	fi
	# Notació llarga
	if [ "$(printf '%s' "$Estil" | grep "2")" != "" ] ; then  # factor binari, sufix en minuscula
		case "$Sufix" in
			"B" )	Sufix="b"	;;
			"K" )	Sufix="k"	;;
			"M" )	Sufix="m"	;;
			"G" )	Sufix="g"	;;
			"T" )	Sufix="t"	;;
			"P" )	Sufix="p"	;;
			"E" )	Sufix="e"	;;
			"Z" )	Sufix="z"	;;
			"Y" )	Sufix="y"	;;
		esac
		if [ "$(printf '%s' "$Estil" | grep "L")" != "" ] ; then
			case "$Sufix" in
				"b" )	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
						Sufix="B  "	# Amb aliniació
					else
						Sufix="B"
					fi
					;;
				"k" )	Sufix="KiB"	;;
				"m" )	Sufix="MiB"	;;
				"g" )	Sufix="GiB"	;;
				"t" )	Sufix="TiB"	;;
				"p" )	Sufix="PiB"	;;
				"e" )	Sufix="EiB"	;;
				"z" )	Sufix="ZiB"	;;
				"y" )	Sufix="YiB"	;;
			esac
		fi
	fi
	# Espai
	if [ "$(printf '%s' "$Estil" | grep -e ' ')" != "" ] ; then
		Sufix=" $Sufix"
	fi
	# Aliniació
	if [ "$(printf '%s' "$Estil" | grep "A")" != "" ] ; then
		while [ ${#EnterVisible} -lt 4 ] ; do
			EnterVisible=" $EnterVisible"
		done
	fi
	printf '%s\n' "$EnterVisible$DecimalVisible$Sufix"
}

#[/admidb]


##### SPECIFIC FUNCTIONS TO THIS SCRIPT #####

#[admidb]
PreparePassword ()
{
	if [ "$PasswordReady" != "1" ] ; then
		SaveServerPassword="$(IniVarValue "$CurrentConfigFile" SaveServerPassword "" "" "" "")"
		if [ "$SaveServerPassword" = "1" ] ; then
			if [ "$ADMIDB_PASS" != "" ] ; then
				ServerPassword="$ADMIDB_PASS"
			else
				ServerPassword="$(IniVarValue "$CurrentConfigFile" ServerPassword "" "" "" "")"
				ServerPassword="$(printf '%s' "$ServerPassword" | xxd -r -p)"
			fi
			PasswordReady="1"
		fi
		if [ "$PasswordReady" != "1" ] ; then
			if [ "$ServerPassword" = "" ] ; then
				if [ "$ADMIDB_PASS" != "" ] ; then
					ServerPassword="$ADMIDB_PASS"
				else
					if [ "$ADMIDB_BATCH" != "1" ] ; then
						printf '%s\n' "Escribe la contraseña del usuario '${ServerUser}' de MySQL/MariaDB (aquí visible):" 1>&2
						read ServerPassword
					fi
				fi
				PasswordReady="1"
			fi
		fi
		if [ "$ADMIDB_BATCH" = "1" ] ; then SaveServerPassword=0 ; fi
		if [ "$SaveServerPassword" = "" ] ; then
			printf '%s\n' "¿Guardar la contraseña de administración de MySQL/MariaDB (usuario ${ServerUser})?" 1>&2
			printf '%s' "[s/N] " 1>&2
			RespuestaSave="$(RespostaLletra $RespuestaSave)"
			if [ "$RespuestaSave" = "s" ] || [ "$RespuestaSave" = "y" ] ; then
				SaveServerPassword="1"
			else
				SaveServerPassword="0"
			fi
			MkfilePP "$CurrentConfigFile" '' u=rw,g=r,o=
			SetIniVarValue "$CurrentConfigFile" SaveServerPassword "" "$SaveServerPassword" "" "# Remember password for MySQL/MariaDB's access user (1=yes, 2=no)"
			chmod o= "$CurrentConfigFile"
			if [ "$SaveServerPassword" = "1" ] ; then
				SetIniVarValue "$CurrentConfigFile" ServerPassword "" "\"$(printf '%s' "$ServerPassword" | xxd -p)\"" "" "# Masked password for MySQL/MariaDB's manager user"
			else
				SetIniVarValue "$CurrentConfigFile" ServerPassword "" "\"\"" "" "# Masked password for MySQL/MariaDB's access user. Please, don't include spaces."
			fi
		fi
	fi
}

EjecutarSQLDecorado ()
# Ejecuta la instrucción SQL especificada como primer parámetro.
# Mantiene las líneas y cabeceras de decoración
{
	local LastStatus=0
	local StatusCode=0
	
	
	#$SqlClientCall "SHOW DATABASES;"
	#LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	PreparePassword
	if [ "$PasswordReady" = "1" ] ; then
		if [ "$ServerPassword" != "" ] ; then
			"$SqlClient" --default-character-set=utf8 --user="$ServerUser" "--password=$ServerPassword" -e "$1"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			if [ $(id -u) -ne 0 ] ; then
				"$SqlClient" --default-character-set=utf8 --user="$ServerUser" -e "$1"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				"$SqlClient" --default-character-set=utf8 -e "$1"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	else
		if [ "$AskPasswordNotice" != "" ] ; then printf '%s\n' "$AskPasswordNotice" ; fi
		"$SqlClient" --default-character-set=utf8 --user="$ServerUser" --password -e "$1"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -ne 0 ] ; then
		echo "SqlCommandWas> $1" 1>&2
	fi
	
	return $StatusCode
}

EjecutarSQLBruto ()
# Ejecuta la instrucción SQL especificada como primer parámetro.
# Sólo muestra datos; sin cabeceras ni líneas de separación entre columnas.
{
	local LastStatus=0
	local StatusCode=0
	
	#$SqlClientCall "SHOW DATABASES;"
	#LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	PreparePassword
	if [ "$PasswordReady" = "1" ] ; then
		if [ "$ServerPassword" != "" ] ; then
			"$SqlClient" --default-character-set=utf8 --batch --skip-column-names --user="$ServerUser" "--password=$ServerPassword" -e "$1"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			if [ $(id -u) -ne 0 ] ; then
				"$SqlClient" --default-character-set=utf8 --batch --skip-column-names --user="$ServerUser" -e "$1"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				"$SqlClient" --default-character-set=utf8 --batch --skip-column-names -e "$1"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	else
		if [ "$AskPasswordNotice" != "" ] ; then printf '%s\n' "$AskPasswordNotice" ; fi
		"$SqlClient" --default-character-set=utf8 --batch --skip-column-names --user="$ServerUser" --password -e "$1"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -ne 0 ] ; then
		echo "SqlCommandWas> $1" 1>&2
	fi

	return $StatusCode
}

NuevaBDTemporal ()
# Sintaxis como función: $(NuevaBDTemporal "$NombreBase")
# Descripción:
#	Crea y devuelve (echo) el nombre de una base de datos temporal.
#	Ejemplo para NuevaBDTemporal "admidb"
#	admidb_tmp_20120821091624_qbg8
# Parámetros esperados:
#	$1	(opcional) Etiqueta ilustrativa
{
	local NombreBase="$1"
	local Tiempo=""
	local Valor=""
	local BDEncontrada=""
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBase" != "" ] ; then NombreBase="${NombreBase}_" ; fi
	Tiempo="$(date +'%Y%m%d%H%M%S')"
	Valor="$(mktemp -t ${NombreBase}tmp_${Tiempo}_XXXX)"
	# --dry-run sólo se soporta en versiones modernas de mktemp
	rm "$Valor"
#	Valor="$(basename "$Valor")"
	Valor="$(printf '%s\n' "$Valor" | tr -s '/' '\n' | tail -n 1)"  # Problems with a path begun with "-" in old GNU basename versions
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	Valor="$(Lowercase "$Valor")"  # Necessay wuen MySQL/MariaDB has lower_case_table_names = 1
	BDEncontrada="$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${Valor}$")"
	while [ $StatusCode -eq 0 ] && [ "$BDEncontrada" != "" ] ; do
		Tiempo="$(date +'%Y%m%d%H%M%S')"
		Valor="$(mktemp -t ${NombreBase}tmp_${Tiempo}_XXXX)"
		# --dry-run sólo se soporta en versiones modernas de mktemp
		rm "$Valor"
#		Valor="$(basename "$Valor")"
		Valor="$(printf '%s\n' "$Valor" | tr -s '/' '\n' | tail -n 1)"  # Problems with a path begun with "-" in old GNU basename versions
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		BDEncontrada="$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${Valor}$")"
	done
	if [ $StatusCode -eq 0 ] ; then
		EjecutarSQLBruto "CREATE DATABASE ${Valor};"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		echo  "$Valor"
	fi
	return $StatusCode
}

Data_empty ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
#	local NombreCampo="$3"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos de la tabla a vaciar:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$NombreTabla" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Tabla con los datos a eliminar:"
			read NombreTabla
		fi
		if [ "$NombreTabla" != "" ] ; then
			EjecutarSQLBruto "DELETE FROM ${NombreBD}.${NombreTabla};"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado tabla para vaciar."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Data_show ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos donde encontrar la tabla:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$NombreTabla" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Tabla de los datos:"
			read NombreTabla
		fi
		if [ "$NombreTabla" != "" ] ; then
			EjecutarSQLDecorado "SELECT * FROM ${NombreBD}.${NombreTabla};"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado tabla para informar."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Data_search ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local NombreCampo="$3"
	local MaxRowsToShow="$4"
	local ExpresionBuscar="$5"
	local Opciones="$6"  # "s" 'scan' mode: only report if data is found, and found data in warn mode.
	local ListaBBDD=""
	local BDActual=""
	local ListaTablas=""
	local TablaActual=""
	local ListaCampos=""
	local CampoActual=""
	local NrCampoActual=0
	local SQLActual=""
	local EncuentrosActual=0
	local TotalResultados=0
	local NrBD=0
	local NrBDActual=0
	local COLUMN_TYPE=''
	local ScanMode=0
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$Opciones" | grep -e 's')" != "" ] ; then ScanMode=1 ; fi
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$(printf '%s' "$NombreTabla" | grep -e '.\..')" != "" ] && [ "$NombreCampo" = "" ] ; then
		NombreCampo="$(printf '%s' "$NombreTabla" | cut -f 2- -d '.')"
		NombreTabla="$(printf '%s' "$NombreTabla" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$NombreBD" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos donde encontrar la tabla:"
		echo "(un punto para todas)"
		read NombreBD
	fi
	if [ "$NombreBD" = "." ] ; then NombreBD="" ; fi
	if [ "$NombreTabla" = "" ] && [ "$NombreTabla" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Tabla de los datos:"
		echo "(un punto para todas)"
		read NombreTabla
	fi
	if [ "$NombreTabla" = "." ] ; then NombreTabla="" ; fi
	if [ "$NombreCampo" = "" ] && [ "$NombreCampo" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Campo a seleccionar:"
		echo "(un punto para todos)"
		read NombreCampo
	fi
	if [ "$NombreCampo" = "." ] ; then NombreCampo="" ; fi
	if ! Is_IntegerNr "$MaxRowsToShow" && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nr. máximo de filas por tabla a mostrar:"
		echo "(0 para no mostrar)"
		read MaxRowsToShow
	fi
	if [ "$ExpresionBuscar" = "" ] && [ "$ExpresionBuscar" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Texto a buscar (use comodines % _ para que no sea exacto):"
		echo "(nada si solo busca estructura)"
		read ExpresionBuscar
	fi
	if ! Is_IntegerNr "$MaxRowsToShow" ; then MaxRowsToShow=0 ; fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreBD}$")" = "" ] ; then
			printf '%s\n' "${sWARN}W: La base de datos \"${NombreBD}\" no existe.${fRESET}"
		fi
		ListaBBDD="$NombreBD"
	else
		ListaBBDD="$(EjecutarSQLBruto "SHOW DATABASES;" | sort | grep -ve '^information_schema$' -ve '^mysql$' -ve '^performance_schema$' -ve '^sys$')"
		if [ "$ExpresionBuscar" != "" ] && [ "$NombreCampo" = "" ] && [ "$NombreTabla" = "" ] ; then
			printf '%s\n' "${sWARN}Atencion: se busca en todos los datos del servidor SQL...${fRESET}"
		fi
	fi
	NrBD=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaBBDD)
	if [ "$ExpresionBuscar" != "" ] || [ "$NombreCampo" != "" ] || [ "$NombreTabla" != "" ] ; then
		NrBDActual=0
		for BDActual in $ListaBBDD ; do
			NrBDActual=$(($NrBDActual + 1))
			EncuentrosActual=0
			if [ "$ExpresionBuscar" != "" ] && [ "$NombreCampo" = "" ] && [ "$NombreTabla" = "" ] && [ "$NombreBD" = "" ] ; then
				echo "Buscando en la base de datos ${BDActual} (${NrBDActual}/${NrBD})..."
			fi
			if [ "$NombreTabla" != "" ] ; then
				ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$NombreTabla\";")"
			else
				ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDActual\" ORDER BY Table_NAME ASC;")"
			fi
			if [ "$ExpresionBuscar" != "" ] || [ "$NombreCampo" != "" ] ; then
				for TablaActual in $ListaTablas ; do
					EncuentrosActual=0
#					if [ "$NombreTabla" != "" ] ; then printf '.' ; fi
					if [ "$NombreCampo" != "" ] ; then
						ListaCampos="$(EjecutarSQLBruto "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" AND COLUMN_NAME = \"$NombreCampo\";")"
					else
						ListaCampos="$(EjecutarSQLBruto "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" ORDER BY COLUMN_NAME ASC;")"
					fi
					if [ "$ListaCampos" != "" ] ; then
						if [ "$ExpresionBuscar" != "" ] ; then
							SQLActual="SELECT COUNT(*) FROM ${BDActual}.${TablaActual} WHERE"
							NrCampoIncluido=0
							for CampoActual in $ListaCampos ; do
								ColCheck="$(EjecutarSQLBruto "SELECT $CampoActual FROM ${BDActual}.${TablaActual} LIMIT 1;" 2>&1)"
								LastStatus=$?
								if [ $LastStatus -eq 0 ] ; then
									NrCampoIncluido=$(($NrCampoIncluido + 1))
									if [ $NrCampoIncluido -gt 1 ] ; then
										SQLActual="$SQLActual OR"
									fi
									SQLActual="$SQLActual LOWER(${CampoActual}) LIKE LOWER(\"${ExpresionBuscar}\")"
								else
									COLUMN_TYPE="$(EjecutarSQLBruto "SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" AND COLUMN_NAME = \"${CampoActual}\";")"
									if [ "$NombreBD" != "" ] ; then
										# Aviso sólo cuando se busca en una sola base de datos
										echo "Nota: No se pudo buscar en el campo \"$CampoActual\" ${ParO}${COLUMN_TYPE}${ParC} de la bd.tabla ${BDActual}.${sINFO}${TablaActual}${fRESET}"
									fi
								fi
							done
							if [ $NrCampoIncluido -gt 0 ] ; then
								SQLActual="${SQLActual};"
								EncuentrosActual=$(EjecutarSQLBruto "$SQLActual")
								LastStatus=$?
								if [ $LastStatus -ne 0 ] ; then echo "$SQLActual" ; fi
								if ! Is_IntegerNr $EncuentrosActual ; then EncuentrosActual=0 ; fi
								if [ $EncuentrosActual -gt 0 ] ; then
									if [ $ScanMode -eq 0 ] ; then
										echo "${BDActual}.${TablaActual} : ${sINFO}${EncuentrosActual} registros${fRESET}"
									else
										echo "${BDActual}.${TablaActual} : ${sWARN}${EncuentrosActual} sospechas${fRESET}"
									fi
									TotalResultados=$(($TotalResultados + $EncuentrosActual))
									if [ $MaxRowsToShow -gt 0 ] ; then
										SQLActual="$(echo "$SQLActual" | sed -e 's|SELECT COUNT.\*.|SELECT *|g')"
										SQLActual="$(echo "$SQLActual" | sed -e "s|;$| LIMIT ${MaxRowsToShow};|g")"
										EjecutarSQLDecorado "$SQLActual"
									fi
									if [ "$NombreCampo" = "" ] ; then
										CamposDato=""
										for CampoActual in $ListaCampos ; do
											SQLActual="SELECT COUNT(*) FROM ${BDActual}.${TablaActual} WHERE LOWER(${CampoActual}) LIKE LOWER(\"${ExpresionBuscar}\");"
											EncuentrosActual=$(EjecutarSQLBruto "$SQLActual" 2>/dev/null)
											LastStatus=$?
											if ! Is_IntegerNr $EncuentrosActual ; then EncuentrosActual=0 ; fi
											if [ $LastStatus -eq 0 ] ; then
												if [ $EncuentrosActual -gt 0 ] ; then
													if [ "$CamposDato" != "" ] ; then CamposDato="${CamposDato}, " ; fi
													CamposDato="${CamposDato}$CampoActual"
												fi
											fi
										done
										echo "	Campos con encuentros: $CamposDato"
									fi
								else
									if [ $ScanMode -ne 0 ] ; then
										echo "${BDActual}.${TablaActual} : ${EncuentrosActual} sospechas"
									fi
								fi
							else
								printf '%s\n' "${sWARN}W: La tabla ${BDActual}.${TablaActual} no tiene campos aptos en los que buscar.${fRESET}" 1>&2
							fi
						else
							EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaCampos)
							echo "${BDActual}.${TablaActual} : $EncuentrosActual campos"
							TotalResultados=$(($TotalResultados + $EncuentrosActual))
						fi
					fi
				done
			else
				EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaTablas)
				echo "${BDActual} : $EncuentrosActual tablas"
				TotalResultados=$(($TotalResultados + $EncuentrosActual))
			fi
		done 
	else
		EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaBBDD)
		echo "$EncuentrosActual bases de datos"
		TotalResultados=$(($TotalResultados + $EncuentrosActual))
	fi
	if [ $TotalResultados -gt 0 ] ; then
		if [ $ScanMode -eq 0 ] ; then
			echo "Total: ${sINFO}${TotalResultados} coincidencias.${fRESET}"
		else
			echo "Total: ${sWARN}${TotalResultados} sospechas.${fRESET}"
		fi
	else
		if [ $ScanMode -eq 0 ] ; then
			echo "Total: $TotalResultados coincidencias."
		else
			echo "Total: $TotalResultados sospechas."
		fi
	fi
	return $StatusCode
}

Data_replace ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local NombreCampo="$3"
	local CadenaBuscar="$4"
	local CadenaReemplazo="$5"
	local ListaBBDD=""
	local BDActual=""
	local ListaTablas=""
	local TablaActual=""
	local ListaCampos=""
	local CampoActual=""
	local NrCampoActual=0
	local SQLActual=""
	local EncuentrosActual=0
	local TotalResultados=0
	local NrBD=0
	local NrBDActual=0
	local COLUMN_TYPE=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$(printf '%s' "$NombreTabla" | grep -e '.\..')" != "" ] && [ "$NombreCampo" = "" ] ; then
		NombreCampo="$(printf '%s' "$NombreTabla" | cut -f 2- -d '.')"
		NombreTabla="$(printf '%s' "$NombreTabla" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$NombreBD" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		printf '%s\n' "Base de datos donde encontrar la tabla:"
		printf '%s\n' "(un punto para todas ¡CUIDADO!)"
		read NombreBD
	fi
	if [ "$NombreBD" = "." ] ; then NombreBD="" ; fi
	if [ "$NombreTabla" = "" ] && [ "$NombreTabla" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		printf '%s\n' "Tabla de los datos:"
		printf '%s\n' "(un punto para todas ¡Cuidado!)"
		read NombreTabla
	fi
	if [ "$NombreTabla" = "." ] ; then NombreTabla="" ; fi
	if [ "$NombreCampo" = "" ] && [ "$NombreCampo" != "." ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		printf '%s\n' "Campo a seleccionar:"
		printf '%s\n' "(un punto para todos ¡cuidado!)"
		read NombreCampo
	fi
	if [ "$NombreCampo" = "." ] ; then NombreCampo="" ; fi
	if [ "$CadenaBuscar" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		printf '%s\n' "Fragmento de texto a buscar:"
		read CadenaBuscar
	fi
	if [ "$CadenaReemplazo" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		printf '%s\n' "Texto de reemplazo:"
		printf '%s\n' "(nada si en realidad se quiere eliminar el fragmento)"
		read CadenaReemplazo
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreBD}$")" = "" ] ; then
			printf '%s\n' "${sWARN}W: La base de datos \"${NombreBD}\" no existe.${fRESET}"
		fi
		ListaBBDD="$NombreBD"
	else
		ListaBBDD="$(EjecutarSQLBruto "SHOW DATABASES;" | sort | grep -ve '^information_schema$' -ve '^mysql$' -ve '^performance_schema$' -ve '^sys$')"
		if [ "$CadenaBuscar" != "" ] && [ "$NombreCampo" = "" ] && [ "$NombreTabla" = "" ] ; then
			printf '%s\n' "${sWARN}W: se busca en todos los datos del servidor SQL...${fRESET}" 1>&2
		fi
	fi
	NrBD=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaBBDD)
	if [ "$CadenaBuscar" != "" ] || [ "$NombreCampo" != "" ] || [ "$NombreTabla" != "" ] ; then
		# TODAVÍA POR COMPROBAR SI EL REEMPLAZO CON COMILLAS FUNCIONA!
		ExpresionBuscar="$(printf %s "%${CadenaBuscar}%" | sed -e 's|"|\\"|g' -e "s|'|\\\'|g")"
		CadenaReemplazo="$(printf %s "$CadenaReemplazo" | sed -e 's|"|\\"|g' -e "s|'|\\\'|g")"
		NrBDActual=0
		for BDActual in $ListaBBDD ; do
			NrBDActual=$(($NrBDActual + 1))
			EncuentrosActual=0
			if [ "$CadenaBuscar" != "" ] && [ "$NombreCampo" = "" ] && [ "$NombreTabla" = "" ] && [ "$NombreBD" = "" ] ; then
				printf '%s\n' "Buscando en la base de datos ${BDActual} (${NrBDActual}/${NrBD})..."
			fi
			if [ "$NombreTabla" != "" ] ; then
				ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$NombreTabla\";")"
			else
				ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDActual\" ORDER BY Table_NAME ASC;")"
			fi
			if [ "$CadenaBuscar" != "" ] || [ "$NombreCampo" != "" ] ; then
				for TablaActual in $ListaTablas ; do
					EncuentrosActual=0
#					if [ "$NombreTabla" != "" ] ; then printf '.' ; fi
					if [ "$NombreCampo" != "" ] ; then
						ListaCampos="$(EjecutarSQLBruto "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" AND COLUMN_NAME = \"$NombreCampo\";")"
					else
						ListaCampos="$(EjecutarSQLBruto "SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" ORDER BY COLUMN_NAME ASC;")"
					fi
					if [ "$ListaCampos" != "" ] ; then
						if [ "$CadenaBuscar" != "" ] ; then
							SQLActual="SELECT COUNT(*) FROM ${BDActual}.${TablaActual} WHERE"
							NrCampoIncluido=0
							for CampoActual in $ListaCampos ; do
								ColCheck="$(EjecutarSQLBruto "SELECT $CampoActual FROM ${BDActual}.${TablaActual} LIMIT 1;" 2>&1)"
								LastStatus=$?
								if [ $LastStatus -eq 0 ] ; then
									NrCampoIncluido=$(($NrCampoIncluido + 1))
									if [ $NrCampoIncluido -gt 1 ] ; then
										SQLActual="$SQLActual OR"
									fi
									SQLActual="$SQLActual ${CampoActual} LIKE \"${ExpresionBuscar}\""
								else
									COLUMN_TYPE="$(EjecutarSQLBruto "SELECT COLUMN_TYPE FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$BDActual\" AND Table_NAME = \"$TablaActual\" AND COLUMN_NAME = \"${CampoActual}\";")"
									printf '%s\n' "${sWARN}W: No se pudo buscar en el campo \"$CampoActual\" ${ParO}${COLUMN_TYPE}${ParC} de la bd.tabla ${BDActual}.${sINFO}${TablaActual}${fRESET}" 1>&2
								fi
							done
							if [ $NrCampoIncluido -gt 0 ] ; then
								SQLActual="${SQLActual};"
								EncuentrosActual=$(EjecutarSQLBruto "$SQLActual")
								LastStatus=$?
								if [ $LastStatus -ne 0 ] ; then printf '%s\n' "$SQLActual" ; fi
								if ! Is_IntegerNr $EncuentrosActual ; then EncuentrosActual=0 ; fi
								if [ $EncuentrosActual -gt 0 ] ; then
									printf '%s\n' "${BDActual}.${TablaActual} : ${sINFO}${EncuentrosActual} registros${fRESET}"
									TotalResultados=$(($TotalResultados + $EncuentrosActual))
									if [ "$NombreCampo" = "" ] ; then
										CamposDato=""
										for CampoActual in $ListaCampos ; do
											SQLActual="SELECT COUNT(*) FROM ${BDActual}.${TablaActual} WHERE ${CampoActual} LIKE \"${ExpresionBuscar}\";"
											EncuentrosActual=$(EjecutarSQLBruto "$SQLActual" 2>/dev/null)
											LastStatus=$?
											if ! Is_IntegerNr $EncuentrosActual ; then EncuentrosActual=0 ; fi
											if [ $LastStatus -eq 0 ] ; then
												if [ $EncuentrosActual -gt 0 ] ; then
													if [ "$CamposDato" != "" ] ; then CamposDato="${CamposDato}, " ; fi
													CamposDato="${CamposDato}$CampoActual"
													printf '%s\n' "	Se reemplaza texto en el campo: $CampoActual"
													EjecutarSQLDecorado "UPDATE ${BDActual}.${TablaActual} SET $CampoActual = REPLACE(${CampoActual}, \"${CadenaBuscar}\", \"${CadenaReemplazo}\");"
													LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
												fi
											fi
										done
									fi
								fi
							else
								printf '%s\n' "${sWARN}W: La tabla ${BDActual}.${TablaActual} no tiene campos aptos en los que buscar.${fRESET}" 1>&2
							fi
						else
							EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaCampos)
							printf '%s\n' "${BDActual}.${TablaActual} : $EncuentrosActual campos"
							TotalResultados=$(($TotalResultados + $EncuentrosActual))
						fi
					fi
				done
			else
				EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaTablas)
				printf '%s\n' "${BDActual} : $EncuentrosActual tablas"
				TotalResultados=$(($TotalResultados + $EncuentrosActual))
			fi
		done 
	else
		EncuentrosActual=$(NumeroParaules () { echo $#; }; NumeroParaules $ListaBBDD)
		printf '%s\n' "$EncuentrosActual bases de datos"
		TotalResultados=$(($TotalResultados + $EncuentrosActual))
	fi
	if [ $TotalResultados -gt 0 ] ; then
		echo "Total: ${sINFO}${TotalResultados} coincidencias.${fRESET}"
	else
		echo "Total: $TotalResultados coincidencias."
	fi
	return $StatusCode
}

Data ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"vaciar" ) CurrentAction="empty" ;;
		"vacia" ) CurrentAction="empty" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
		"buscar" ) CurrentAction="search" ;;
		"busca" ) CurrentAction="search" ;;
		"cambiar" ) CurrentAction="search" ;;
		"cambia" ) CurrentAction="search" ;;
		"reemplazar" ) CurrentAction="search" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para datos:"
			echo "	data show     (muestra el contenido de una tabla de datos)"
			echo "	data empty    (Elimina todos los datos de una tabla)"
			echo "	data search   (Busca un texto en cualquier ámbito)"
			echo "	data replace  (Reemplaza un texto en cualquier ámbito ¡Cuidado!)"
			StatusCode=1
			;;
		* )
			Data_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

Column_show ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local NombreCampo="$3"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$(printf '%s' "$NombreTabla" | grep -e '.\..')" != "" ] && [ "$NombreCampo" = "" ] ; then
		NombreCampo="$(printf '%s' "$NombreTabla" | cut -f 2- -d '.')"
		NombreTabla="$(printf '%s' "$NombreTabla" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos donde encontrar la tabla:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$NombreTabla" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Tabla de los datos:"
			read NombreTabla
		fi
		if [ "$NombreTabla" != "" ] ; then
			if [ "$NombreCampo" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
				echo "Campo concreto a volcar:"
				read NombreCampo
			fi
			if [ "$NombreCampo" != "" ] ; then
				EjecutarSQLBruto "SELECT $NombreCampo FROM ${NombreBD}.${NombreTabla};"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				echo "PROBLEMA: No se ha especificado la columna para informar."
			fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado tabla para informar."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Column_properties ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local NombreCampo="$3"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$(printf '%s' "$NombreTabla" | grep -e '.\..')" != "" ] && [ "$NombreCampo" = "" ] ; then
		NombreCampo="$(printf '%s' "$NombreTabla" | cut -f 2- -d '.')"
		NombreTabla="$(printf '%s' "$NombreTabla" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos donde encontrar la tabla:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$NombreTabla" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Tabla a informar:"
			read NombreTabla
		fi
		if [ "$NombreTabla" != "" ] ; then
			if [ "$NombreCampo" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
				echo "Campo concreto a informar:"
				read NombreCampo
			fi
			if [ "$NombreCampo" != "" ] ; then
				EjecutarSQLDecorado "SELECT * FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$NombreBD\" AND Table_NAME = \"$NombreTabla\" AND COLUMN_NAME = \"$NombreCampo\";"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				echo "PROBLEMA: No se ha especificado la columna para informar."
			fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado tabla para informar."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Column_list ()
{
	Table_show "$@"
	return $?
}

Column ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"listar" ) CurrentAction="list" ;;
		"lista" ) CurrentAction="list" ;;
		"propiedades" ) CurrentAction="properties" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para campo:"
			echo "	column list         (lista de columnas en una tabla de datos)"
			echo "	column properties   (Ver todo detalle de una columna de tabla)"
			echo "	column show         (Mostrar el contenido de una columna de tabla de datos)"
#			echo "	column create       (Añadir una columna en una tabla de datos existente)"
#			echo "	column remove       (Eliminar una columna de una tabla de datos)"
			StatusCode=1
			;;
		* )
			Column_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

Table_show ()
{
	local NombreBD="$1"
	local NombreTabla="$2"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$(printf '%s' "$NombreBD" | grep -e '.\..')" != "" ] && [ "$NombreTabla" = "" ] ; then
		NombreTabla="$(printf '%s' "$NombreBD" | cut -f 2- -d '.')"
		NombreBD="$(printf '%s' "$NombreBD" | cut -f 1 -d '.')"
	fi
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos donde encontrar la tabla:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$NombreTabla" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Tabla a informar:"
			read NombreTabla
		fi
		if [ "$NombreTabla" != "" ] ; then
			EjecutarSQLDecorado "SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_KEY AS \"Key\", COLUMN_DEFAULT AS \"Default\", CHARACTER_SET_NAME AS \"CharSet\", EXTRA FROM information_schema.COLUMNS WHERE Table_SCHEMA = \"$NombreBD\" AND Table_NAME = \"$NombreTabla\";"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado tabla para informar."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Table_list ()
{
	local NombreBD="$1"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Base de datos a informar:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		EjecutarSQLDecorado "SELECT Table_NAME, ENGINE, Table_ROWS, CONCAT(ROUND(((CAST(DATA_LENGTH + INDEX_LENGTH AS SIGNED) - CAST(DATA_FREE AS SIGNED)) / 1024 / 1024), 2),' MiB') AS 'Datos', UPDATE_TIME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado base de datos para informar."
	fi
	return $StatusCode
}

Table_copy ()
{
	local BaseTableSrc="$1"
	local BaseTableDest="$2"
	local CreateCommand=""
	local LastStatus=0
	local StatusCode=0
	
	if [ "$BaseTableSrc" = "" ] ; then
		printf '%s\n' 'ERROR: Source database.table not specified.' 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$BaseTableDest" = "" ] ; then
		printf '%s\n' 'ERROR: Source database.table not specified as next parameter.' 1>&2
		LastStatus=102 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$BaseTableSrc" != "" ] && [ "$BaseTableSrc" = "$BaseTableDest" ] ; then
		printf '%s\n' 'ERROR: Source and destination database.table must not be the same.' 1>&2
		LastStatus=103 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		CreateCommand="$(EjecutarSQLBruto "SHOW CREATE TABLE ${BaseTableSrc};")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		CreateCommand="$(echo $CreateCommand | sed -e "s/.*CREATE TABLE.*\` [(]/CREATE TABLE ${BaseTableDest} ${ParO}/g")"
		EjecutarSQLBruto "$CreateCommand"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		EjecutarSQLBruto "INSERT INTO $BaseTableDest SELECT * FROM ${BaseTableSrc};"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Table_delete ()
{
	local BaseTable="$1"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$BaseTable" = "" ] ; then
		printf '%s\n' 'ERROR: database.table not specified.' 1>&2
		LastStatus=101 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		EjecutarSQLBruto "DROP TABLE ${BaseTable};"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Table ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"listar" ) CurrentAction="list" ;;
		"lista" ) CurrentAction="list" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
		"eliminar" ) CurrentAction="delete" ;;
		"remove" ) CurrentAction="delete" ;;
		"drop" ) CurrentAction="delete" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para tabla:"
			echo "	table list    (lista de tablas en una base de datos)"
			echo "	table show    (Ver campos y propiedades de una tabla de datos)"
			echo "	table copy    (Clonar tabla con datos)"
#			echo "	table crear   (Crear una tabla en una base de datos existente)"
			echo "	table delete  (Eliminar una tabla de datos)"
			StatusCode=1
			;;
		* )
			Table_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

User_delete ()
{
	local NombreUsuario="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a suprimir con sus permisos:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		EjecutarSQLDecorado "DROP USER '${NombreUsuario}'@'localhost';"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		EjecutarSQLDecorado "FLUSH PRIVILEGES;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna cuenta de usuario."
	fi
	return $StatusCode
}

User_create ()
{
	local NombreUsuario="$1"
	local Contrasenya="$2"
	local NombreBD="$3"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a crear:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		if [ "$Contrasenya" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Contraseña (ahora visible) para la cuenta de usuario:"
			read Contrasenya
		fi
		if [ "$Contrasenya" = "." ] || [ "$Contrasenya" = "+" ] ; then
			if [ "$Contrasenya" = "+" ] ; then
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1).$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				fi
			else
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
				fi
			fi
			printf '%s\n' "Generating new random password: ${sINFO}${Contrasenya}${fRESET}"
		fi
		if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "¿A qué base de datos tendrá completo acceso el usuario?"
			read NombreBD
		fi
		if [ "$NombreBD" != "" ] ; then
			EjecutarSQLDecorado "GRANT ALL ON ${NombreBD}.* TO '${NombreUsuario}'@'localhost' IDENTIFIED BY \"${Contrasenya}\";"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			EjecutarSQLDecorado "FLUSH PRIVILEGES;"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				echo "Permisos creados."
			fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado base de datos para los permisos."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre para usuario de MySQL/MariaDB."
	fi
	return $StatusCode
}

User_crear ()
{
	User_create "$@"
	return $?
}

User_passwd ()
{
	local NombreUsuario="$1"
	local Contrasenya="$2"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a actualizar:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		if [ "$Contrasenya" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Nueva contraseña (ahora visible) para la cuenta de usuario:"
			read Contrasenya
		fi
		if [ "$Contrasenya" = "." ] || [ "$Contrasenya" = "+" ] ; then
			if [ "$Contrasenya" = "+" ] ; then
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1).$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				fi
			else
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
				fi
			fi
			printf '%s\n' "Generating new random password: ${sINFO}${Contrasenya}${fRESET}"
		fi
		EjecutarSQLDecorado "ALTER USER '${NombreUsuario}'@'localhost' IDENTIFIED BY '${Contrasenya}';"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		EjecutarSQLDecorado "FLUSH PRIVILEGES;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "${sGOODNORMAL}Efectivo.${fRESET}"
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre para usuario de MySQL/MariaDB."
	fi
	return $StatusCode
}

User_password ()
{
	User_passwd "$@"
	return $?
}

User_export ()
{
	local User_Name="$1"
	local File_Name="$2"
	local User_Password=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$User_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de cuenta no especificado como primer parámetro.${fRESET}" 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$File_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de fichero no especificado como segundo parámetro.${fRESET}" 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		User_Password="$(EjecutarSQLBruto "SELECT Password FROM mysql.user WHERE User = \"${User_Name}\";")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$User_Password" != "" ] ; then
			printf '%s' "$User_Password" > "$File_Name"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				printf '%s\n' "Contraseña ${ParO}cifrada${ParC} de la cuenta \"$User_Name\" exportada:"
				printf '%s\n' "$User_Password"
			fi
		else
			echo "${sERROR}E: Cuenta de usuario \"$User_Name\" no encontrada.${fRESET}" 1>&2
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	return $StatusCode
}

User_import ()
# TO DO: Implement use of Remote_create()
{
	local User_Name="$1"
	local File_Name="$2"
	local DB_Name="$3"
	local User_Password=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$User_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de cuenta a re/crear no especificado como primer parámetro.${fRESET}" 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$File_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de fichero no especificado como segundo parámetro, para obtener la contraseña.${fRESET}" 1>&2
		echo "I: Se espera que el fichero contenga una sola línea con la contraseña ya cifrada para MySQL/MariaDB." 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		User_Password="$(cat "$File_Name")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		User_create "$User_Name" "$User_Password" "$DB_Name"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
#		EjecutarSQLDecorado "UPDATE mysql.user SET Password = \"${User_Password}\" WHERE User = \"${User_Name}\";"
		EjecutarSQLDecorado "SET PASSWORD FOR '${User_Name}'@'localhost' = \"${User_Password}\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

User_show ()
{
	local NombreUsuario="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario por analizar:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		echo "Información de cuenta:"
		EjecutarSQLDecorado "SELECT User, Host, Password FROM mysql.user WHERE User = \"$NombreUsuario\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo ""
		echo "Permisos sobre el servicio:"
		EjecutarSQLDecorado "SELECT * FROM information_schema.USER_PRIVILEGES WHERE GRANTEE LIKE \"'$NombreUsuario'%\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo ""
		echo "Permisos sobre bases de datos:"
		EjecutarSQLDecorado "SELECT * FROM information_schema.SCHEMA_PRIVILEGES WHERE GRANTEE LIKE \"'$NombreUsuario'%\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna cuenta de usuario."
	fi
	return $StatusCode
}

User_list ()
{
	local LastStatus=0
	local StatusCode=0
	
	EjecutarSQLDecorado "SELECT User, Host FROM mysql.user;"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

User_host ()
{
	local NombreUsuario="$1"
	local ClientHost="$2"
	local ClientHost_Old=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a actualizar:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		ClientHost_Old="$(EjecutarSQLBruto "SELECT Host FROM mysql.user WHERE User = '${NombreUsuario}' LIMIT 1;")"
		if [ "$ClientHost_Old" != "" ] ; then
			printf '%s\n' "La cuenta de usuario \"${NombreUsuario}\" hasta ahora autoriza las sesiones desde: ${sVALUE}${ClientHost_Old}${fRESET}"
		fi
		if [ "$ClientHost" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Nuevo origen-cliente para conectarse como $NombreUsuario (% para todos):"
			read ClientHost
		fi
		if [ "$ClientHost" = "" ] ; then
			printf '%s\n' "${sWARN}W: Se especifica Host vacio, y probablemente la cuenta no podra iniciar sesion.${fRESET}" 1>&2
		else
			if [ "$ClientHost" = "$ClientHost_Old" ] ; then
				printf '%s\n' "${sINFO}I: El Host es el mismo que habia, pero se reescribe igualmente.${fRESET}"
			fi
		fi
#		EjecutarSQLDecorado "UPDATE mysql.user SET Host = \"${ClientHost}\" WHERE User = \"${NombreUsuario}\";"
#		EjecutarSQLDecorado "GRANT ALL ON ${NombreBD}.* TO '${NombreUsuario}'@'%';"
		EjecutarSQLDecorado "RENAME USER '${NombreUsuario}'@'${ClientHost_Old}' TO '${NombreUsuario}'@'${ClientHost}';"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "${sGOODNORMAL}Efectivo.${fRESET}"
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre para usuario de MySQL/MariaDB."
	fi
	return $StatusCode
}

User ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	
	case "$CurrentAction" in
		"listar" ) CurrentAction="list" ;;
		"lista" ) CurrentAction="list" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
		"crear" ) CurrentAction="create" ;;
		"eliminar" ) CurrentAction="delete" ;;
		"remove" ) CurrentAction="delete" ;;
		"drop" ) CurrentAction="delete" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para usuario local:"
			echo "	user list      (lista de usuarios con permisos de MySQL/MariaDB)"
			echo "	user show      (Ver propiedades de un usuario de MySQL/MariaDB)"
			echo "	user create    (Crear permisos para un nuevo usuario local en MySQL/MariaDB)"
			echo "	user delete    (Eliminar una cuenta de usuario de MySQL/MariaDB)"
			echo "	user passwd    (Cambiar contraseña de una cuenta)"
			echo "	user host      (Cambiar origen-cliente para una cuenta)"
			echo "	user import    (Crear o actualizar un usuario con la contraseña de un fichero)"
			echo "	user export    (Guardar la contraseña cifrada de una cuenta en un fichero)"
			StatusCode=1
			;;
		* )
			User_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
}

Remote_delete ()
{
	local NombreUsuario="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a suprimir con sus permisos:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		EjecutarSQLDecorado "DROP USER '${NombreUsuario}'@'%';"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		EjecutarSQLDecorado "FLUSH PRIVILEGES;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna cuenta de usuario."
	fi
	return $StatusCode
}

Remote_create ()
{
	local NombreUsuario="$1"
	local Contrasenya="$2"
	local NombreBD="$3"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario remoto a crear:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		if [ "$Contrasenya" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Contraseña (ahora visible) para la cuenta de usuario remoto:"
			read Contrasenya
		fi
		if [ "$Contrasenya" = "." ] || [ "$Contrasenya" = "+" ] ; then
			if [ "$Contrasenya" = "+" ] ; then
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1).$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				fi
			else
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
				fi
			fi
			printf '%s\n' "Generating new random password: ${sINFO}${Contrasenya}${fRESET}"
		fi
		if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "¿A qué base de datos tendrá completo acceso el usuario remoto?"
			read NombreBD
		fi
		if [ "$NombreBD" != "" ] ; then
			EjecutarSQLDecorado "GRANT ALL ON ${NombreBD}.* TO '${NombreUsuario}'@'%' IDENTIFIED BY \"${Contrasenya}\";"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			EjecutarSQLDecorado "FLUSH PRIVILEGES;"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				echo "Permisos creados para el acceso remoto."
			fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado base de datos para los permisos."
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre para usuario de MySQL/MariaDB."
	fi
	return $StatusCode
}

Remote_passwd ()
{
	local NombreUsuario="$1"
	local Contrasenya="$2"
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreUsuario" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Nombre de usuario a actualizar:"
		read NombreUsuario
	fi
	if [ "$NombreUsuario" != "" ] ; then
		if [ "$Contrasenya" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Nueva contraseña (ahora visible) para la cuenta de usuario:"
			read Contrasenya
		fi
		if [ "$Contrasenya" = "." ] || [ "$Contrasenya" = "+" ] ; then
			if [ "$Contrasenya" = "+" ] ; then
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1).$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ')"
				fi
			else
				if Is_Executable apg ; then
					Contrasenya="$(apg -a 0 -M NCL -m 8 -x 10 | head -n 1)"
				else
					printf '%s\n' "${sWARN}W: apg utility not found to generate better passwords.${fRESET}" 1>&2
					Contrasenya="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | tr -s '\t' ' ' | cut -f 1 -d ' ' | cut -c 1-10)"
				fi
			fi
			printf '%s\n' "Generating new random password: ${sINFO}${Contrasenya}${fRESET}"
		fi
		EjecutarSQLDecorado "ALTER USER '${NombreUsuario}'@'%' IDENTIFIED BY '${Contrasenya}';"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		EjecutarSQLDecorado "FLUSH PRIVILEGES;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "${sGOODNORMAL}Efectivo.${fRESET}"
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre para usuario de MySQL/MariaDB."
	fi
	return $StatusCode
}

Remote_password ()
{
	Remote_passwd "$@"
	return $?
}

Remote_export ()
{
	User_export "$@"
	return $?
}

Remote_import ()
# TO DO: Implement use of Remote_create()
{
	local User_Name="$1"
	local File_Name="$2"
	local DB_Name="$3"
	local User_Password=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$User_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de cuenta a re/crear no especificado como primer parámetro.${fRESET}" 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ "$File_Name" = "" ] ; then
		echo "${sERROR}E: Nombre de fichero no especificado como segundo parámetro, para obtener la contraseña.${fRESET}" 1>&2
		echo "I: Se espera que el fichero contenga una sola línea con la contraseña ya cifrada para MySQL/MariaDB." 1>&2
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		User_Password="$(cat "$File_Name")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		User_create "$User_Name" "$User_Password" "$DB_Name"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
#		EjecutarSQLDecorado "UPDATE mysql.user SET Password = \"${User_Password}\" WHERE User = \"${User_Name}\";"
		EjecutarSQLDecorado "SET PASSWORD FOR '${User_Name}'@'%' = \"${User_Password}\";"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Remote_show ()
{
	User_show "$@"
	return $?
}

Remote_list ()
{
	User_list "$@"
	return $?
}

Remote_host ()
{
	User_host "$@"
	return $?
}

Remote ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	
	case "$CurrentAction" in
		"listar" ) CurrentAction="list" ;;
		"lista" ) CurrentAction="list" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
		"crear" ) CurrentAction="create" ;;
		"eliminar" ) CurrentAction="delete" ;;
		"remove" ) CurrentAction="delete" ;;
		"drop" ) CurrentAction="delete" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para usuario remoto:"
			echo "	remote list      (lista de usuarios con permisos de MySQL/MariaDB)"
			echo "	remote show      (Ver propiedades de un usuario de MySQL/MariaDB)"
			echo "	remote create    (Crear permisos para un nuevo usuario en MySQL/MariaDB)"
			echo "	remote delete    (Eliminar una cuenta de usuario de MySQL/MariaDB)"
			echo "	remote passwd    (Cambiar contraseña de una cuenta)"
			echo "	remote host      (Cambiar origen-cliente para una cuenta)"
			echo "	remote import    (Crear o actualizar un usuario con la contraseña de un fichero)"
			echo "	remote export    (Guardar la contraseña cifrada de una cuenta en un fichero)"
			StatusCode=1
			;;
		* )
			Remote_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
}

Db_export ()
# Notas:
#	- Si como nombre de fichero se proporciona un directorio existente, crea un fichero plano .sql dentro del directorio
{
	local NombreBD="$1"
	local NombreFichero="$2"
	local Options="$3"
	local ExtensionFichero=""
	local FicheroTemporal=""
	local ElDirTemp=""
	local Utility='mysqldump'
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] ; then
		echo "EXPORTADOR DE BASE DE DATOS"
		echo ""
		echo "Copia una base de datos de MySQL/MariaDB en un fichero."
		echo ""
		echo "Es necesario especificar almenos el nombre de base de datos:"
		echo ""
		echo "	db export nombreBD rutafichero"
		echo ""
		echo "Donde:"
		echo "	nombreBD es la base de datos existente"
		echo "	rutafichero es el fichero donde guardar los datos SQL"
		echo ""
		echo "Ejemplo1: $NombrePrograma db export myblog myblog.sql"
		echo "Ejemplo2: $NombrePrograma db export onewiki /var/backups/mysql_databases/onewiki/$(date '+%F').sql.bz2"
		echo "Ejemplo3: $NombrePrograma db export onewiki myblog.sql.bz2 --compatible"
		echo ""
		echo "Nota: Si se especifica un directorio existente, se crea un fichero dentro con el nombre de la BD."
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if ! Is_Executable mysqldump && ! Is_Executable mariadb-dump ; then
		echo "PROBLEMA: No se encuentra la utilidad 'mariadb-dump'"
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		if Is_Executable mariadb-dump ; then Utility='mariadb-dump' ; fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$NombreFichero" = "" ] ; then
			NombreFichero="${NombreBD}.sql"
		fi
		if [ -d "$NombreFichero" ] ; then
			NombreFichero="${NombreFichero}/${NombreBD}.sql"
		fi
		ElDirTemp="$TempDir"
		if [ "$TempDir" = "" ] ; then
			ElDirTemp="$(Dirname "$NombreFichero")"
		fi
#		ExtensionFichero="$(echo "$NombreFichero" | sed -e "s/\./\n/g" | tail --lines=1)"
#		SubExtensionFichero="$(echo "$NombreFichero" | sed -e "s/\./\n/g" | tail --lines=2 | head --lines=1)"
		ExtensionFichero="$(printf '%s\n' "$NombreFichero" | tr -s '/' '\n' | tail -n 1)"  # Problems with a path begun with "-" in old GNU basename versions
		ExtensionFichero="$(printf '%s\n' "$ExtensionFichero" | tr '.' '\n' | tail -n 1)"
		ExtensionFichero="$(Lowercase "$ExtensionFichero")"
		SubExtensionFichero="$(printf '%s\n' "$NombreFichero" | tr -s '/' '\n' | tail -n 1)"  # Problems with a path begun with "-" in old GNU basename versions
		SubExtensionFichero="$(printf '%s\n' "$SubExtensionFichero" | tr '.' '\n' | tail -n 2 | head -n 1)"
		SubExtensionFichero="$(Lowercase "$SubExtensionFichero")"

		if [ "$SubExtensionFichero" = "tar" ] ; then ExtensionFichero="${SubExtensionFichero}.${ExtensionFichero}" ; fi
		case "$ExtensionFichero" in
			"gz" )
				if ! Is_Executable gzip ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'gzip'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"tar.gz" )
				if ! Is_Executable tar || ! gzip ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'gzip' o 'tar'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"tar.bz2" )
				if ! Is_Executable tar || ! Is_Executable bzip2 ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'bzip2' o 'tar'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"bz2" )
				if ! Is_Executable bzip2 ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'bzip2'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"tar.lzma" )
				if ! Is_Executable tar || ! Is_Executable lzma ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'bzip2' o 'tar'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"lzma" )
				if ! Is_Executable lzma ; then
					echo "PROBLEMA: No se encuentra el programa de compresión 'bzip2' o 'tar'"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				;;
			"sql" )
				sleep 0
				;;
			* )
				echo "PROBLEMA: Extension de fichero \"$ExtensionFichero\" no reconocida."
				echo "SOLUCION: Especifique un nombre de fichero con una de las extensiones:"
				echo "          .sql .gz .bz2 .lzma"
				LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				;;
		esac
		if [ $StatusCode -eq 0 ] ; then
			if [ "$Options" = "--compatible" ] ; then
				Options="--compatible=mysql40 --default-character-set=utf8"
			fi
			FixSyntax1=''
			FixSyntax2=''
			if [ "$(printf '%s' "$Options" | grep -e '--compatible')" != "" ] ; then
				FixSyntax1='-e'
				FixSyntax2='s@^/\*\(.*\)\\-\(.*\)\*/@/*\1-\2*/@g'
			fi
			FicheroTemporal="$(NuevoTemporalSeguro "admidb" "sql" "$ElDirTemp")"
			PaqueteTemporal="$(NuevoTemporalSeguro "admidb" "$ExtensionFichero" "$ElDirTemp")"
			PreparePassword
			ServiceVersion="$($Utility --version | tr -s '\t ' '\n' | grep -e '^[0-9].*\.[0-9]' | head -n 1 | cut -f 1 -d '.')"
			if Is_IntegerNr "$ServiceVersion" && [ $ServiceVersion -ge 5 ] ; then
				# ENGINE=InnoDB ENGINE=MyISAM
				# https://stackoverflow.com/questions/41229248/getting-error-during-uploading-database-to-my-server-sql-error-1064
				if [ "$PasswordReady" = "1" ] ; then
					echo "Exportando ${sINFO}${NombreBD}${fRESET} de MySQL/MariaDB..."
#					mysqldump --user="$ServerUser" "--password=$ServerPassword" -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction "$NombreBD" | sed -e 's|^/*\(.*\)\\-\(.*\)\*/|/*\1-\2*/|g' > "$FicheroTemporal"
					$Utility --user="$ServerUser" "--password=$ServerPassword" -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction $Options "$NombreBD" | sed $FixSyntax1 $FixSyntax2 -e 's|TYPE=InnoDB|ENGINE=InnoDB|gi' -e 's|TYPE=MyISAM|ENGINE=MyISAM|gi' > "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					if [ "$AskPasswordNotice" != "" ] ; then printf '%s\n' "$AskPasswordNotice" ; fi
# Está por ver si esto funciona
#					mysqldump --user="$ServerUser" --password -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction "$NombreBD" | sed -e 's|^/*\(.*\)\\-\(.*\)\*/|/*\1-\2*/|g' > "$FicheroTemporal"
					$Utility --user="$ServerUser" --password -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction $Options "$NombreBD" | sed $FixSyntax1 $FixSyntax2 -e 's|TYPE=InnoDB|ENGINE=InnoDB|gi' -e 's|TYPE=MyISAM|ENGINE=MyISAM|gi' > "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			else
				if [ "$FixSyntax1" != "" ] ; then
					FixSyntax1="sed $FixSyntax1"
				else
					FixSyntax1='cat'
				fi
				if [ "$PasswordReady" = "1" ] ; then
					echo "Exportando ${sINFO}${NombreBD}${fRESET} de MySQL/MariaDB..."
#					mysqldump --user="$ServerUser" "--password=$ServerPassword" -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction "$NombreBD" | sed -e 's|^/*\(.*\)\\-\(.*\)\*/|/*\1-\2*/|g' > "$FicheroTemporal"
					$Utility --user="$ServerUser" "--password=$ServerPassword" -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction $Options "$NombreBD" | $FixSyntax1 $FixSyntax2 > "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				else
					if [ "$AskPasswordNotice" != "" ] ; then printf '%s\n' "$AskPasswordNotice" ; fi
# Está por ver si esto funciona
#					mysqldump --user="$ServerUser" --password -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction "$NombreBD" | sed -e 's|^/*\(.*\)\\-\(.*\)\*/|/*\1-\2*/|g' > "$FicheroTemporal"
					$Utility --user="$ServerUser" --password -f --hex-blob -q --add-drop-table --add-locks --create-options -K --single-transaction $Options "$NombreBD" | $FixSyntax1 $FixSyntax2 > "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			case "$ExtensionFichero" in
				"gz" )
					echo "Comprimiendo..."
					cat "$FicheroTemporal" | gzip --rsyncable --best > "$PaqueteTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				"tar.gz" )
					echo "Comprimiendo..."
					tar -czf "$PaqueteTemporal" "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				"tar.bz2" )
					echo "Comprimiendo..."
					tar -cjf "$PaqueteTemporal" "$FicheroTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				"bz2" )
					echo "Comprimiendo..."
					cat "$FicheroTemporal" | bzip2 --best > "$PaqueteTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				"tar.lzma" )
					echo "Comprimiendo..."
					tar -cf - "$FicheroTemporal" | lzma -zfc -6 > "$PaqueteTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				"lzma" )
					echo "Comprimiendo..."
					cat "$FicheroTemporal" | lzma -zfc -6 > "$PaqueteTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;

				"sql" )
					mv "$FicheroTemporal" "$PaqueteTemporal"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
				* )
					echo "PROBLEMA: Extension de fichero \"$ExtensionFichero\" no reconocida."
					echo "SOLUCION: Especifique un nombre de fichero con una de las extensiones:"
					echo "          .sql .gz .bz2 .lzma"
					LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					;;
			esac
		fi
		if [ $StatusCode -eq 0 ] && [ ! -d "$(Dirname "$NombreFichero")" ] ; then
			mkdir "$(Dirname "$NombreFichero")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			mv "$PaqueteTemporal" "$NombreFichero"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			printf '%s\n' "${sGOODNORMAL}${NombreBD} exportada${fRESET} en: $NombreFichero ${ParO}$(NumeroResumit $(PathSize "$NombreFichero") '2L ')${ParC}"
			EliminarTemporalSeguro "$PaqueteTemporal"
			EliminarTemporalSeguro "$FicheroTemporal"
		fi
	fi
	return $StatusCode
}

Db_backup ()
{
	printf '%s\n' "${sERROR}E: Use 'export' en lugar de 'backup' para salvaguardar bases de datos.${fRESET}" 1>&2
	LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
}

Databases_export ()
{
	local DirectorioDestino="$1"
	local ExtensionesDestino="$2"
	local ListaBBDD=""
	local BDActual=""
	local LastStatus=0
	local StatusCode=0
	
	if [ $# -gt 0 ] ; then
		ListaBBDD="$(EjecutarSQLBruto "SHOW DATABASES;" | sort)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$DirectorioDestino" = "" ] ; then
				DirectorioDestino="."
			else
				mkdir -p "$DirectorioDestino"
			fi
			if [ "$ExtensionesDestino" = "" ] ; then ExtensionesDestino="sql" ; fi
			for BDActual in $ListaBBDD ; do
				Db_export "$BDActual" "${DirectorioDestino}/${BDActual}.${ExtensionesDestino}"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				printf '\n'
			done
		fi
	else
		printf '%s\n' "Sintaxis:"
		printf '%s\n' "	$ProgramName ddbb backup [directorio] [extension]"
		printf '%s\n' "Ejemplo:"
		printf '%s\n' "	$ProgramName ddbb backup /var/backups/mysql_databases tar.bz2"
	fi
	return $StatusCode
}

Databases_backup ()
{
	local DirectorioDestino="$1"
	local ExtensionesDestino="$2"
	local ListaBBDD=""
	local BDActual=""
	local DestName=''
	local LastStatus=0
	local StatusCode=0
	
#	if [ $# -gt 0 ] ; then
		ListaBBDD="$(EjecutarSQLBruto "SHOW DATABASES;" | sort)"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$DirectorioDestino" = "" ] ; then
#				DirectorioDestino="."
				DirectorioDestino="/var/backups/mysql_databases"
				printf '%s\n' "Destino no especificado. Seleccionando automáticamente: $DirectorioDestino"
			fi
			if [ "$ExtensionesDestino" = "" ] ; then
#				ExtensionesDestino="sql"
				ExtensionesDestino="tar.gz"
				printf '%s\n' "Empaquetado no especificado. Seleccionando automáticamente: $ExtensionesDestino"
			fi
			for BDActual in $ListaBBDD ; do
				mkdir -p "${DirectorioDestino}/${BDActual}"
				DestName="$(date +'%F %T' | tr -s ':' '-' | tr -s ' ' '_')"
				Db_export "$BDActual" "${DirectorioDestino}/${BDActual}/${DestName}.${ExtensionesDestino}"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#				if [ $StatusCode -eq 0 ] ; then
#					du -Psxh "${DirectorioDestino}/${BDActual}/${DestName}.${ExtensionesDestino}"
#				fi
				printf '\n'
			done
		fi
#	else
#		printf '%s\n' "Sintaxis:"
#		printf '%s\n' "	$ProgramName ddbb backup [directorio] [extension]"
#		printf '%s\n' "Ejemplo:"
#		printf '%s\n' "	$ProgramName ddbb backup /var/backups/mysql_databases tar.bz2"
#	fi
	return $StatusCode
}

Databases_scan ()
{
	local DdbbList=''
	local CurDB=''
	local DestName=''
	local LastStatus=0
	local StatusCode=0
	
	DdbbList="$(EjecutarSQLBruto "SHOW DATABASES;" | sort)"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
		for CurDB in $DdbbList ; do
			echo "========== $CurDB =========="
			Db_scan "$CurDB"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			printf '\n'
		done
	fi
	return $StatusCode
}

Databases ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"exportar" ) CurrentAction="export" ;;
		"exporta" ) CurrentAction="export" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para BBDD:"
			echo "	ddbb list   (listar sólo nombres de las bases de datos)"
			echo "	ddbb export (acciones para guardar todas las BD en ficheros)"
			echo "	ddbb backup (acciones para guardar todas las BD en ficheros organizados)"
			echo "	ddbb check  (Revisar todas las bases de datos)"
			echo "	ddbb repair (Revisar y autoreparar todas las bases de datos)"
			echo "	ddbb scan   (buscar datos sospechosos de asalto)"
			echo ""
			echo "Ejemplo1: $NombrePrograma ddbb export /var/backups bz2"
			echo "Ejemplo2: $NombrePrograma ddbb export"
			echo "Ejemplo3: $NombrePrograma ddbb export . sql"
			echo "Ejemplo4: $NombrePrograma ddbb backup /var/backups/mysql_databases sql.bz2"
			echo "Ejemplo5: $NombrePrograma ddbb backup"
			echo "Ejemplo6: $NombrePrograma ddbb backup . sql.gz"
			StatusCode=1
			;;
		* )
			Databases_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

Db_rename ()
{
	local NombreOrigen="$1"
	local NombreDestino="$2"
	local RespuestaSobreescribir="$3"
	local BDEncontrada=""
	local ListaTablas=""
	local TablaActual=""
	local BDTemporal=""
	
	local LastStatus=0
	local StatusCode=0
	
	if [ $StatusCode -eq 0 ] ; then
		if [ "$NombreOrigen" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Escribe el nombre antiguo de la base de datos:"
			read NombreOrigen
		fi
		BDEncontrada="$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreOrigen}$")"
		if [ "$BDEncontrada" = "" ] ; then
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se encuentra la base de datos «${NombreOrigen}» en MySQL/MariaDB."
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$NombreDestino" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
			echo "Escribe el nombre nuevo para la base de datos:"
			read NombreDestino
		fi
		BDEncontrada="$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreDestino}$")"
		if [ "$BDEncontrada" != "" ] ; then
			if [ "$ADMIDB_BATCH" != "1" ] ; then
				if [ "$RespuestaSobreescribir" = "" ] ; then
					echo "La base de datos de destino (${NombreDestino}) ya existe;"
					echo "¿Reemplazarla? [s/N]"
				fi
			else
				if [ "$RespuestaSobreescribir" = "" ] ; then RespuestaSobreescribir='yes' ; fi
			fi
			RespuestaSobreescribir="$(RespostaLletra $RespuestaSobreescribir)"
			if [ "$RespuestaSobreescribir" != "s" ] && [ "$RespuestaSobreescribir" != "y" ] ; then
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				echo "No se procede con el cambio de nombre; ${NombreDestino} ya existe."
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$NombreOrigen" != "" ] && [ "$NombreDestino" != "" ] ; then
			BDEncontrada="$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreDestino}$")"
			if [ "$BDEncontrada" != "" ] ; then
				# Hacemos una «copia» de seguridad del destino, por si hay que revertir
				BDTemporal="$(NuevaBDTemporal "admidb")"
				ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreDestino\";")"
				for TablaActual in $ListaTablas ; do
					EjecutarSQLBruto "RENAME TABLE ${NombreDestino}.${TablaActual} TO ${BDTemporal}.${TablaActual};"
					LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				done
				EjecutarSQLBruto "DROP DATABASE IF EXISTS ${NombreDestino};"
			fi
			ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreOrigen\";")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				EjecutarSQLBruto "DROP DATABASE IF EXISTS ${NombreDestino};"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				EjecutarSQLBruto "CREATE DATABASE ${NombreDestino};"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				for TablaActual in $ListaTablas ; do
					if [ $StatusCode -eq 0 ] ; then
						EjecutarSQLBruto "RENAME TABLE ${NombreOrigen}.${TablaActual} TO ${NombreDestino}.${TablaActual};"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					fi
				done
				if [ $StatusCode -eq 0 ] ; then
					EjecutarSQLBruto "DROP DATABASE IF EXISTS ${NombreOrigen};"
				fi
			fi
			if [ "$BDTemporal" != "" ] ; then
				if [ $StatusCode -eq 0 ] ; then
					# Eliminamos la «copia» de seguridad
					EjecutarSQLBruto "DROP DATABASE IF EXISTS ${BDTemporal};"
				else
					# Restauramos la «copia» de seguridad
					EjecutarSQLBruto "DROP DATABASE IF EXISTS ${NombreDestino};"
					EjecutarSQLBruto "CREATE DATABASE ${NombreDestino};"
					ListaTablas="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDTemporal\";")"
					for TablaActual in $ListaTablas ; do
						EjecutarSQLBruto "RENAME TABLE ${BDTemporal}.${TablaActual} TO ${NombreDestino}.${TablaActual};"
						LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
					done
					EjecutarSQLBruto "DROP DATABASE IF EXISTS ${BDTemporal};"
				fi
			fi
		else
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			echo "PROBLEMA: No se ha especificado el origen o destino."
		fi
	fi
	return $StatusCode
}

Db_import ()
{
	local NombreBD="$1"
	local NombreFichero="$2"
	local SinTemporal="$3"
	local FTemporal=""
	local ExtensionOrigen=""
	local ElDirTemp=""
	local BDTemporal=""
	local RespuestaContinuar=""
	local LastStatus=0
	local StatusCode=0

	if [ "$NombreFichero" = "" ] ; then
		echo "CARGADOR DE BASE DE DATOS"
		echo ""
		echo "Elimina una base de datos de MySQL/MariaDB (si existe),"
		echo "importa un fichero en su lugar."
		echo ""
		echo "Es necesario especificar por linea de comandos"
		echo "una de las 2 sintaxis:"
		echo ""
		echo "	db import nombreBD rutafichero"
		echo "	db import nombreBD rutafichero --notemp"
		echo ""
		echo "Donde:"
		echo "	nombreBD es la base de datos a re/crear"
		echo "	rutafichero es el fichero con los datos a import"
		echo "	--notemp (opcional) permite evitar el error: Changing schema is not allowed."
		echo ""
		echo "Ejemplo1: $NombrePrograma db import myblog myblog_2011-04-19_01h17m.2.sql.gz"
		echo "Ejemplo2: $NombrePrograma db import onewiki /tmp/onewiki_2011-04-19.sql"
		echo ""
		echo "Nota: Si en lugar de un fichero se especifica un directorio, entonces se selecciona de ahí el último alfabeticamente."
		exit 0
	fi
	
	if [ -d "$NombreFichero" ] ; then
		NombreFichero="${NombreFichero}/$(ls -1pr "$NombreFichero" | grep -iE "\.(sql|gz|bz2|lzma)$" | head --lines=1)"
		if [ "$NombreFichero" != "" ] ; then
			Valor="$(printf '%s\n' "$NombreFichero" | tr -s '/' '\n' | tail -n 1)"  # Problems with a path begun with "-" in old GNU basename versions
			echo "Se importará el paquete $Valor"
		else
			echo "PROBLEMA: No se reconoce ningún paquete de base de datos en el directorio."
			LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	
	if [ ! -f "$NombreFichero" ] ; then
		echo "PROBLEMA: Fichero \"$NombreFichero\" no encontrado."
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	if [ $StatusCode -eq 0 ] && [ "$(EjecutarSQLBruto "SHOW DATABASES;" | grep -e "^${NombreBD}$")" != "" ] ; then
		if [ "$ADMIDB_BATCH" = "1" ] ; then
			printf '%s\n' "${sWARN}W: Se reemplaza una base de datos ya existente como \"${NombreBD}\" por el fichero importado.${fRESET}"
		else
			printf '%s\n' "${sWARN}W: Una base de datos llamada $NombreBD ya existe en el servicio.${fRESET}"
			printf '%s' "¿La quiere reemplazar por el fichero importado? [s/N] "
			RespuestaContinuar="$(RespostaLletra $RespuestaContinuar)"
			if [ "$RespuestaContinuar" != "s" ] && [ "$RespuestaContinuar" != "y" ] ; then
				echo "No se importa a $NombreBD"
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$(echo "$NombreBD" | grep -ie 'mysql')" != "" ] || [ "$(echo "$NombreBD" | grep -ie 'schema')" != "" ] ; then
			if [ "$ADMIDB_BATCH" != "1" ] ; then
				printf '%s\n' "${sWARN}W: El nombre de base de datos $NombreBD parece reservado.${fRESET}"
				printf '%s' "¿Seguro que quiere proceder? [s/N] "
				RespuestaContinuar="$(RespostaLletra $RespuestaContinuar)"
			fi
			if [ "$RespuestaContinuar" != "s" ] && [ "$RespuestaContinuar" != "y" ] ; then
				echo "No se importa el nombre reservado $NombreBD"
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		if [ "$(echo "$NombreBD" | grep -ie 'mysql')" != "" ] || [ "$(echo "$NombreBD" | grep -ie 'schema')" != "" ] ; then
			if [ "$ADMIDB_BATCH" != "1" ] ; then
				printf '%s\n' "${sWARN}W: El nombre de base de datos $NombreBD parece reservado.${fRESET}"
				printf '%s' "¿Seguro que quiere proceder? [s/N] "
				RespuestaContinuar="$(RespostaLletra $RespuestaContinuar)"
			fi
			if [ "$RespuestaContinuar" != "s" ] && [ "$RespuestaContinuar" != "y" ] ; then
				echo "No se importa el nombre reservado $NombreBD"
				LastStatus=130 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi
	
	if [ $StatusCode -eq 0 ] ; then
		PreDir="$(pwd)"
		mkdir -p "${TempDir}/db_import.$$"
		chmod u=rwX,g=rX,o= "${TempDir}/db_import.$$"
		cd "${TempDir}/db_import.$$"
		if [ "$SinTemporal" = "" ] ; then
			BDTemporal="$(NuevaBDTemporal "admidb")"
		else
			BDTemporal="$NombreBD"
		fi
		if [ "$(printf '%s' "$NombreFichero" | grep -ie '\.sql$')" = "" ] ; then
			DesempaquetarContenido "$NombreFichero"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] ; then
				SqlFiles="$(find -type f)"
			fi
		else
			SqlFiles="$NombreFichero"
		fi
		if [ $StatusCode -eq 0 ] ; then
			FTemporal="${TempDir}/db_import.$$/load.$$.sql"
			echo "DROP DATABASE IF EXISTS $BDTemporal;" >> "$FTemporal"
			echo "CREATE DATABASE $BDTemporal;" >> "$FTemporal"
			IFS="$(printf "\n\b")" ; for CurFile in $SqlFiles ; do unset IFS
				echo "" >> "$FTemporal"
				echo "USE $BDTemporal;" >> "$FTemporal"
				echo "" >> "$FTemporal"
				cat "$CurFile" | sed -e 's|^/\*\(.*\)\\-\(.*\)\*/|/*\1-\2*/|g' >> "$FTemporal"
			done
		fi
	fi
	if [ $StatusCode -eq 0 ] ; then
		PreparePassword
		if [ "$PasswordReady" = "1" ] ; then
			echo "Cargando en MySQL/MariaDB..."
			"$SqlClient" --default-character-set=utf8 --user="$ServerUser" "--password=$ServerPassword" < "$FTemporal"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		else
			if [ "$AskPasswordNotice" != "" ] ; then printf '%s\n' "$AskPasswordNotice" ; fi
# Está por ver si esto funciona
			"$SqlClient" --default-character-set=utf8 --user="$ServerUser" --password < "$FTemporal"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $LastStatus -ne 0 ] ; then
			echo "Se intentaba cargar el fichero SQL: $FTemporal" 1>&2
		fi
		if [ $StatusCode -eq 0 ] && [ "$BDTemporal" != "$NombreBD" ] ; then
			Db_rename "$BDTemporal" "$NombreBD" si
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
	fi
	if [ "$BDTemporal" != "" ] && [ "$BDTemporal" != "$NombreBD" ] ; then
		EjecutarSQLBruto "DROP DATABASE IF EXISTS ${BDTemporal};"
	fi
	cd "$PreDir"
	if [ $StatusCode -eq 0 ] ; then
		rm -r "${TempDir}/db_import.$$"
		echo "$NombreBD importada."
	else
		echo "Datos temporales en: ${TempDir}/db_import.$$"
	fi
	return $StatusCode
}

Db_restore ()
{
	printf '%s\n' "${sERROR}E: Use 'import' en lugar de 'restore' para restaurar bases de datos.${fRESET}" 1>&2
	LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
}

Db_update ()
{
	local NombreBD="$1"
	local RutaPaquetes="$2"
	local RutaControl="$3"
	local PaqueteSeguir=""
	local EstadoActual=""
	local EstadoAnterior=""
	local LastStatus=0
	local StatusCode=0

	if [ "$NombreBD" = "" ] || [ "$RutaPaquetes" = "" ] || [ "$RutaControl" = "" ] ; then
		echo "ACTUALIZADOR DE BASE DE DATOS"
		echo ""
		echo "Comprueba si una lista de ficheros ha cambiado,"
		echo "y en tal caso importa el último para MySQL/MariaDB."
		echo ""
		echo "Es necesario especificar por linea de comandos:"
		echo ""
		echo "	db actualizar nombreBD rutapaquetes rutacontrol"
		echo ""
		echo "Donde:"
		echo "	nombreBD es la base de datos a re/crear"
		echo "	rutapaquetes es el directorio con los posibles ficheros a importar"
		echo "	rutacontrol es el fichero a crear o usar para el seguimiento"
		echo ""
		echo "Ejemplo1: $NombrePrograma db actualizar myblog /var/backups/databases /var/tmp/actualiza-myblog.dir"
		exit 0
	fi
	
	if [ ! -d "$RutaPaquetes" ] ; then
		echo "PROBLEMA: Directorio \"$RutaPaquetes\" no encontrado."
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	
	if [ $StatusCode -eq 0 ] ; then
		if [ ! -f "$RutaControl" ] ; then
			touch "$RutaControl"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -eq 0 ] || [ ! -f "$RutaPaquetes" ] ; then
				echo "PROBLEMA al crear el fichero de seguimiento $RutaControl"
				LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
	fi

	if [ $StatusCode -eq 0 ] ; then
		PaqueteSeguir="${RutaPaquetes}/$(ls -1pr "$RutaPaquetes" | grep -iE "\.(sql|gz|bz2|lzma)$" | head --lines=1)"
		EstadoActual="$(ls -la "$PaqueteSeguir")"
		EstadoAnterior="$(cat "$RutaControl")"
		if [ "$EstadoActual" != "$EstadoAnterior" ] ; then
			ls -la "$PaqueteSeguir" > "$RutaControl"
			Db_import "$NombreBD" "$RutaPaquetes"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			if [ $StatusCode -ne 0 ] ; then
				# Si falló, marcamos el estado anterior para que se reintente.
				echo "$EstadoAnterior" > "$RutaControl"
			fi
		else
			echo "$EstadoActual"
			echo "No han cambiado los datos a importar. No se actualiza $NombreBD"
		fi
	fi
	return $StatusCode
}

Db_delete ()
{
	local NombreBD="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "${sWARN}¡ATENCION! Se perderan los datos contenidos.${fRESET}"
		echo "Si quiere anular la operación, sólamente pulse [ENTER]."
		echo "Nombre de la base de datos a eliminar:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		EjecutarSQLDecorado "DROP DATABASE $NombreBD;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre."
		echo "          No se ha eliminado nada."
	fi
	return $StatusCode
}

Db_create ()
{
	local NombreBD="$1"
	local CrearSuUsuario="$2"
	local ContrasenyaUsuario="$3"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Escribe el nombre para una nueva base de datos (máx. 64 carácteres, recom.16):"
		echo "(máximo 64 carácteres; se recomienda no pasar de 16):"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		EjecutarSQLDecorado "CREATE DATABASE $NombreBD;"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] ; then
			echo "Base de datos creada en MySQL/MariaDB."
			if [ "$ADMIDB_BATCH" != "1" ] ; then
				if [ "$CrearSuUsuario" = "" ] ; then
					echo "¿Crear un usuario local de MySQL/MariaDB con el mismo nombre y permisos completos para esta BD?"
				fi
				CrearSuUsuario="$(RespostaLletra "$CrearSuUsuario")"
			fi
			if [ "$CrearSuUsuario" = "y" ] || [ "$CrearSuUsuario" = "s" ] ; then
				User_create "$NombreBD" "$ContrasenyaUsuario" "$NombreBD"
			fi
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ningun nombre."
		echo "          No se ha creado nada."
	fi
	return $StatusCode
}

Db_show ()
{
	local NombreBD="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local Options="$1"
	local TablesList=""
	local CurrentTable=""
	local CurrentCount=0
	local CurRowLength=0
	local CurTableWeight=0
	local RutaListado=""
#	local TableNr=0
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Escribe el nombre de la base de datos a informar:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		if [ "$Options" = "--stats" ] ; then
			TablesList="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\" ORDER BY Table_NAME ASC;")"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			RutaListado="${DirTemp}/${ProgramName}.$(id -un).$$.dbshow"
			MkdirPP "$RutaListado" '' u=rw,g=r,o=
			ListadoEncabezados "$RutaListado" A 'Table_NAME' '_RowsCount' '_R.Length' '_Weight~ '
			IFS="$(printf "\n\b")" ; for CurrentTable in $TablesList ; do unset IFS
				CurrentCount=$(EjecutarSQLBruto "SELECT COUNT(*) FROM ${NombreBD}.${CurrentTable};")
				if ! Is_IntegerNr $CurrentCount ; then CurrentCount="" ; fi
				CurRowLength="$(EjecutarSQLBruto "SELECT AVG_ROW_LENGTH FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\" AND Table_NAME = \"${CurrentTable}\" ;")"
				CurTableWeight=''
				if Is_IntegerNr $CurrentCount && Is_IntegerNr $CurRowLength ; then
					CurTableWeight="$(NumeroResumit $(($CurrentCount * $CurRowLength)) 'A02L ')"
				fi
				CurrentCount="$(NiceNumber "$CurrentCount")"
				CurRowLength="$(NiceNumber "$CurRowLength")"
				if [ "$CurRowLength" != "" ] ; then CurRowLength="$CurRowLength B" ; fi
				ListadoNuevoRegistro "$RutaListado" "$CurrentTable" "| $CurrentTable" "$CurrentCount" "$CurRowLength" "$CurTableWeight |"
			done
			ListadoMostrar "$RutaListado" ' | ' a
			rm -r "$RutaListado"
		else
			if [ "$Options" = "--numrows" ] ; then
				TablesList="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\" ORDER BY Table_NAME ASC;")"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#				RutaListado="$(NuevoTemporalSeguro Db_listar)" ; rm -f "$RutaListado" ; mkdir "$RutaListado"
				RutaListado="${DirTemp}/${ProgramName}.$(id -un).$$.dbshow"
				MkdirPP "$RutaListado" '' u=rw,g=r,o=
#				ListadoEncabezados "$RutaListado" 0 'Table_NAME' '_RowsCount'
				ListadoEncabezados "$RutaListado" A 'Table_NAME' '_RowsCount  '
				IFS="$(printf "\n\b")" ; for CurrentTable in $TablesList ; do unset IFS
#					TableNr=$(($TableNr + 1))
					CurrentCount=$(EjecutarSQLBruto "SELECT COUNT(*) FROM ${NombreBD}.${CurrentTable};")
					if ! Is_IntegerNr $CurrentCount ; then CurrentCount="" ; fi
					CurrentCount="$(NiceNumber "$CurrentCount")"
#					ListadoNuevoRegistro "$RutaListado" "$TableNr" "| $CurrentTable" "$CurrentCount |"
					ListadoNuevoRegistro "$RutaListado" "$CurrentTable" "| $CurrentTable" "$CurrentCount |"
				done
				ListadoMostrar "$RutaListado" ' | ' a
#				EliminarTemporalSeguro "$RutaListado"
				rm -r "$RutaListado"
			else
				EjecutarSQLDecorado "SELECT Table_NAME, ENGINE, AVG_ROW_LENGTH AS \"R.Length\", Table_COLLATION AS \"Collation\", Table_ROWS AS \"Rows~\" FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\" ORDER BY Table_NAME ASC;"
				# Table_ROWS is only a rough estimate
				printf '%s\n' 'Note: You can append --stats option to list precise rows count and tables weight.' 1>&2
			fi
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna base de datos"
	fi
	return $StatusCode
}

Db_report ()
{
	local NombreBD="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local Options="$1"
	local TablesList=""
	local CurrentTable=""
	local CurrentCount=0
	local RutaListado=""
#	local TableNr=0
	local LastStatus=0
	local StatusCode=0
	
	if [ "$NombreBD" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Escribe el nombre de la base de datos a informar:"
		read NombreBD
	fi
	if [ "$NombreBD" != "" ] ; then
		printf '%s\n' "== Tablas de la base de datos $NombreBD =="
		TablesList="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$NombreBD\" ORDER BY Table_NAME ASC;")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
#		RutaListado="$(NuevoTemporalSeguro Db_listar)" ; rm -f "$RutaListado" ; mkdir "$RutaListado"
		RutaListado="${DirTemp}/${ProgramName}.$(id -un).$$.dbreport"
		MkdirPP "$RutaListado" '' u=rw,g=r,o=
		ListadoEncabezados "$RutaListado" A 'Table_NAME' '_RowsCount'
		IFS="$(printf "\n\b")" ; for CurrentTable in $TablesList ; do unset IFS
			CurrentCount=$(EjecutarSQLBruto "SELECT COUNT(*) FROM ${NombreBD}.${CurrentTable};")
			if ! Is_IntegerNr $CurrentCount ; then CurrentCount="" ; fi
			ListadoNuevoRegistro "$RutaListado" "$CurrentTable" "| $CurrentTable" "$CurrentCount |"
		done
		ListadoMostrar "$RutaListado" ' | ' a
#		EliminarTemporalSeguro "$RutaListado"
		rm -r "$RutaListado"
		if [ "$TablesList" != "" ] ; then
			IFS="$(printf "\n\b")" ; for CurrentTable in $TablesList ; do unset IFS
				printf '%s\n' ""
				printf '%s\n' "== Campos de la tabla $CurrentTable =="
				Table_show "$NombreBD" "$CurrentTable"
			done
		fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna base de datos"
	fi
	return $StatusCode
}

Db_scan ()
{
	local DbName="$1"
	local CurTable=''
	local Columns=''
	local LastStatus=0
	local StatusCode=0
	
	if [ "$DbName" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Escribe el nombre de la base de datos a informar:"
		read DbName
	fi
	if [ "$DbName" != "" ] ; then
		TablesList="$(EjecutarSQLBruto "SELECT Table_NAME FROM information_schema.TABLES WHERE Table_SCHEMA = \"$DbName\" ORDER BY Table_NAME ASC;")"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		IFS="$(printf "\n\b")" ; for CurTable in $(printf '%s\n' "$TablesList" | grep -ie user) ; do unset IFS
			for CurExpression in admin@nomxwp.info wpadmin@gmail.com archivefeed ; do
				Data_search "$DbName" "$CurTable" . 0 "%${CurExpression}%" 's'
			done
		done
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna base de datos"
	fi
	return $StatusCode
}

Db_list ()
# Analizar tamaño de datos http://www.novell.com/communities/node/8706/check-mysql-database-size-using-sql-query
{
	local ListaBBDD=""
	local BDActual=""
	local TamanyoActual=0
	local LastStatus=0
	local StatusCode=0
	
	ListaBBDD="$(EjecutarSQLBruto 'SHOW DATABASES;' | sort)"
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	if [ $StatusCode -eq 0 ] ; then
#		RutaListado="$(NuevoTemporalSeguro Db_listar)" ; rm -f "$RutaListado" ; mkdir "$RutaListado"
		RutaListado="${DirTemp}/${ProgramName}.$(id -un).$$.dbreport"
		MkdirPP "$RutaListado" '' u=rw,g=r,o=
		ListadoEncabezados "$RutaListado" A 'BASE DE DATOS' 'MOTORES' 'DCharSet' 'Def.Collation.' '_PESO DATOS'
		BDActual="Base de datos"
		for BDActual in $ListaBBDD ; do
#			TamanyoActual="$(EjecutarSQLBruto "SELECT CONCAT(sum(ROUND(((DATA_LENGTH + INDEX_LENGTH - DATA_FREE) / 1024 / 1024),2)),' MiB') AS Size FROM INFORMATION_SCHEMA.TABLES where Table_SCHEMA = \"$BDActual\" ;")"
#			TamanyoActual="$(EjecutarSQLBruto "SELECT sum(DATA_LENGTH + INDEX_LENGTH - DATA_FREE) AS Size FROM INFORMATION_SCHEMA.TABLES where Table_SCHEMA = \"$BDActual\" ;")"
#			Error solucionado con la función CAST(): ERROR 1690 (22003) at line 1: BIGINT UNSIGNED value is out of range in '((`information_schema`.`TABLES`.`DATA_LENGTH` + `information_schema`.`TABLES`.`INDEX_LENGTH`) - `information_schema`.`TABLES`.`DATA_FREE`)'
			MotorActual="$(EjecutarSQLBruto "SELECT ENGINE FROM information_schema.TABLES WHERE Table_SCHEMA = \"$BDActual\";" | sort --unique | grep -ive 'MEMORY')"
			MotorActual="$(echo $MotorActual | tr -s ' ' ',')"
			TamanyoActual="$(EjecutarSQLBruto "SELECT sum(cast(DATA_LENGTH + INDEX_LENGTH AS signed) - CAST(DATA_FREE as signed)) AS Size FROM INFORMATION_SCHEMA.TABLES where Table_SCHEMA = \"$BDActual\";")"
			TamanyoActual="$(echo $TamanyoActual | sed -e 's/ /\n/g' | tail --lines=1)"
			if [ ${#TamanyoActual} -gt 15 ] || [ "$(echo "$TamanyoActual" | grep -e '^-')" != "" ] || [ "$TamanyoActual" = "NULL" ] ; then
				if [ -d "${DatabasesDir}/${BDActual}" ] ; then
					TamanyoActual="$(du --apparent-size -Psxb "${DatabasesDir}/${BDActual}")"
					TamanyoActual=$(UnaParaula () { echo $1; }; UnaParaula $TamanyoActual)
				else
					TamanyoActual="?"
				fi
			fi
			if Is_IntegerNr $TamanyoActual ; then
				TamanyoActual=$(($TamanyoActual * 100 / 1024 / 1024))
				LongTemp1=$((${#TamanyoActual} - 2))
				if [ $LongTemp1 -gt 0 ] ; then
					PartTemp1="$(echo "$TamanyoActual" | cut -c 1-${LongTemp1})"
				else
					PartTemp1=0
				fi
				PartTemp2="$(($TamanyoActual - $(($PartTemp1 * 100))))"
				while [ ${#PartTemp2} -lt 2 ] ; do
					PartTemp2="${PartTemp2}0"
				done
				TamanyoActual="${PartTemp1},${PartTemp2} MiB"
			fi
			CharSet="$(EjecutarSQLBruto "SELECT DEFAULT_CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.SCHEMATA where SCHEMA_NAME = \"$BDActual\";")"
			Collation="$(EjecutarSQLBruto "SELECT SUBSTRING(DEFAULT_COLLATION_NAME, 1, 14) FROM INFORMATION_SCHEMA.SCHEMATA where SCHEMA_NAME = \"$BDActual\";")"
			ListadoNuevoRegistro "$RutaListado" "$BDActual" "$BDActual" "$MotorActual" "$CharSet" "$Collation" "$TamanyoActual"
			printf '.' 1>&2
		done
		printf '\n' 1>&2
		ListadoMostrar "$RutaListado" ' | ' a
#		EliminarTemporalSeguro "$RutaListado"
		rm -r "$RutaListado"
	fi
	return $StatusCode
}

Databases_list ()
{
	local LastStatus=0
	local StatusCode=0
	
	EjecutarSQLBruto 'SHOW DATABASES;'
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Databases_check ()
{
	local LastStatus=0
	local StatusCode=0
	
	if Is_Executable mariadbcheck ; then
		mariadbcheck --all-databases
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		mysqlcheck --all-databases -u root -p
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Databases_repair ()
{
	local LastStatus=0
	local StatusCode=0
	
	if Is_Executable mariadbcheck ; then
		mariadbcheck --all-databases --auto-repair
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		mysqlcheck --all-databases --auto-repair -u root -p
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	fi
	return $StatusCode
}

Db ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"listar" ) CurrentAction="list" ;;
		"lista" ) CurrentAction="list" ;;
		"ver" ) CurrentAction="show" ;;
		"mostrar" ) CurrentAction="show" ;;
		"informe" ) CurrentAction="report" ;;
		"crear" ) CurrentAction="create" ;;
		"eliminar" ) CurrentAction="delete" ;;
		"remove" ) CurrentAction="delete" ;;
		"drop" ) CurrentAction="delete" ;;
		"renombrar" ) CurrentAction="rename" ;;
		"importar" ) CurrentAction="import" ;;
		"actualizar" ) CurrentAction="update" ;;
		"actualiza" ) CurrentAction="update" ;;
		"exportar" ) CurrentAction="export" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para BD:"
			echo "	db list     (listar las bases de datos de MySQL/MariaDB)"
			echo "	db show     (Listar tablas y propiedades de una BD)"
			echo "	db report   (Listar tablas de una BD e informar de cada una de ellas)"
			echo "	db create   (crear una base de datos MySQL/MariaDB y su usuario)"
			echo "	db delete   (eliminar una base de datos de MySQL/MariaDB)"
			echo "	db rename   (cambiar el nombre de una BD en MySQL/MariaDB)"
			echo "	db import   (eliminar una BD y cargar en su lugar un fichero)"
			echo "	db update   (importar de un directorio sólo si ha cambiado)"
			echo "	db export   (guardar una BD en un fichero)"
			echo "	db scan     (buscar datos sospechosos de asalto)"
			echo "	ddbb list   (listar sólo nombres de las bases de datos)"
			echo "	ddbb export (acciones para guardar todas las BD en ficheros)"
			echo "	ddbb backup (acciones para guardar todas las BD en ficheros organizados)"
			echo "	ddbb check  (Revisar todas las bases de datos)"
			echo "	ddbb repair (Revisar y autoreparar todas las bases de datos)"
			echo "	ddbb scan   (buscar datos sospechosos de asalto)"
			StatusCode=1
			;;
		* )
			Db_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

Service_stop ()
{
	local LastStatus=0
	local StatusCode=0
	
	service mysql stop
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Service_start ()
{
	local LastStatus=0
	local StatusCode=0
	
	service mysql start
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Service_restart ()
{
	local LastStatus=0
	local StatusCode=0
	
	Service_parar
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	Service_iniciar
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Run ()
{
	local Instruccion="$1"	# Aun entrecomillando, esto no recoge todas las palabras.
	local LastStatus=0
	local StatusCode=0
	
	if [ "$Instruccion" = "" ] && [ "$ADMIDB_BATCH" != "1" ] ; then
		echo "Instrucción SQL a ejecutar:"
		read Instruccion
	else
		# Apaño para que recoja todas las palabras
		Instruccion="$(echo "$@")"
	fi
	if [ "$Instruccion" != "" ] ; then
		EjecutarSQLDecorado "$Instruccion"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	else
		LastStatus=1 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		echo "PROBLEMA: No se ha especificado ninguna instrucción."
	fi
	LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
	return $StatusCode
}

Service_run ()
{
	Run "$@"
	return $?
}

Service ()
{
	local CurrentAction="$1"
	if [ "$1" != "" ] || [ "$2" != "" ] || [ "$3" != "" ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0
	
	case "$CurrentAction" in
		"parar" ) CurrentAction="stop" ;;
		"para" ) CurrentAction="stop" ;;
		"iniciar" ) CurrentAction="start" ;;
		"inicia" ) CurrentAction="start" ;;
		"reiniciar" ) CurrentAction="restart" ;;
		"reinicia" ) CurrentAction="restart" ;;
		"ejecutar" ) CurrentAction="run" ;;
	esac
	case "$CurrentAction" in
		"--help" | "" )
			echo "Acciones para servicio:"
			echo "	run                (Ejecutar directamente una instrucción SQL)"
			echo "	service stop       (Detener MySQL/MariaDB)"
			echo "	service start      (Ejecutar MySQL/MariaDB)"
			echo "	service restart    (Detener y volver a iniciar)"
#			echo "	service installl   (Instalar MySQL/MariaDB)"
#			echo "	service uninstall  (Desinstalar MySQL/MariaDB)"
			echo "	ddbb check         (Revisar bases de datos)"
			echo "	ddbb repair        (Revisar y autoreparar bases de datos)"
			StatusCode=1
			;;
		* )
			Service_$CurrentAction "$@"
			LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			;;
	esac
	return $StatusCode
}

#[/admidb]

##### TEMPLATE FUNCTIONS TO CUSTOMIZE #####

Install_precp_Pre ()
# Example: Install_precp_Pre /cdrom/program
# Will be run after uninstalling previous version (all before installing new program files).
# Useful for old programs migration/uninstall before doing anything about this one.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Install_precp_Post ()
# Example: Install_precp_Post /cdrom/program
# Will be run after uninstalling previous version and before copying new program files.
# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script or redo them later.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Install_postcp_More ()
# Example: Install_postcp_More /cdrom/service
# Will be run once program files are already installed.
{
	local SoftwareDir="$1"
	if [ $# -gt 0 ] ; then shift ; fi
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Uninstall_predel_More ()
# Will be run after stopping service and before program files removal.
{
	sleep 0
}

Uninstall_Postdeletion ()
# Will be run after program files removal.
{
	sleep 0
}

Uninstall_purge_MorePre ()
# Will be run after Uninstall_Postdeletion and just before removing config files/dirs and logs.
{
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Uninstall_purge_MorePost ()
# Will be run just after removing config files/dirs and logs.
{
	local LastStatus=0
	local StatusCode=0

	return $StatusCode
}

Configuration_Saved_Pre ()
# Preliminar configurations at beginning of Configuration(). Useful to migrate old configurations and data before assuming them for production.
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
}

Configuration_Saved_Post ()
{
	local ReadOnly=0
	if [ "$1" = "ro" ] ; then ReadOnly=1 ; shift ; fi
#[admidb]
	local RespuestaSave="$1"
	local ValorActual=""
	local HuboCambios=""
	local DescripcionActual=""
	local OldConfigFile="$HOME/.admidb.conf"
	local AncientConfigFile="$HOME/.gestionarbd.conf"
	local ServiceConfFiles=''
	local CurFile=''
	local LastStatus=0
	local StatusCode=0
	
	if [ $ReadOnly -eq 0 ] ; then
		if [ -f "$AncientConfigFile" ] ; then
			if [ "$(cat "$AncientConfigFile" 2>/dev/null | cut -f 1 -d '#' | grep -ve '^$' | grep -e '..*=')" != "" ] ; then
				MkfilePP "$OldConfigFile" '' u=rw,g=r,o=
				if [ "$(cat "$OldConfigFile" 2>/dev/null)" != "" ] ; then printf '%s\n' "" >> "$OldConfigFile" ; fi
				printf '%s\n' "# Parameters migrated from $AncientConfigFile" >> "$OldConfigFile"
				cat "$AncientConfigFile" | cut -f 1 -d '#' | grep -ve '^$' | grep -e '..*=' >> "$OldConfigFile"
			fi
			mv "$AncientConfigFile" "${AncientConfigFile}.bak"
		fi
		if [ -f "$OldConfigFile" ] ; then
			if [ "$(cat "$OldConfigFile" 2>/dev/null | cut -f 1 -d '#' | grep -ve '^$' | grep -e '..*=')" != "" ] ; then
				MkfilePP "$CurrentConfigFile" '' u=rw,g=r,o=
				if [ "$(cat "$CurrentConfigFile" 2>/dev/null)" != "" ] ; then printf '%s\n' "" >> "$CurrentConfigFile" ; fi
				printf '%s\n' "# Parameters migrated from $OldConfigFile" >> "$CurrentConfigFile"
				cat "$OldConfigFile" | cut -f 1 -d '#' | grep -ve '^$' | grep -e '..*=' >> "$CurrentConfigFile"
			fi
			mv "$OldConfigFile" "${OldConfigFile}.bak"
		fi
		if [ ! -f "$CurrentConfigFile" ] ; then
			echo "# Asistente de administracion de MySQL/MariaDB" >> "$CurrentConfigFile"
			echo "# Parametros de funcionamiento del programa $NombrePrograma" >> "$CurrentConfigFile"
			echo "" >> "$CurrentConfigFile"
			chmod u=rw,go= "$CurrentConfigFile"
			HuboCambios="1"
		fi
	else
		if [ "$(cat "$CurrentConfigFile" 2>/dev/null | cut -f 1 -d '#' | grep -ve '^$')" = "" ] ; then
			if [ -f "$OldConfigFile" ] ; then
				CurrentConfigFile="$OldConfigFile"
			else
				if [ -f "$AncientConfigFile" ] ; then
					CurrentConfigFile="$AncientConfigFile"
				fi
			fi
		fi
	fi
	
	DescripcionActual="# TMPDIR: Directorio donde volcar datos exportados o importados."
	DescripcionActual="$(echo "$DescripcionActual" ; echo "# Dejar vacio \"\" para que se use el mismo directorio que el paquete.")"
	TempDir="$(GetOrSetIniVarValue "$CurrentConfigFile" TMPDIR "" '"/tmp"' "" "" "# TMPDIR: Directorio donde volcar datos exportados o importados.\n# Dejar vacio \"\" para que se use el mismo directorio que el paquete." "$ReadOnly")"
	
	ServerUser="$(GetOrSetIniVarValue "$CurrentConfigFile" ServerUser "" 'root' "" "" "# Username to manage MySQL/MariaDB. Please, don't include spaces." "$ReadOnly")"	#'
	
	if Is_Executable mariadb ; then
		SqlClient="mariadb"
	else
		SqlClient="mysql"
	fi
	if [ "$PasswordReady" = "1" ] ; then
		AskPasswordNotice=""
		if [ "$ServerPassword" != "" ] ; then
			SqlClientCall="\"$SqlClient\" --default-character-set=utf8 \"--user=${ServerUser}\" \"--password=${ServerPassword}\""
		else
			SqlClientCall="\"$SqlClient\" --default-character-set=utf8"
		fi
	else
		AskPasswordNotice="Se pide la contraseña para el usuario de MySQL/MariaDB $ServerUser"
		SqlClientCall="\"$SqlClient\" --default-character-set=utf8 --user=${ServerUser} --password"
	fi
	
	if [ "$HuboCambios" = "1" ] && [ $StatusCode -eq 0 ] ; then
		echo "Preferencias y datos de usuario MyQL guardados en $CurrentConfigFile"
		echo ""
	fi
	rm -f /tmp/my.cnf.$$
	ServiceConfFiles="$(find /etc/mysql 2>/dev/null | grep -e '\.cnf$' -e '\.conf$')"
	IFS="$(printf "\n\b")" ; for CurFile in $ServiceConfFiles ; do unset IFS
		cat "$CurFile" | tr -s '\t' ' ' | tr -s ' ' | sed -e 's/ =/=/g' | sed -e 's/= /=/g' >> /tmp/my.cnf.$$
	done
	DatabasesDir="$(IniVarValue /tmp/my.cnf.$$ datadir "" "" "" "")"
	rm -f /tmp/my.cnf.$$
#[/admidb]
}

ProgramHelp ()
{
	local SudoPrefix=''
	local SudoSuffix=''
	local InstallerActions=''
	local BaseName=''
	
	BaseName="$(printf '%s\n' "$MeExecutable" | tr -s '/' '\n' | tail -n 1)"
	printf '%s\n' "$(ScriptHeaderValue Description) ${ParO}${ProgramName}${ParC} $(ScriptHeaderValue Version)"
	ScriptHeaderValue Homepage
	printf '%s\n' ""
	if [ $(MeInstalled) -eq 1 ] || [ "$InstalledExpected" = "0" ] ; then
		if [ "$RootRequired" != "0" ] && [ $(id -u) -ne 0 ] ; then
			if Is_Executable sudo ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			if [ $(MeInstalled) -eq 1 ] ; then
				InstallerActions="|uninstall|purge"
			else
				InstallerActions="|install"
			fi
		fi
#[admidb]
#		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {${InstallerActions}}${SudoSuffix}"
		printf '%s\n' "Usage: ${SudoPrefix}${ProgramName} {object${InstallerActions}}${SudoSuffix}"
		printf '%s\n' ""
		printf '%s\n' "Syntax for object: ${SudoPrefix}${ProgramName} Object Action Parameters"
		printf '%s\n' "Get help for each of these objects:"
		printf '%s\n' "	service    ${ParO}MySQL/MariaDB${ParC}"
		printf '%s\n' "	db         ${ParO}Database${ParC}"
		printf '%s\n' "	user       ${ParO}Local name with permissions${ParC}"
		printf '%s\n' "	remote     ${ParO}Remote username with permissions${ParC}"
		printf '%s\n' "	table      ${ParO}Table in a DB${ParC}"
		printf '%s\n' "	column     ${ParO}Table column in a DB${ParC}"
		printf '%s\n' "	data       ${ParO}Cell values${ParC}"
		printf '%s\n' ""
		printf '%s\n' "Environment variables:"
		printf '%s\n' "	ADMIDB_PASS     ${ParO}To use value as password with SQL client${ParC}"
		printf '%s\n' "	ADMIDB_BATCH=1  ${ParO}To not ask informations, such as passwords or parameters${ParC}"
#[/admidb]
	else
		if [ $(id -u) -ne 0 ] ; then
			if Is_Executable sudo ; then
				SudoPrefix="sudo "
			else
				SudoPrefix='su -c "'
				SudoSuffix='"'
			fi
		fi
		if [ "$ProgramInstaller" = "1" ] ; then
			printf '%s\n' "To install as a system program:"
			printf '%s\n' "${SudoPrefix}${BaseName} install${SudoSuffix}"
			printf '%s\n' ""
			printf '%s\n' "To remove the program:"
			printf '%s\n' "${SudoPrefix}${ProgramName} uninstall${SudoSuffix}"
		else
			printf '%s\n' "${sWARN}W: Program not installed. Use your package manager to install ${ProgramName}.${fRESET}"
		fi
	fi
}


##### MAIN SCRIPT #####

LastStatus=0 ; StatusCode=0
if [ $(id -u) -eq 0 ] && [ "$USER" != "root" ] && [ "$USER" != "" ] && [ "$(env | grep -ie LXC -ie '^container=')" != "" ] ; then
	# Patch to not legate some LXC unprivileged variables on lxc-attach/calls
	export USER=root ; export HOME=/root ; export LOGNAME=root
	if [ -f /etc/default/locale ] ; then
		IFS="$(printf "\n\b")" ; for CurVar in $(cat /etc/default/locale | cut -f 1 -d '#' | grep -e '=') ; do unset IFS
			eval export $CurVar
		done
	fi
fi
MeCallFile="$0"
PreviousDir="$(pwd)"
MeDir="$(Dirname "$MeCallFile")"
cd "$MeDir"
MeDir="$(pwd)"
cd "$PreviousDir"
MeCallFile="${MeDir}/$(printf '%s\n' "$MeCallFile" | tr -s '/' '\n' | tail -n 1)"
if [ "$LogLevel" = "" ] ; then LogLevel=3 ; fi	# A nested call can export LogLevel
LogProgram 4 '$' "$0" "$@"
#cd / # avoid blocking mount points from being unmounted - It's a problem to deal with user specified relative paths

Action="$1"
if [ $# -gt 0 ] ; then shift ; fi
Action="$(Lowercase "$Action")"
case "$Action" in
	"remove" ) Action="uninstall" ; ActionMode="remove" ;;
	"purge" ) Action="uninstall" ; ActionMode="purge" ;;
	"-h" ) Action="--help" ;;
	"-V" ) Action="--version" ;;
#[admidb]
	"servicio" ) Action="service" ;;
	"bd" ) Action="db" ;;
	"bbdd" ) Action="ddbb" ;;
	"databases" ) Action="ddbb" ;;
	"usuario" ) Action="user" ;;
	"usuaria" ) Action="user" ;;
	"usuarios" ) Action="user" ;;
	"tabla" ) Action="table" ;;
	"tablas" ) Action="table" ;;
	"tables" ) Action="table" ;;
	"campo" ) Action="column" ;;
	"columna" ) Action="column" ;;
	"field" ) Action="column" ;;
	"datos" ) Action="data" ;;
	"dato" ) Action="data" ;;
#[/admidb]
esac
case "$Action" in
	"install" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				LastStatus=93 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			else
				LackDependencies="$(DependenciasFaltan "$DependsOnSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "${sERROR}E: Following software must be installed before this dependent program:${fRESET}" 1>&2
					printf '%s\n' "   ${LackDependencies}${fRESET}" 1>&2
					LastStatus=53 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				fi
				LackDependencies="$(DependenciasFaltan "$RecommendedSoftware")"	#"
				if [ "$LackDependencies" != "" ] ; then
					printf '%s\n' "${sWARN}W: Following software is also recommended for this program:${fRESET}" 1>&2
					printf '%s\n' "   ${LackDependencies}${fRESET}" 1>&2
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "${sERROR}E: Install actions need to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
			LastStatus=45 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			LogProgram 3 "Installing $ProgramName $(ScriptHeaderValue Version)"
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "precp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				# WARNING: Package manager cannot invoke this action before this script file is installed. Recommended to copy actions to preinst script.
				Install_precp "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "cp" ] || [ "$PackageManager_Call" = "whole" ] ; then
#				printf '%s\n' "Installing $ProgramName"
				Install_cp "$MeExecutable" "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postcp" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Install_postcp "$@"
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
					printf '%s\n' ""
					printf '%s\n' "For more options, run: ${ProgramName} --help"
				fi
			fi
		fi
		;;
	"uninstall" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		if [ "$ActionMode" = "remove" ] ; then
			if [ "$ProgramInstaller" = "1" ] || [ "$PackageManager_Call" != "" ] ; then
				printf '%s\n' "${sWARN}W: To uninstall $ProgramName you must use the action \"uninstall\"${fRESET}" 1>&2
				LastStatus=80 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
			if [ $StatusCode -eq 0 ] ; then
				LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "" ] ; then
			if [ "$ProgramInstaller" != "1" ] ; then
				LastStatus=93 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				ProgramHelp 1>&2
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ $(id -g) -ne 0 ] ; then
			printf '%s\n' "${sERROR}E: Uninstall actions need to be run with superuser ${ParO}root${ParC} permissions.${fRESET}" 1>&2
			LastStatus=45 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			LogProgram 3 "Uninstalling $ProgramName $(ScriptHeaderValue Version)"
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "predel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_predel
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
				if [ $StatusCode -eq 0 ] && [ "$PackageManager_Call" = "predel" ] ; then
					cp "$MeExecutable" "/var/tmp/${ProgramName}.uninstall.tmp"
					cp "$MeExecutable" "${DirTempX}/${ProgramName}.uninstall.tmp"	# Compatibility with old package script: postrm
					chmod u=rx,g=r,o= "/var/tmp/${ProgramName}.uninstall.tmp"
					chmod u=rx,g=r,o= "${DirTempX}/${ProgramName}.uninstall.tmp"
				fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "del" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_del
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "postdel" ] || [ "$PackageManager_Call" = "whole" ] ; then
				Uninstall_postdel
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		if [ $StatusCode -eq 0 ] && [ "$ActionMode" = "purge" ] ; then
			if [ "$PackageManager_Call" = "" ] || [ "$PackageManager_Call" = "purge" ] ; then
				Uninstall_purge
				LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
			fi
		fi
		;;
	"debug" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		export LogLevel=4
		"$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"--version" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		printf '%s\n' "${ProgramName} $(ScriptHeaderValue Version)"
		ScriptHeaderValue Copyright
		ScriptHeaderValue Homepage
		;;
	"--help" )
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[admidb]
	"service" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Service "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"db" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Db "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"ddbb" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Databases "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"user" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		User "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"remote" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Remote "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"table" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Table "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"column" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Column "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"data" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Data "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	"run" )
		Configuration "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Run "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
#[admidb]
	"" )
		printf '%s\n' "${sERROR}E: No action specified.${fRESET}" 1>&2
		LastStatus=79 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp 1>&2
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
	* )
		printf '%s\n' "${sERROR}E: Syntax error; Unknown action ${Action}${fRESET}" 1>&2
		LastStatus=90 ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		Configuration ro "$@"
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		ProgramHelp 1>&2
		LastStatus=$? ; if [ $StatusCode -eq 0 ] ; then StatusCode=$LastStatus ; fi
		;;
esac

cd "$PreviousDir" 2>/dev/null
if [ "$NozeroStatusToFile" != "" ] && [ ! -f "$NozeroStatusToFile" ] && [ $StatusCode -ne 0 ] ; then echo $StatusCode > "$NozeroStatusToFile" ; fi
exit $StatusCode
