Não use uma variável chamada path dentro de funções quando estiver usando o zsh
Tags: todayilearned, zsh, terminal, bash
Published: 2024-03-20 21:56:23

🏴󠁧󠁢󠁥󠁮󠁧󠁿 English version [here](https://dev.to/lucassperez/til-dont-set-a-variable-named-path-inside-functions-when-using-zsh-30k5).

Antigamente eu criava aliases para coisas comuns, como uma sequência de comandos ou comandos com flags e opções específicas. Porém, quando ficava muito complicado, eu criava um shell script para lidar com coisas mais complexas. Aí então eu criava aliases que simplesmente chamavam esses scripts. Eu preferia isso do que por o script no meu path porque daí meus scripts podiam ter nomes descritivos e eu não teria que ficar lidando com alterar a variável `PATH`

Mas daí em algum momento eu percebi que eu podia simplesmente criar funções nos arquivos de configuração do meu shell (como por exemplo o .bashrc, o .zshrc etc) e daí eu teria essas funções disponíveis para mim. Além disso, usando a estratégia script+alias, quando eu executava `which <comando>`, eu não teria uma saída tão útil assim, pois aparecia para mim algo como `comando=/caminho/para/o/script`. Mas quando eu defino funções, executar `which <função>` me mostra a implementação dela.

_Eu acho que o comportamento de `which` não é o mesmo em todas as distros, mas no Fedora ele faz isso._

É claro que se a implementação de tal função for muito grande, talvez mostrá-la não seja tão útil assim, mas enfim.

Você pode basicamente copiar e colar seus shell scripts em definições de funções, só tem que lembrar que muito provavelmente você vai querer trocar `exit` por `return`.

_Ou será que não?_

Hoje eu queria criar uma função que chamaria um curl pra pegar um token de autorização, e depois usaria esse token em outro curl para uma rota fornecida como argumento. Isso para que eu não tenha que sair copiando e colando tokens o dia todo e ficar passando um `--header` pro curl toda hora só pra trabalhar na minha API.

Então eu fiz algo mais ou menos assim:

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

O que eu queria era passar o caminho como primeiro argumento pra função e colocar depois de http://localhost:3000 (para que eu não tenha que escrever isso toda hora. Somos preguiçosos, certo?). Eu também queria poder passar mais opções para o segundo curl que é feito. Então eu pego o primeiro argumento com `$1`, faço um shift pra removê-lo da lista de argumentos, e simplesmente passo o resto pro segundo curl.

Então eu posso fazer coisas assim:

```sh
# Faz um GET para http://localhost:3000/posts
curl-da-minha-api /posts
```

Mas também posso fazer coisas assim:

```sh
# Faz um POST para http://localhost:3000/posts com aquele corpo
curl-da-minha-api /posts -X POST -d '{"title": "Post Legal"}'
```

Isso deixa minha função flexível, eu apenas **tenho que saber** que exatamente o **PRIMEIRO** argumento é o path. Eu poderia iterar sobre os argumentos, usar getopts ou algo assim, mas eu apenas queria fazer algo rápido e que funcionasse.

Porém!!

Quando eu fui executar essa função, eu tive um erro esquisito:


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

Eu pensei, o quê? Como assim? Eu tenho curl instalado. Vamos editar o arquivo onde eu defini a função:

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

Praticamente todo comando que eu tentei a partir daqui dava o mesmo erro, inclusive `ls`, `cd` etc.

O que está acontecendo? Por que minha função não só não funcionou, como também aparentemente estragou minha sessão no terminal?

A mensagem de erro não é muito claro. Eu pensei, vou por essa função num shell script e executá-lo com a opção `-x` pra conseguir debugar melhor. Mas quando eu fiz isso, a função simplesmente funcionou! Sem problemas com nada!

Depois de quebrar minha cabeça, eu tive uma ideia. Talvez a linha `path=...` fosse um problema. Eu renomeei a variável simplesmente para `p` e agora a função funcionava perfeitamente.

E ainda mais esquisito, eu tentei executar a mesma função usando nosso bom e velho amigo **bash**, e eu sabe o quê? Não teve nenhum problema usando a variável chamada `path`. Então parece que tem alguma magia misteriosa acontecendo com o **zsh**.

E por falar nisso, eu alterei minha função para usar `local p=...` quando definia a variável. Usando o `local` impedia a função de estragar tudo quando eu usava o nome path, assim: `local path=...`. Porém, a função continuava não funcionava. Interessante. E daí eu pensei, será que o **zsh** pode usar tanto `path` como `PATH`?

Bom, parece que sim! No **zsh**, se você fizer `echo $path`, você vai receber a mesma coisa que se fizer `echo $PATH`! Então aí está, como eu estava criando uma variável chamada `path`, ela estava sobreescrevendo a minha variável de ambiente **PATH**! Por isso que usar `local` salvou meu terminal, porque quando não usava, esse valor sobreescrito afetava tudo mesmo após a função ter terminado.

No **bash**, executar `echo $path` simplesmente não retorna nada. Eu também tentei no **zsh** fazer `echo $home`, mas isso não mostra nada também. Então estranhamente, o **zsh** não faz diferença entre maiúsculas e minúsculas somente para a variável do path. Mas em todas as outras variáveis que tentei, maiúsculas e minúsculas importavam, seja `x`, `minhavariável` ou `shell`.

Além disso, todos os meus scripts eu faço usando o shebang `/sh`, por isso eu não tive problemas quando copiei e colei minha função em um script. Seja lá o que estiver em `/sh` no meu computador, ele faz diferença entre `path` e `PATH`.

E enquanto eu escrevia isso, eu decidi googlear um pouco (mas não enquanto tentava resolver esse problema, porque a gente é preguiçoso apenas para escrever localhost, e não para depurar programas, certo?). Aparentemente, esse comportamento de usar `path` e `PATH` também está presente no **csh** e no **tcsh**. Além disso, eu _ACHO_ que o **zsh** foi baseado/inspirado no **tcsh** e no **ksh** (eu acho, não me cobrem depois), então talvez venha daí essa coisa.

E também para deixar claro, não é que o **zsh** não diferencia maiúsuclas e minúsculas para o path. O que acontece é que ele entende ambos `path` e `PATH`, porque coisas como `PaTh` e `PAth` não funcionam). Percebi isso posteriormente com mais testes.

Pra ser sincero, eu acho isso meio esquisito e fere um pouco o "princípio da surpresa mínima" (tem esse nome em português? Estou falando [disso](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)). Mas acho que hoje aprendi 
algo novo.

Aprendi que o **zsh** não diferencia `path` e `PATH`.
Além disso, usem `local` em suas funções!