While environment variables can have any name (including the empty string) not containing an equal sign or a null byte, shells map environment variables to shell variables and in most shells, variable names are limited to ASCII alphanumerical characters and _
where the first character can't be a digit (except for the positional parameters and other special ones like $*
, $-
, $@
, …, (which are not mapped to the corresponding environment variables)). Also note that some variables are reserved/special by/to the shell.
Exceptions to that:
The rc
shell and its derivatives like es
and akanga
support any name except the empty string, and those that are all-numeric or contain =
characters (and always export all their variables to the environment, and beware of special variables like *
, status
, pid
...):
; '%$£"' = test
; echo $'%$£"'
test
; '' = x
zero-length variable name
;
However, it uses its own encoding for variables whose name don't contain alnums or for arrays when passed in the environment of commands being executed:
$ rc -c '+ = zzz; __ = zzz; a = (zzz xxx); env' | sed -n /zzz/l
__2b=zzz$
__5f_=zzz$
a=zzz\001xxx$
$ env +=x rc -c "echo $'+'"
x
$ env __2b=x rc -c "echo $'+'"
x
AT&T ksh
, yash
and zsh
(also bash
but only for single-byte characters) support alnums in the current locale, not only ASCII ones.
$ Stéphane=1
$ echo "$Stéphane"
1
In those shells, you could change the locale to consider most characters as alpha, but still that wouldn't work for ASCII characters like .
. You can fool zsh
or ksh
into thinking £
is a letter, but not that .
or any other ASCII character (where allowing characters in variable names is concerned, not for the [[:alpha:]]
glob for instance).
ksh93
has special variables whose name contains a dot like ${.sh.version}
, but those are not mapped to environment variables and are special. The .
is to make sure it doesn't conflict with other variables. If it had chosen to call it $sh_version
, then it could have potentially broken scripts that used that variable already (see for instance how zsh
has issues with its $path
or $commands
special array/hash variables (a la csh) that break some scripts).
Note that in addition to shells not supporting those variables, some shells like pdksh/mksh do remove them from the environment they receive (bash
removes the one with an empty name, ash
, ksh
and bash
remove those environment strings that don't contain a =
character):
$ env %%%=test 1=%%% a.b=%%% mksh -c env | grep %%%
$ env %%%=test 1=%%% a.b=%%% bash -c env | grep %%%
%%%=test
a.b=%%%
1=%%%
$ perl -le '$ENV{""}="%%%"; exec "bash", "-c", "env"' | grep %%%
$ perl -le '$ENV{""}="%%%"; exec "zsh", "-c", "env"' | grep %%%
=%%%
$ echo 'main(){char*a[]={"sh","-c","env",0};char*e[]={"%%%",0};
execve("/bin/ash",a,e);}'|tcc -run - | grep %%%
$ echo 'main(){char*a[]={"sh","-c","env",0};char*e[]={"%%%",0};
execve("/bin/zsh",a,e);}'|tcc -run - | grep %%%
%%%
To sum up, best is to stick with variable names supported by most shells and even try to use upper case for environment variables (and lower case or mixed case for not-exported shell variables) avoiding those that are special in shells (like IFS
, PS1
, BASH_VERSION
...).
If you do need to set such a variable in a shell that doesn't support them, but doesn't discard them, you can either reexecute yourself, with something like:
#! /bin/ksh -
perl -e 'exit 1 unless defined($ENV{"a.b"})' || exec env a.b=%%% "$0" "$@"
(obviously, if you need to do it in the middle of the script, that won't help, but you could then have a look at that approach to save and restore the shell execution environment over a re-exec).
Or try the debugger approach:
gdb --batch-silent -ex 'call putenv("a.b=%%%")' --pid="$$"
(that one seems to work with zsh
, yash
, csh
and tcsh
on Linux amd64, but not with any of the other shells I tried (mksh
, ksh93
, bash
, dash
)).