7

Today I noticed one of my zsh functions is not functioning; I investigated the issue, and the culprit was this:

for i in a b
do
echo "$i"
done
0
0

Then I opened a new zsh, and in that, things were working normally:

for i in a b
do
echo "$i"
done
a
b

Can anyone explain the reason behind the first one acting weird to me?

  • 3
    Was variable i previously typeset (or declared) as an integer in the first case? – steeldriver Aug 18 '18 at 18:48
  • @steeldriver I changed i to weirdName13 and it indeed fixed the issue. But I hadn't done anything with i. How can one undo this typeset? And how can I protect my scripts when i is typeset as an integer? – HappyFace Aug 18 '18 at 19:27
  • 1
    @HappyFace if you haven't declared your function's local variables as local, expect them to be stomped by random things. – muru Aug 19 '18 at 1:21
10
% typeset -i i
% for i in a b; print $i
0
0

Variables in zsh can be assigned various types, above -i for "represent internally as an integer", which will cause the variable to be represented as that. This can also be done via integer i. There are various ways to inspect what a variable is:

% print ${(t)i}
integer
% typeset -p i
typeset -i i=0

And the usual shell + to disable (see also set -x, set +x) it:

% typeset +i i
% for i in a b; print $i
a
b

It may be beneficial to hunt down what changed the type of that variable and restrict that change to a local scope (within a function) so that the global namespace is not polluted with random types, especially on oft used throwaway variable names such as i. It is rather easy to pollute the global namespace, as all that requires is assignment to an undeclared variable:

% () { local l=42; g=43 }
% print $l

% print $g
43

Or someone could needlessly add the -g flag to a typeset which makes the variable global:

% () { typeset -g -i h=42 }
% for h in a b; print $h
0
0

Exported variables may persist into new processes (a non-subshell child via fork, or a replacement via exec), but not the type:

% export -i h=42
% print $h ${(t)h}
42 integer-export

% ( print ${(t)h} )
integer-export

% exec zsh -l
% print $h ${(t)h}
42 scalar-export
  • Is it possible to protect a script against this pollution? – HappyFace Aug 18 '18 at 19:30
  • 2
    "a script" runs as a different process, as I understand it, so should not be affected. if you are sourcing random code (., source, eval) into an existing process then bad code could easily pollute the environment, in which case you'll either need to not do that, or audit every line of the random code to see what it does – thrig Aug 18 '18 at 19:55
  • 2
    I think you could also unset var to make it lose any special properties it may have. Well, unless it's also readonly, in which case you'd need to typeset +r var first. Bash and ksh could have the same issue with variables set as integers, with the same solution. Except that you can't unset readonly variables in them. – ilkkachu Aug 18 '18 at 20:38
  • 1
    @ilkkachu, also, in bash or mksh, you may need to call unset several times in case it has been declared local in several ancestor contexts (in those (and yash, unset doesn't unset, it peels off one layer of local (except in bash when the variable has been declared in the current context)). – Stéphane Chazelas Aug 19 '18 at 8:30
  • What's a non-subshell child via fork? export is for scalar variables to be transmitted across execs, that's independent of processes. – Stéphane Chazelas Aug 19 '18 at 13:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.