Lakshay Kalbhor
Lakshay Kalbhor

Reputation: 617

Not able to change global variable in function used for zsh prompt

I'm trying to build a zsh function that returns an output based on a time interval. Initially the "You're thirsty" condition is true, but after changing the variable thirsty through the command line and setting it to false, the initial if statement goes through, but the variable thirsty in it doesn't change the global variable thirsty. Is there a way to modify the global variable thirsty?

thirsty=
last_time=

drink_water() {
echo -n "$thirsty"

  if [[ $thirsty == false ]]; then
    last_time="$[$(date +%s) + 10]"
    thirsty=true
    echo -n "${last_time} $(date +%s) ${thirsty}"

  elif [[ $[last_time] -lt $(date +%s) ]]; then
    echo -n "πŸ’§ You're thirsty"
  fi

}

Upvotes: 3

Views: 1931

Answers (2)

John Starich
John Starich

Reputation: 619

You can use Zsh Hooks.

Hooks avoid the issues of command substitution here because they run in the same shell, rather than a subshell.

drink_water_prompt=

thirsty=
last_time=
drink_water_gen_prompt() {
    drink_water_prompt="$thirsty"
    if [[ $thirsty == false ]]; then
        last_time="$[$(date +%s) + 10]"
        thirsty=true
        drink_water_prompt+="${last_time} $(date +%s) ${thirsty}"
    elif [[ $[last_time] -lt $(date +%s) ]]; then
        drink_water_prompt+="πŸ’§ You're thirsty"
    fi
}

autoload -Uz add-zsh-hook 
add-zsh-hook precmd drink_water_gen_prompt

PROMPT='${drink_water_prompt}'

These also allow more than one precmd() function.

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295815

Since your code is actually called from:

PROMPT='$(drink_water)'

...everything it contains is run in a subprocess spawned as part of this command substitution operation ($() is a "command substitution": It creates a new subprocess, runs the code given in that subprocess, and reads the subprocess's output). When that subprocess exits, changes to variables -- even global variables -- made within the subprocess are lost.

If you put your update code directly inside a precmd function, then it would be run before each prompt is printed but without a command substitution intervening. That is:

precmd() {
  local curr_time=$(date +%s) # this is slow, don't repeat it!
  if [[ $thirsty = false ]]; then
    last_time="$(( curr_time + 10 ))"
    thirsty=true
    PROMPT="$last_time $curr_time $thirsty"
  elif (( last_time < curr_time )); then
    PROMPT="πŸ’§ You're thirsty"
  fi
}

Of course, you can set your PROMPT with a command substitution, but updates to variable state have to be done separately, outside that command substitution, if they are to persist.

Upvotes: 4

Related Questions