Skip to main content

Bash Escaping Issue (Part 3)

Further testing has flushed out another escaping issue.

The Issue

When using declare -p to print the definition of an environment variable, the value is quoted with double quotes. Single quotes in the value are therefore not escaped.

docker@basetest:~$ BASETEST_VAR=$'one\n"two\'\tthree"\nfour'
docker@basetest:~$ echo "${BASETEST_VAR}"
one
"two'   three"
four
docker@basetest:~$ declare -p BASETEST_VAR
declare -- BASETEST_VAR="one
\"two'  three\"
four"

In the previous implementation, I unescaped double quotes but forgot to escape single quotes! When the declare command for an environment variable that contains both a newline and a single quote was executed in the new Bash shell, the command failed with a syntax error.

The Fix

This issue is easy to fix by adding code to escape single quotes. The order of operations is significant:

  1. Unescape any double quotes.
  2. Escape any backslashes.
  3. Escape any single quotes.
  4. Transform a trailing double quote to a single quote.

The new version of the demonstration script, available in full on GitHub at demo.sh, is shown below.

_demo_load_env () {
  local defcmd defn line quoteflag value var
  while IFS=$'\n' read -r line ; do
    if [[ "${line}" =~ ^declare ]] ; then
      if [ -n "${defn}" ] ; then
        echo "${defn}"
        defn=""
      fi
      defcmd="${line#declare -* }"
      var="${defcmd%%=*}"
      case "${var}" in
        BASH_* | FUNCNAME | GROUPS | cmd | val )
          ;;
        DEMO_ENV | defcmd | defn | line | var )
          ;;
        * )
          defn="${line}"
          ;;
      esac
    else
      if [[ "${defn}" =~ ^[^=]*=\$ ]] ; then
        if [ "${quoteflag}" -eq 1 ] ; then
          defn="${defn%"'"}\""
        fi
      else
        value="${defn#*'"'}"
        value="${value//\\'"'/'"'}"
        value="${value//\\/\\\\}"
        value="${value//"'"/\\"'"}"
        defn="${defn%%=*}=\$'${value}"
      fi
      line="${line//\\'"'/'"'}"
      line="${line//\\/\\\\}"
      line="${line//"'"/\\"'"}"
      if [ "${line: -1}" == "\"" ] ; then
        line="${line%?}'"
        quoteflag=1
      else
        quoteflag=0
      fi
      defn="${defn}\\n${line}"
    fi
  done < <(declare -p)
  test -z "${defn}" || echo "${defn}"
  alias -p
}

Update

This demonstration script has issues! The final version can be found in Abort Transformation!