Bash

函式內的隨機未綁定變數錯誤

  • December 23, 2019

我在 bash 中創建了一個函式,當我呼叫它時,它會因錯誤而崩潰unbound variable。我不明白,因為聲明了被稱為未綁定的變數。此外,它似乎是隨機觸發的,有時在第 66 行崩潰,有時在第 76 行崩潰,有時在第 86 行崩潰。

這是功能:

#!/usr/bin/env bash

function setConfigLS() {
   declare DFLT_CFG_FILE="${WEB_DOCUMENT_ROOT}/application/config/config.php"
   declare DFLT_ARRAY='config'
   declare cfgFile="$DFLT_CFG_FILE"
   declare array="$DFLT_ARRAY"
   declare value key arg
   declare -a args=()

   while (( $# > 0 )); do
       arg="$1" && shift
       case "$arg" in
           --file=*)
               cfgFile="${arg#*=}"
           ;;
           -f|--file)
               cfgFile="$1"
               shift
           ;;
           --value=*)
               value="${arg#*=}"
           ;;
           -v|--value)
               value="$1"
               shift
           ;;
           --key=*)
               key="${arg#*=}"
           ;;
           -k|--key)
               key="$1"
               shift
           ;;
           --array=*)
               array="${arg#*=}"
           ;;
           -a|--array)
               array="$1"
               shift
           ;;
           -h|--help)
               echo >&2 'Set a LimeSurvey configuration option.'
               echo >&2 ''
               echo >&2 'Usage:'
               echo >&2 '  setConfigLS [options...] <KEY> <VALUE>'
               echo >&2 '  setConfigLS [options...] --value=<VALUE> --key=<KEY>'
               echo >&2 ''
               echo >&2 'Options:'
               echo >&2 '  --file, -f <CONFIG_FILE>  LimeSurvey configuration file.'
               echo >&2 "                              Default: ${DFLT_CFG_FILE}"
               echo >&2 '  --array, -a <ARRAY>       Name of array containing the configuration.'
               echo >&2 "                              Default: ${DFLT_ARRAY}"
               echo >&2 '  --key, --k <KEY>          Key of the configuration option to set. (required)'
               echo >&2 '  --value, -v <VALUE>       Value of the configuration option. (required)'
               echo >&2 '  --help, -h                Prints this message.'
               echo >&2 ''
               return 0
           ;;
           *)
               args+=( "$arg" )
           ;;
       esac
   done

   if [ -z "$key" ]; then # line 66: key: unbound variable
       if (( ${#args} > 0 )); then
           key="${args[0]}"
           args=( "${args[@]:1}" )
       else
           echo 'Error: `--key` is required' >&2
           return 1
       fi
   fi

   if [ -z "$value" ]; then # line 76: value: unbound variable
       if (( ${#args} > 0 )); then
           value="${args[0]}"
           args=( "${args[@]:1}" )
       else
           echo 'Error: `--value` is required' >&2
           return 1
       fi
   fi

   if (( ${#args} > 0 )); then # line 86: args: unbound variable
       echo 'Error: too many arguments' >&2
       return 1
   fi

   array="${array//\//\\\/}"
   value="${value//$'\n'/\\$'\n'}"

   ssed -Ri "$cfgFile" \
       -e 's~^(\s*)('"${array}"'\s*=>\s*array\s*\()((?:\([^)]*\)|[^)])+)~\1\2\n\1    \3\n\1~'

   ssed -Ri "$cfgFile" \
       -e '/^\s*'"${array}"'\s*=>\s*array\s*\([^)]*$/ {
               :a
               n
               s~^((?:\s*(?:[^,/\s]|/[^/]))+)(\s*//.*)?$~\1,\2~
               s~^(\s*)//\s*('"${key//~/\\~}"'\s*=>)~\1\2~
               /^\s*\)/ {
                   i \        '"${key}"'=>'"${value}"',
                   bq
               }
               /^\s*'"${key//\//\\\/}"'\s*=>/ {
                   s~>.*~>'"${value//~/\\~}"',~
                   bq
               }
               ba
               :q
           }'
}

我嘗試替換declare value key arg為…

declare value=
declare key=
declare arg=

…但它並沒有改變任何東西。

我有點困惑!我錯過了什麼?有什麼我沒看到的嗎?


編輯 1

該函式是從基於 ubuntu 18.04 的 docker 映像的入口點腳本呼叫的。事實上,我使用這個圖像

函式的文件被複製到/opt/docker/functions/set-config-ls.sh.

這是呼叫該函式的腳本:

#!/usr/bin/env bash
set -eu

declare FUNC_DIR='/opt/docker/functions'
declare APP_DIR="${WEB_DOCUMENT_ROOT}"
declare DB_SETUP_PHP="/opt/docker/db_setup.php"

source "${FUNC_DIR}/tty-loggers.sh"
source "${FUNC_DIR}/yes-no.sh"
source "${FUNC_DIR}/file-env.sh"
source "${FUNC_DIR}/set-config-ls.sh"
source "${FUNC_DIR}/env-list-vars.sh"


####################################################################
########################## Setup Variables #########################

fileEnv 'LIMESURVEY_DB_TYPE' 'mysql'
fileEnv 'LIMESURVEY_DB_HOST' 'mysql'
fileEnv 'LIMESURVEY_DB_PORT' '3306'
fileEnv 'LIMESURVEY_TABLE_PREFIX' ''
fileEnv 'LIMESURVEY_ADMIN_NAME' 'Lime Administrator'
fileEnv 'LIMESURVEY_ADMIN_EMAIL' 'lime@lime.lime'
fileEnv 'LIMESURVEY_ADMIN_USER' ''
fileEnv 'LIMESURVEY_ADMIN_PASSWORD' ''
fileEnv 'LIMESURVEY_DEBUG' '0'
fileEnv 'LIMESURVEY_SQL_DEBUG' '0'
fileEnv 'MYSQL_SSL_CA' ''
fileEnv 'LIMESURVEY_USE_INNODB' ''

# if we're linked to MySQL and thus have credentials already, let's use them
fileEnv 'LIMESURVEY_DB_NAME' "${MYSQL_ENV_MYSQL_DATABASE:-limesurvey}"
fileEnv 'LIMESURVEY_DB_USER' "${MYSQL_ENV_MYSQL_USER:-root}"

if [ "${LIMESURVEY_DB_USER}" = 'root' ]; then
   fileEnv 'LIMESURVEY_DB_PASSWORD' "${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}"
else
   fileEnv 'LIMESURVEY_DB_PASSWORD' "${MYSQL_ENV_MYSQL_PASSWORD:-}"
fi

if [ -z "${LIMESURVEY_DB_PASSWORD}" ]; then
   logError 'error: missing required LIMESURVEY_DB_PASSWORD environment variable' >&2
   logError '  Did you forget to -e LIMESURVEY_DB_PASSWORD=... ?' >&2
   logError '' >&2
   logError '  (Also of interest might be LIMESURVEY_DB_USER and LIMESURVEY_DB_NAME.)' >&2
   exit 1
fi

declare -A CONNECTION_STRINGS=(
   [mysql]="mysql:host=${LIMESURVEY_DB_HOST};port=${LIMESURVEY_DB_PORT};dbname=${LIMESURVEY_DB_NAME};"
   [dblib]="dblib:host=${LIMESURVEY_DB_HOST};dbname=${LIMESURVEY_DB_NAME}"
   [pgsql]="pgsql:host=${LIMESURVEY_DB_HOST};port=${LIMESURVEY_DB_PORT};user=${LIMESURVEY_DB_USER};password=${LIMESURVEY_DB_PASSWORD};dbname=${LIMESURVEY_DB_NAME};"
   [sqlsrv]="sqlsrv:Server=${LIMESURVEY_DB_HOST};Database=${LIMESURVEY_DB_NAME}"
)

if [ -z "${CONNECTION_STRINGS[${LIMESURVEY_DB_TYPE}]}" ]; then
   logError "error: invalid database type: ${LIMESURVEY_DB_TYPE}" >&2
   logError "  LIMESURVEY_DB_TYPE must be either \"mysql\", \"dblib\", \"pgsql\" or \"sqlsrv\"." >&2
   exit 1
fi


####################################################################
######################## Download LimeSurvey #######################

if [ ! -f "${APP_DIR}/.RELEASE_${LIMESURVEY_GIT_RELEASE}" ] || isYes "${LIMESURVEY_FORCE_FETCH}"; then
   find "$APP_DIR" -maxdepth 1 -type f -name '.RELEASE_*' -delete

   logInfo "Retrieving LimeSurvey... (this operation may take a while)" >&2
   wget -O "/tmp/lime.tar.gz" \
       --progress="$( [ -t 1 ] && echo 'bar:noscroll' || echo 'dot:mega' )" \
       "https://github.com/LimeSurvey/LimeSurvey/archive/${LIMESURVEY_GIT_RELEASE}.tar.gz"


   logInfo "Extracting files from archive..." >&2
   tar -xzf "/tmp/lime.tar.gz" \
       --strip-components=1 \
       --keep-newer-files \
       --exclude-vcs \
       --to-command='sh -c '\''
           mkdir -p "$(dirname "'"${APP_DIR}"'/$TAR_FILENAME")" &&
               touch "'"${APP_DIR}"'/$TAR_FILENAME" &&
               dd of="'"${APP_DIR}"'/$TAR_FILENAME" >/dev/null 2>&1 &&
               echo "'"${APP_DIR}"'/$TAR_FILENAME" '\' |
       xargs -I '{}' touch -t 195001010000 '{}'

   chown -R "${APPLICATION_USER}:${APPLICATION_GROUP}" "$APP_DIR"
   rm "/tmp/lime.tar.gz"

   touch ".RELEASE_${LIMESURVEY_GIT_RELEASE}"
fi


####################################################################
######################### LimeSurvey Setup #########################

# Install BaltimoreCyberTrustRoot.crt.pem
if [ ! -f "${APP_DIR}/BaltimoreCyberTrustRoot.crt.pem" ]; then
   logInfo "Downloading BaltimoreCyberTrustroot.crt.pem..."
   curl -fsSLo "${APP_DIR}/BaltimoreCyberTrustRoot.crt.pem" \
       "https://www.digicert.com/CACerts/BaltimoreCyberTrustRoot.crt.pem"
fi

if [ ! -f "${APP_DIR}/application/config/config.php" ]; then
   logWarn "No config file for LimeSurvey"
   logWarn "  Copying default config file..."
   # Copy default config file but also allow for the addition of attributes
   echo "            'attributes' => array()," |
       awk '/lime_/ && c == 0 { c = 1; system("cat") } { print }' \
           "${APP_DIR}/application/config/config-sample-${LIMESURVEY_DB_TYPE}.php" \
           > "${APP_DIR}/application/config/config.php"
fi

# Set LimeSurvey configs
setConfigLS -a 'db' -k 'connectionString' "'${CONNECTION_STRINGS[${LIMESURVEY_DB_TYPE}]}'"
setConfigLS -a 'db' -k 'tablePrefix' "'${LIMESURVEY_TABLE_PREFIX}'"
setConfigLS -a 'db' -k 'username' "'${LIMESURVEY_DB_USER}'"
setConfigLS -a 'db' -k 'password' "'${LIMESURVEY_DB_PASSWORD}'"
setConfigLS -a 'urlManager' -k 'urlFormat' "'path'"
setConfigLS -k 'debug' "${LIMESURVEY_DEBUG}"
setConfigLS -k 'debugsql' "${LIMESURVEY_SQL_DEBUG}"

if [ -n "${MYSQL_SSL_CA}" ]; then
   setConfigLS -a 'db' 'attributes' \
       "array(PDO::MYSQL_ATTR_SSL_CA => '${APP_DIR//\//\\\/}\/${MYSQL_SSL_CA}',
           PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false)"
fi

declare cfg key val
for ENV_VAR in $(envListVars "limesurvey\."); do
       val="$(envGetValue "$ENV_VAR")"
       cfg="${ENV_VAR#limesurvey.}"
       cfg="${cfg%%.*}"
       key="${ENV_VAR#limesurvey.*.}"
       setConfigLS -a "$cfg" "$key" "$val"
done

mkdir -p "${APP_DIR}/upload/surveys"
chown -R "${APPLICATION_USER}:${APPLICATION_GROUP}" \
   "${APP_DIR}/tmp" "${APP_DIR}/upload" "${APP_DIR}/application/config"

####################################################################
#################### LimeSurvey Database Setup #####################

if [ -n "${LIMESURVEY_USE_INNODB}" ]; then
   # If you want to use INNODB - remove MyISAM specification from LimeSurvey code
   sed -i "/ENGINE=MyISAM/s/\(ENGINE=MyISAM \)//1" \
       "${APP_DIR}/application/core/db/MysqlSchema.php"
fi

logInfo "Waiting for database..." >&2
while ! curl -sL "${LIMESURVEY_DB_HOST}:${LIMESURVEY_DB_PORT:-3306}"; do sleep 1; done

DBSTATUS=$(TERM=dumb php -f "$DB_SETUP_PHP" -- \
   "${LIMESURVEY_DB_HOST}" "${LIMESURVEY_DB_USER}" "${LIMESURVEY_DB_PASSWORD}" \
   "${LIMESURVEY_DB_NAME}" "${LIMESURVEY_TABLE_PREFIX}" "${MYSQL_SSL_CA}" \
   "${APP_DIR}") &>/dev/null

if [ "${DBSTATUS}" != "DBEXISTS" ] &&  [ -n "${LIMESURVEY_ADMIN_USER}" ] && [ -n "${LIMESURVEY_ADMIN_PASSWORD}" ]; then
   logInfo 'Database not yet populated - installing Limesurvey database' >&2
   su - "${APPLICATION_USER}" \
       -c php -f "${APP_DIR}/application/commands/console.php" -- \
           install "${LIMESURVEY_ADMIN_USER}" "${LIMESURVEY_ADMIN_PASSWORD}" \
           "${LIMESURVEY_ADMIN_NAME}" "${LIMESURVEY_ADMIN_EMAIL}" verbose
fi

if [ -f "${APP_DIR}/application/commands/UpdateDbCommand.php" ]; then
   logInfo 'Updating database...' >&2
   su - "${APPLICATION_USER}" -c php "${APP_DIR}/application/commands/console.php" updatedb
else
   logWarn 'WARNING: Manual database update may be required!' >&2
fi

if [ -n "${LIMESURVEY_ADMIN_USER}" ] && [ -n "${LIMESURVEY_ADMIN_PASSWORD}" ]; then
   logInfo 'Updating password for admin user...' >&2
   su - "${APPLICATION_USER}" \
       -c php -f "${APP_DIR}/application/commands/console.php" -- \
           resetpassword "${LIMESURVEY_ADMIN_USER}" "${LIMESURVEY_ADMIN_PASSWORD}"
fi

這是輸出bash --version

GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

編輯 2

我把我能做的都放在了 github 上。這是送出

我不完全確定,但我認為如果您複製儲存庫並執行啟動腳本,它應該可以工作。

這不會為任何value,key或設置值arg

declare value key arg

因此,如果未達到keyin 的分配:case

while (( $# &gt; 0 )); do
   arg="$1" && shift
   case "$arg" in
       --key=*)
           key="${arg#*=}"
       ;;

thenkey在循環之後仍將被取消設置(“未綁定”),並且由於腳本具有set -u,因此在使用時會引發錯誤。

if [ -z "$key" ]; then # line 66: key: unbound variable

將變數初始化為空字元串(如declare key= value= arg=)將消除該問題。

但是,您也可以參考args

if [ -z "$key" ]; then # line 66: key: unbound variable
   if (( ${#args} &gt; 0 )); then

請注意,這指的是args,不是args[@],您取的是數組第零個元素的長度args,而不是其中的元素數。但如果args為空,則第零個元素不存在,這又是一個錯誤。

引用自:https://unix.stackexchange.com/questions/558621