-3
prepend() { [ -d "$2" ] && eval $1=\"$2':'\$$1\" && export $1; }

So I came across this function and let's say I run it as

prepend PATH /home/xyz/bin

Now, I am not able to understand how the different parameters expand and specially in the eval part.

So when I plug the values in, I get

prepend() { [ -d "/home/xyz/bin" ] && eval PATH=\"/home/xyz/bin':'\$PATH\" && export $1; }

My only question is, how is

eval PATH=\"/home/xyz/bin':'\$PATH\"

evaluating?

5
  • 3
    Here's something about bash eval and why it's dangerous Commented Jul 2 at 17:13
  • 4
    How about forgetting about eval, and just ask how to add a string with colon to a variable?? Commented Jul 2 at 17:34
  • @KamilCuk They need eval because the variable name is dynamic. Commented Jul 2 at 18:20
  • 4
    Since are using Bash you don't need eval for this. Try export "$1=$2:${!1}". See What does "${!var}" mean in shell script? for an explanation of ${!1}. Also see BashFAQ/006 (How can I use variable variables (indirect variables, pointers, references) or associative arrays?). No matter how you do it you should check that $1 contains a valid variable name before trying to assign to it. Otherwise bad things may happen if prepend is used incorrectly (e.g. because of a bug). Commented Jul 2 at 20:12
  • @pjh That's appropriate if the variable is an environment variable, but not if you're assigning to an ordinary variable. Commented Jul 3 at 16:28

3 Answers 3

2

In Bash, to add a string with : to a variable use printf -v.

In Bash, to expand a variable which name is stored in a variable, use ${! indirect expansion.

In Bash, consider:

prepend() {
  [[ -d "$2" ]] &&
  printf -v "$1" "%s:%s" "$2" "${!1}" &&
  export "$1"
}

In contrast, POSIX sh - like dash or busybox, do not have Bash extensions. For exal, I imagine the code like I would write without eval, and then put the string inside ", and add backslashes where needed.

prepend() {
  [ -d "$2" ] &&
  eval "$1=\"\$2:\$$1\"" &&
  export "$1"
}

The code has a bug, that when ${!1} is empty, then : is added. This is typically fixed by using ${var:+ expansion, see https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html . Bash version:

prepend() {
  [[ -d "$2" ]] &&
  printf -v "$1" "%s" "$2${!1:+:${!1}}" &&
  export "$1"
}

and sh version:

prepend() {
  [ -d "$2" ] &&
  eval "$1=\"\$2\${$1:+:\$$1}\"" &&
  export "$1"
}
Sign up to request clarification or add additional context in comments.

Comments

1

It is advisable to avoid eval in the first place. There is no need for such a big hammer in this case.

prepend() {
  declare -gn variable="$1"
  variable="$(printf '%s:' "${@:2}")${variable}"
}

This works fine for me:

echo "$PATH"
prepend PATH "${HOME}/bin" '/foo/blah'
echo "$PATH"

Comments

0

The eval command line is first subject to escape and quote processing. Escape processing turns \" into ", and quote processing turns ':' into : (there's actually no need to quote this, since : has no special meaning in this context).

After this, the command that will be evaluated is:

PATH="/home/xyz/bin:$PATH"

whose meaning should be obvious.

Note that if the string you're trying to prepend contains any literal doublequote characters, the result will not work as intended. If there are an odd number you'll get a syntax error for mismatched quotes; if there are an even number they'll simply be discarded silently. E.g.

prepend PATH '/home/xyz/b"n'

results in

PATH="/home/xyz/b"n:$PATH"

The solution to this is to use the Q operator in the parameter expansion, rather than wrapping double quotes around the expansion.

prepend() { [ -d "$2" ] && eval $1=${2@Q}\":\$$1\" && export $1; }

The above prepend call results in

PATH='/home/xyz/b"n'":$PATH"

It also works correctly with embedded single quotes

prepend PATH "/home/xyz/b'n"

produces

PATH='/home/xyz/b'\''n'":$PATH"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.