How can I un-export a variable, without losing its value?
EDIT: For bash
only, as pointed out in the comments:
The -n
option to export
removes the export
property from each given name. (See help export
.)
So for bash
, the command you want is: export -n foo
There's no standard way.
You can avoid using a temporary variable by using a function. The following function takes care to keep unset variables unset and empty variables empty. It does not however support features found in some shells such as read-only or typed variables.
unexport () {
while [ "$#" -ne 0 ]; do
eval "set -- \"\${$1}\" \"\${$1+set}\" \"\$@\""
if [ -n "$2" ]; then
unset "$3"
eval "$3=\$1"
fi
shift; shift; shift
done
}
unexport foo bar
In ksh, bash and zsh, you can unexport a variable with typeset +x foo
. This preserves special properties such as types, so it's preferable to use it. I think that all shells that have a typeset
builtin have typeset +x
.
case $(LC_ALL=C type typeset 2>&1) in
typeset\ *\ builtin) unexport () { typeset +x -- "$@"; };;
*) unexport () { … };; # code above
esac
I wrote a similar POSIX function, but this doesn't risk arbitrary code execution:
unexport()
while case ${1##[0-9]*} in ### rule out leading numerics
(*[!_[:alnum:]]*|"") ### filter out bad|empty names
set "" ${1+"bad name: '$1'"} ### prep bad name error
return ${2+${1:?"$2"}} ### fail w/ above err or return
esac
do eval set '"$'"{$1+$1}"'" "$'"$1"'" "$'@\" ### $1 = ( $1+ ? $1 : "" )
eval "${1:+unset $1;$1=\$2;} shift 3" ### $$1 = ( $1:+ ? $2 : -- )
done
It will also handle as many arguments as you care to provide it. If an argument is a valid name that is not otherwise already set it is silently ignored. If an argument is a bad name it writes to stderr and halts as appropriate, though any valid name preceding an invalid on its command-line will still be processed.
I thought of another way. I like it a lot better.
unexport()
while unset OPTARG; OPTIND=1 ### always work w/ $1
case ${1##[0-9]*} in ### same old same old
(*[!_[:alnum:]]*|"") ### goodname && $# > 0 || break
${1+"getopts"} : "$1" ### $# ? getopts : ":"
return ### getopts errored or ":" didnt
esac
do eval getopts :s: '"$1" -"${'"$1+s}-\$$1\""
eval unset "$1; ${OPTARG+$1=\${OPTARG}#-}"
shift
done
Well, both of these use a lot of the same techniques. Basically if a shell var is unset a reference to it will not expand with a +
parameter expansion. But if it is set - regardless of its value - a parameter expansion like: ${parameter+word}
will expand to word
- and not to the variable's value. And so shell variables self-test and self-substitute on success.
They can also self-fail. In the top function if a bad name is found I move $1
into $2
and leave $1
null because the next thing I do is either return
success if all args have been processed and the loop is at an end, or, if the arg was invalid, the shell will expand the $2
into $1:?
which will kill a scripted shell and return an interrupt to an interactive one while writing word
to stderr.
In the second one getopts
does the assignments. And it won't assign a bad name - rather write it will write out a standard error message to stderr. What's more it saves the arg's value in $OPTARG
if the argument was the name of a set variable in the first place. So after doing getopts
all that is needed is to eval
a set OPTARG
's expansion into the appropriate assignment.