1

I'm working with kubectl and zsh, and I would like to store the namespace flag in an environment variable.

Instead of writing:

kubectl get pods -n mynamespace

I would like to do something like:

n='-n mynamespace'
kubectl get pods $n

but doing this, I obtain:

No resources found in  mynamespace namespace.

Note the two spaces between the words in and mynamespace.

If I run kubectl get pods -n mynamespace, I obtain the correct output:

NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          16m

What happens here? Why is there two spaces in the output between the words in and mynamespace? And how can I store the namespace flag in an environment variable?

1

2 Answers 2

4

zsh is not performing word splitting on the variable and passes its value as a single parameter. I don't have kubectl here but you can reproduce the behavior with anything, for example mkdir:

var='-p a/b/c/d'
comp% mkdir $var
mkdir: invalid option -- ' '
Try 'mkdir --help' for more information.

You could invoke the same error with:

comp% mkdir '-p a/b/'
mkdir: invalid option -- ' '
Try 'mkdir --help' for more information.

You can see what zsh is really doing using strace -f mkdir $var:

execve("/usr/bin/mkdir", ["mkdir", "-p a/b/c/d"], 0x7ffce5e6afb0 /* 61 vars */) = 0

As suggested in this answer on SO you need to enable word splitting, for example:

comp% mkdir -p ${=var}

In your case it would be:

kubectl get pods ${=n}
1
  • 1
    Thank you for your answer. Another solution is to define n as an array: n=(-n mynamespace). Commented Mar 8, 2022 at 22:15
2

Don't confuse shell variables (or variables of any language) with environment variable. Environment variables are an array of strings in the var=value syntax that is passed to a command alongside the array of arguments when it is being executed. Like command line arguments, they are used to pass strings from one command to another unrelated command.

Shells have that particularity that they map some of their scalar variables to some of the environment variables they receive on startup (the ones whose name are valid shell variable names and don't otherwise conflict with special shell variables), while other languages use APIs such as getenv() to retrieve them (or map them to a special associative array like the ENVIRON of awk or the %ENV of perl), but that doesn't mean shells can't have variables of their own nor are limited to scalar variables.

If you want to store more than one value in a variable, you would use an array / list variable. zsh has supported arrays since version 2.0 in 1991 (long before bash), it's actually the shell that introduced the array=(foo bar) syntax to declare them (similar to the set array=(foo bar) of csh from the late 70s), now also supported by ksh93, bash, mksh and yash.

In zsh, you'd do:

options=(-n mynamespace)
kubectl get pods $options

While in yash, you'd do:

options=(-n mynamespace)
kubectl get pods "$options"

And in ksh93/bash (also supported by zsh, where it also has the advantage of not discarding the empty elements, and yash):

options=(-n mynamespace)
kubectl get pods "${options[@]}"

Now, arrays can't be exported as environment variables, which are just plain strings of non-NUL bytes.

If you have to pass those two arguments for kubectl as one environment variable, you need somehow to combine them using some form of encoding to get a string out of an array of strings, and later split it to get those two arguments back for kubectl.

You chose to join them with spaces as a crude encoding. Note that it means each of the element can't contain spaces.

To join words with an arbitrary separator, in zsh, you can use the j parameter expansion flag, and to split the s parameter expansion flag:

words=(-n mynamespace) # two arguments in an array variable
export ENV_VAR=${(j[ ])mynamespace} # joined into an environment variable

Then from another zsh invoked in that environment, where the ENV_VAR environment variable has been mapped to the $ENV_VAR shell variable:

words=(${(s[ ])ENV_VAR}) # split back into an array on space

In bash or ksh93 which don't have parameter expansion flags, you could use "${array[*]}" to join array elements with the first character of $IFS (which happens to be the space character in its default value), and split+glob to split:

words=(-n mynamespace) # two arguments in an array variable
IFS=' '
export ENV_VAR="${words[*]}" # joined into an environment variable

And in another bash/ksh93 in that environment:

IFS=' '
set -o noglob
words=($ENV_VAR) # leaving a variable unquoted is split+glob in bash/ksh93!
                 # here with glob disabled above.

You can also do that in zsh, but only when in sh or ksh emulation mode. By default zsh doesn't do split+glob upon unquoted parameter expansions, you have to request each explicitly: $=var for splitting, $~var for globbing. So if for some reason you wanted to use IFS-splitting instead of the j/s parameter expansion flags, you could do:

IFS=' '
words=($=ENV_VAR) # explicitly request IFS-splitting, here on space only.
                  # no need to disable globbing globally like in ksh93/bash/yash
                  # as globbing is not done implicitly by default upon
                  # parameter expansion in zsh.

Now, I don't know about kubectl, but for applications that parse options the standard way (like with the standard getopt() C function), for options that take an argument, like with grep -e expression, you can pass two -e and expression arguments or one -eexpression argument, so here, you could still store that -n and its mynamespace argument in a scalar variable with:

option=-nmynamespace
kubectl get pods $option

(note that if $option was empty, its expansion, because it's unquoted would not result in any argument to be passed to kubectl. While that (and the fact that empty array elements are discarded) could be considered a misfeature, but here it would happen to be what you want).

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.