76

I am not able to use - in variables in shell. Is there a way to be able to use it, because I have one script which depends on such named variables:

$export a-b=c
-bash: export: `a-b=c': not a valid identifier

$export a_b=c

First throws the given error and second works fine.

muru
  • 72,889
xyz
  • 2,991
  • 2
    possible cross site duplicate of: http://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names – Ciro Santilli OurBigBook.com Aug 21 '14 at 20:07
  • Shells usually don't allow such variable names. You'd have to bypass the shell, perhaps even with a custom C program loading the variables into your comand's environment. Can't you fix this misfeature (even possible secuirity risk)? – vonbrand Jul 19 '19 at 14:56
  • Note ${this-that} POSIXly means "the value of variable this if it is set, and if it is not set the actual string that". This long-standard and very widely-used form couldn't work if a varname can include hyphen. – dave_thompson_085 Mar 16 '24 at 23:16

10 Answers10

93

I've never met a Bourne-style shell that allowed - in a variable name. Only ASCII letters (of either case), _ and digits are supported, and the first character must not be a digit.

If you have a program that requires an environment variable that doesn't match the shell restrictions, launch it with the env program.

env 'strange-name=some value' myprogram

Note that some shells (e.g. modern dash, mksh, zsh) remove variables whose name they don't like from the environment. (Shellshock has caused people to be more cautious about environment variable names, so restrictions are likely to become tighter over time, not more permissive.) So if you need to pass a variable whose name contains special character to a program, pass it directly, without a shell in between (env 'strange-name=some value' sh -c'…; myprogram' may or may not work).

  • No longer works. – Jesse Glick Feb 09 '17 at 21:00
  • 8
    @JesseGlick Yes it does. (Your comment would be useful if you defined “not working”: with what data? what is the effect instead of the desired effect? and if you said which implementation of env you used on what operating system.) – Gilles 'SO- stop being evil' Feb 09 '17 at 21:17
  • Sorry. Ubuntu Yakkety with all updates, env from coreutils, sh from dash: env 'with-dashes=value' bash -c 'env | fgrep dashes' works but env 'with-dashes=value' sh -c 'env | fgrep dashes' prints nothing. That is, env itself is fine, but Dash seems to specifically block these variables. Thus if the program in question is launched via a shell wrapper with a typical #!/bin/sh header, there is no apparent way to pass in such variables. example workaround – Jesse Glick Apr 14 '17 at 12:23
  • 1
    @JesseGlick That's dash removing the variable. You have to use env closer to the calling site of the program that needs this variable name, without a shell in between. Dash isn't alone in removing variables whose name it doesn't like. – Gilles 'SO- stop being evil' Apr 14 '17 at 13:05
  • 1
    @JesseGlick It's true that some things that used to work might no longer work, though: since shellshock, people have become more conservative about variable names, and dash did in fact change since I wrote this answer (not as a consequence of Shellshock though, but to avoid a bug along the same lines). I've added a note to my answer. – Gilles 'SO- stop being evil' Apr 14 '17 at 13:10
  • env ++=foo 1=bar zsh -c 'printenv ++ 1' prints foo and bar for me on Debian with zsh 5.9. What system are you on? – Stéphane Chazelas May 05 '23 at 06:42
  • @StéphaneChazelas I was probably on whatever release of Debian was stable in 2011. But testing right now, the oldest zsh I've kept around (5.0.7) does pass on ++ and 1, as well as 5.8 on Debian. But not 5.8 on macOS (whereas macOS's antique bash lets them through). – Gilles 'SO- stop being evil' May 05 '23 at 07:18
  • IIRC zsh does use some *env() standard APIs or may be environ underneath, so they may be the ones stripping them. – Stéphane Chazelas May 05 '23 at 07:19
23

You can access a hyphenated variable using an indirect reference.

$ env 'my-hyphenated-variable=hello' /bin/bash
$ name='my-hyphenated-variable'
$ echo ${!name}
hello
  • 1
    I had never heard of this functionality until reading this comment.Provided a much easier solution for me than the accepted answer. – DrStrangepork Aug 26 '15 at 15:03
  • 14
    This behavior has been disabled in newer versions of bash. See http://stackoverflow.com/questions/36989263/why-cant-environment-variables-with-dashes-be-accessed-in-bash-4-1-2 for a thorough analysis of the situation. – Nicolas Dudebout Sep 15 '16 at 02:16
18

That's not possible in Bash.

From the Definitions section in the manual page of bash:

name A word consisting only of alphanumeric characters and underscores, and beginning with an alphabetic character or an underscore. Also referred to as an identifier.

From the Parameters section in the manual page of bash:

A parameter is an entity that stores values. It can be a name, a number, or one of the special characters listed below under Special Parameters. A variable is a parameter denoted by a name.

Lekensteyn
  • 20,830
  • 3
    +1 for demonstrating the usefulness of man pages once more – ktf Oct 31 '11 at 11:48
  • 2
    Old post I know but, I want to point out that to newcomers man pages can be quite cryptic. I still have times when I need hunt down better explanations/examples. Anyone would be lying if they said it never happened to them. – TCZ8 Dec 17 '14 at 15:08
  • 2
    @TCZ8 The only people that have told me they thought man pages were 'cryptic' are the same sort of people that don't read error messages and just skim read everything, skipping right over what they need to read. – mrr Jan 24 '17 at 22:49
9

If your script depends on having variable names have hyphens, that's a programming error. If it is convenient for you because of the tools that you regularly use to have the variable names contain a hyphen, you may have to learn more and different tools.

Have you tried using tr to convert the hyphens into underscores?

hyphenated_name="a-b"
unhyphenated_name=$(echo $hyphenated_name | tr '-' '_')
declare -x $unhyphenated_name="some value"

Bash does allow '-' to appear in function names. I do this all the time. For example:

function foo-bar() {
   echo "$@"
}
4

The dash (-) character is a break character and not allowed as part of variable names. There are ways to hack this with quoted variables, but the parsing of it is really problematic. There are also other characters with special meanings in the context of variable names in bash, notably braces, parenthesis, operator characters and quotes. (e.g. {}()=+-&'" and more)

I would suggest that practically you need to find another paradigm on which to build your script. You might have a hang over idea from other languages about "variable variable names". This is generally not a good idea in shell scripts.

If you edit this or ask a new question with details of your context and what you are trying to accomplish we might be able to suggest a good way to script it.

Caleb
  • 70,105
4

The Bash manual defines a "name" as:

A 'word' consisting solely of letters, numbers, and underscores, and beginning with a letter or underscore. 'Name's are used as shell variable and function names. Also referred to as an 'identifier'.

So you can't use a hyphen in a name.

1

You can play with env and sed.

As an example, I needed to read this variable 'ELASTICSEARCH_CLUSTER-NODES'.
The env command output this :

~ $ env
ELASTICSEARCH_CLUSTER-NODES=elasticsearch:9200
JAVA_ALPINE_VERSION=8.212.04-r0
HOSTNAME=17eb9e7fec4c
...

So to extract the variable :

ESHOST=`env | sed -n 's/ELASTICSEARCH_CLUSTER-NODES=\(.*\)/\1/p'`
0

You can use the env command to set and unset environment variables with hiphens "-".

To set you must use env to run your command: env command. You pass variables this way:

env a-b=c command

See it working with:

env a-b=c env

or to make it clearer:

env a-b=c env|grep 'a-b'
neves
  • 279
0

It depends on the Bash version, but as of 2024-03, it is about POSIX overall 1:

...uppercase letters, digits, and the <underscore> ( '_' )...

Example

$ printf '%s\n' "$BASH_VERSION";
5.1.16(1)-release

$ ABC-DEF=1;
ABC-DEF=1: command not found
$ n='ABC_DEF';
$ "$n=1";
ABC_DEF=1: command not found
$ declare ABC-DEF=1;
-bash: declare: `ABC-DEF=1': not a valid identifier
$ declare 'ABC-DEF=1';
-bash: declare: `ABC-DEF=1': not a valid identifier
$ export 'ABC-DEF=1';
-bash: export: `ABC-DEF=1': not a valid identifier
$ n='ABC-DEF';
$ "$n"=1;
ABC-DEF=1: command not found
$ declare -- "$n=1";
-bash: declare: `ABC-DEF=1': not a valid identifier

$ n='ABC_DEF';
$ declare -- "$n"=1;
$ declare -p -- "$n";
declare -- ABC_DEF="1"
$ n='ABC_DEF';
$ declare -- n1='ABC_DEF_1';
$ declare -- "$n"=1;
$ declare -- "$n1"=2;
$ declare -p "$n" "$n1";
declare -- ABC_DEF="1"
declare -- ABC_DEF_1="2"
$ set | grep ABC;
ABC_DEF=1
ABC_DEF_1=2
_=ABC_DEF_1
n=ABC_DEF
n1=ABC_DEF_1

References

1 POSIX.1-2017 (i.e. IEEE Std 1003.1-2017)

...
These strings have the form name=value; names shall not contain
the character '='. For values to be portable across systems conforming
to POSIX.1-2017, the value shall be composed of characters from the
portable character set (except NUL and as indicated below). There is
no meaning associated with the order of strings in the environment. If
more than one string in an environment of a process has the same name,
the consequences are undefined.

Environment variable names used by the utilities in the Shell and Utilities volume of POSIX.1-2017 consist solely of uppercase letters, digits, and the <underscore> ( '_' ) from the characters defined in Portable Character Set and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. Uppercase and lowercase letters shall retain their unique identities and shall not be folded together. The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities. Note: Other applications may have difficulty dealing with environment variable names that start with a digit. For this reason, use of such names is not recommended anywhere. ...

Source

Related

Artfaith
  • 474
  • 1
    "$n=1"; isn't really relevant: it's not an assignment and will fail even if n contains a valid name. (The same with declare or export is different, but that's just the shell language(s) being odd.) – ilkkachu Mar 16 '24 at 20:55
  • 2
    note that the first part you quoted, "...uppercase letters, digits, and the ( '' )..."_ is from a context that starts "Environment variable names used by the utilities in [the specification] consist solely of ...", which seems to say it's just what the standard utilities make use of, not all that is allowed. It goes on to say that names with lowercase letters are reserved for application use, so they're at least implicitly allowed. – ilkkachu Mar 16 '24 at 21:07
  • 1
    In any case, environment variables are slightly different from shell variables, and for those, the shell language specification is clear: "A variable is a parameter denoted by a name." where a name is "In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.", that is, lowercase letters are included. – ilkkachu Mar 16 '24 at 21:10
-1

I believe that only letters, numbers, and underscore are allowed for bash variables. This is the case in many programming languages (javascript being an exception).

I recommend not having your script depend on those types of variable names.

In fact, you should try to program in such a way that you can replace variable names with other names and it doesn't make a difference. In general, variable names should describe what the variable contains. This makes it much easier to debug; if not for you, then for the next developer that is trying to figure out the code.

evan
  • 128