TIL: Don't set a variable named path inside functions when using zsh
Tags: todayilearned, zsh, terminal, bash
Published: 2024-03-20 21:22:05

🇧🇷🇵🇹 Versão em português [aqui](https://dev.to/lucassperez/nao-use-uma-variavel-chamada-path-dentro-de-funcoes-quando-estiver-usando-o-zsh-1ob8).

In the past, I used to use aliases for common things, sometimes a sequence of commands or commands with specific flags and options. But when it got too complicated, I used to create shell scripts to handle more complex logic. Then I would create aliases that just call these shell scripts. I prefered this over putting the scripts in my path because then I could name then with more descriptive names and I wouldn't have to deal with the `PATH` variable.

But at some point I realized that I can just create functions and put then in my shell config file (like .bashrc, .zshrc etc) and then I would have these functions available to me. Also, using the script+alias aproach, running `which <command>` wouldn't be all that useful, it would just show me something like `command=/path/to/script`. But when I define functions, running `which <function>` shows me the implementation of it.

_I think the behaviour of `which` is not the same on every distro, but on Fedora it does that._

Of course that if the implementation is several lines long, it might not be helpful anyways, but anyways.

You can pretty much copy paste your shell scripts into functions definitions, just have in mind that instead of using `exit`, you should use `return`.

_Or can you?_

Today I wanted to create a function that would call curl to get an authorization token, and then use this token to make another curl to a supplied route, so I wouldn't have to copy paste around a token the whole day and deal with yet another `--header` flag in curl just to develop my API.

So I did something like this:

```sh
my-api-curl() {
  token=`curl ... | jq -r '.token'`
  path="$1"
  shift
  curl \
    --header "Authorization: Bearer $token" \
    --url "http://localhost:3000/$path" \
    "$@"
}
```

What I wanted was to supply the path as the first argument to the function and append it to localhost:3000 (so I don't have to type so much all the time. We're lazy, right?). And then I would like to pass curl flags to my function so it would repass it to curl internaly. So I grab the first argument with `$1`, I do a shift to remove it from the arg list, and pass the rest as is to curl.

So I can do things like this:

```sh
# Do a GET to http://localhost:3000/posts
my-api-curl /posts
```

But I can also do things like this:

```sh
# Do a POST to http://localhost:3000/posts with that body
my-api-curl /posts -X POST -d '{"title": "Nice Post"}'
```

This makes my function flexible, I just **have to know** that the **FIRST** argument is the url. I could iterate over the arguments, use getopts or something, but I was doing something dirt and quick.

So when I was going to run this function, I got a weird error:

```sh
$ my-api-curl /posts
curl: Command not found
```

I thought, what? I do have curl. Lets edit the file where I define it:

```sh
vim ~/.zshrc
vim: Command not found
```

Pretty much every command I tried from here on would say Command not found, I could not use `ls`, `cd` etc.

What is going on? Why didn't my function work, and why did it bork my terminal session?

The error message is not very clear. I wanted to run it as a script with the -x flag for debugging purposes, but when I copy pasted it into a file and ran it with `sh`, it just worked! No problems with it!

After some scratching my head, I had a hunch that the line `path=...` was troublesome. I renamed the variable to simply `p` and now the function works.

And even weirder, I tried the same using our trusty old friend **bash**, and I had no problem with using a variable named `path`. So I guess there is some weird **zsh** magic going on there.

And by the way, I updated my function to use `local p=...` when defining the variable. When doing so, it does not bork my terminal session if I use `local path=...`, but the function itself does not work. Interesting. And then I thought, maybe **zsh** can use both `path` and `PATH`?

Well, it is true! In **zsh**, if you `echo $path`, you'll get the same thing as `echo $PATH`! So there you go, since I was using a variable named `path`, it was overriding my **PATH** env var! That's why using `local` saved my terminal, because when not using it, this overwritten value would affect everything, not just the function.

In **bash**, `echo $path` just outputs nothing. I also tried in **zsh** to `echo $home`, but this outputs nothing as well. So weirdly, **zsh** is case insensitive only for the path variable. It is not case insensitive for any other variable I tried, be it `x`, `myvariable` or `shell`.

All my scripts shebangs are `/sh` though, so that's why I didn't have a problem when I copy pasted my function to a script, it worked well, because whatever `/sh` is in my computer, it is not case insensitive for my PATH variable.

And while writing this, I decided to try some googling (I didn't while I was solving the problem, because I like figuring things on my own. We're just lazy to type, not to debug, right?), it looks like this is also the behaviour of **csh** and **tcsh**. Also, **zsh** is kind of based on **tcsh** and **ksh** I think (don't quote me on that), so maybe that's where it comes from.

Also, zsh is not actually case insensitive, since pAth, PAtH etc does not work. It simply understands both path and PATH. I learned that with further experiments.

To be honest, I thing this is weird and kind of hurts the principle of least surprises, but I guess **today I learned**.

TIL **zsh** is _somewhat_ case insensitive for the `PATH`.
Also, use `local` in your functions!