Skip to main content

Bash Escaping Issue (Part 2)

Testing the updated code discussed in Bash Escaping Issue, I indeed found another escaping issue!

The Issue

When using declare -p to print the definition of an environment variable, the value is quoted with double quotes. Any double quotes in the value are therefore 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 did not unescape double quotes! The backslashes were escaped, resulting in added backslashes.

declare -- BASETEST_VAR=$'one\n\\"two\tthree\\"\nfour'

The Fix

This issue is easy to fix by adding code to unescape double quotes. The order of operations is significant. In the first line of a definition, double quotes must be unescaped (line 29) before backslashes are escaped (line 30). In additional lines of a definition, double quotes must be escaped (line 32) before trailing quotes as transformed (line 34) and backslashes are escaped (line 39).

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//\\'"'/'"'}"
        defn="${defn%%=*}=\$'${value//\\/\\\\}"
      fi
      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 yet another escaping issue! The issue and fix are discussed in the following blog entry:

The final version of the script can be found in Abort Transformation!