35

Say I have a bash script file config.sh. It's meant to be source'd by other scripts and variables defined is used as customization of the upper-level scripts.

The problem is, if config.sh has a temporary variable and its name conflicts with upper-level scripts' variable, it breaks the upper-level one.

config.sh:

TMP1=abc
CONFIG_INPUT_DIR="$TMP1"/in
CONFIG_OUTPUT_DIR="$TMP1"/out

upper-level script:

TMP1=def
source config.sh
echo $TMP1

The last echo prints abc, not def.

Solution 1

My current solution is to append a random string to the temporary variable name to make it almost impossible to conflict. e.g:

TMP1_vFc9Uiew=abc
CONFIG_INPUT_DIR="$TMP1_vFc9Uiew"/in
CONFIG_OUTPUT_DIR="$TMP1_vFc9Uiew"/out
unset TMP1_vFc9Uiew

which is painful and makes the code hard to read, in addition not to be perfect.

Solution 2 using local keyword

After some searching, I've come to know local keyword. But when I simply declare TMP1 as local, bash complains that config.sh: line 1: local: can only be used in a function.

So my another solution is to enclose whole config script as a function:

function config_func_rZ0Yqkpm() {
  local TMP1=abc
  CONFIG_INPUT_DIR="$TMP1"/in
  CONFIG_OUTPUT_DIR="$TMP1"/out
}
config_func_rZ0Yqkpm
unset config_func_rZ0Yqkpm

which is better than previous solution in maintainability and readability, but there's some possibility to conflict as well as solution 1.

Question

I want to know more robust and smart solution without any possibility to conflict.

Thanks.

3 Answers 3

10

A trick I learned from the keychain utility is using one program to build a source-able file containing just the variables that you want to export from your program. You could modify your script to echo the variables you want to set and then source the output from your program:

$ echo $FOO

$ source <(echo FOO=bar)
$ echo $FOO
bar
$ 

I used echo FOO=bar to simulate the larger script; your program is probably more involved. The important part is that you must modify your program to output the variables and values you would like to set, rather than just setting them. This lets you decide which variables to expose and which ones to hold private at the cost of another shell process.

Sign up to request clarification or add additional context in comments.

3 Comments

ssh-agent uses a similar technique, with the -c and -s options.
@jjlin: Thank you! I thought keychain wasn't exactly the place I saw it first, but it was the first place I found the documentation. :) Funny, it's sitting right there in the open in the ssh-agent manpage...
AFAIK Using this technique requires the caller always to be aware of using the source command. Isn't there a technique which makes it possible to change the caller's environment variables without these inconveniences?
10

You could avoid variables and use functions in config.sh to hold your values:

get_dirname() { echo "abc"; }
CONFIG_INPUT_DIR="$(get_dirname)/in"
CONFIG_OUTPUT_DIR="$(get_dirname)/out"
unset -f get_dirname

If you're still concerned about name collision for functions, this doesn't really help you.

Comments

8

The "ssh-agent" method:

config.sh

#!/bin/bash
TMP=abc
printf "CONFIG_INPUT_DIR=%s/in\n" "$TMP"
printf "CONFIG_OUTPUT_DIR=%s/out\n" "$TMP"

main program:

TMP1=def
eval "$(config.sh)"
echo "$TMP1"

2 Comments

You would put ";" at the end of each printf command so that eval commands are separated by ";".
What I should have done is quote the command substitution so that the newlines are preserved. Fixing that.

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.