zsh function only runs once - zsh

I'm fairly new to zsh (oh-my-zsh) and i'm trying to write a custom theme.
I ran into a problem and reduced it to the following testcase
PROMPT='$RANDOM > '
works as expected, it produces a random number on each command.
But when using a function
PROMPT='$(my_random) > '
function my_random(){
echo $RANDOM
}
it always returns the same number, even after source ~/.zshrc still the same number. only when i close the terminal window and open it again, i get a new number which stays the same for the complete session.
only when i do:
PROMPT='$RANDOM $(my_random) > '
function my_random(){
echo $RANDOM
}
i get two random numbers as expected... any explanation for this behaviour?
btw, i'm using kde's konsole on a fresh arch install.
Edit
fwiw i found using /dev/urandom directly works well. I would still like to know whats going on.
function my_random() {
echo $(cat /dev/urandom | tr -dc '0-9' | head -c5)
}

$()-expansion happens in a subshell, and changes to $RANDOM in a subshell don’t affect the parent. From zshparam(1):
The values of RANDOM form an intentionally-repeatable pseudo-random sequence; subshells that reference RANDOM will result in identical pseudo-random values unless the value of RANDOM is referenced or seeded in the parent shell in between subshell invocations.
You don’t need to turn to setting the prompt to reproduce it:
% echo $(echo $RANDOM)
17454
% echo $(echo $RANDOM)
17454
bash doesn’t share zsh’s behaviour here.
The annoying bit is that prompt expansion also happens in a subshell, so you can’t just fix this by referencing $RANDOM in, say, precmd. The best way I can find is to do it in an empty expansion:
PROMPT='${RANDOM##*}$(my_random) > '

As said in a comment by chepner, you can fix this by putting : $RANDOM; in your precmd. This causes the value of $RANDOM to be taken and a new one to be generated.
e.g.
precmd() {
: $RANDOM;
...
}

Related

When to use spawn.with_shell and when spawn is only needed?

I'm confused when i should use awful.spawn and when to use awful.spawn.with_shell. To me these look and work the same.
The only difference I see is that in awful.spawn you can set client rules and make a callback.
I would appreciate any examples or rules on when to use each one.
awful.spawn.with_shell really does not do more than spawning the given command with a shell: https://github.com/awesomeWM/awesome/blob/c539e0e4350a42f813952fc28dd8490f42d934b3/lib/awful/spawn.lua#L370-L371
function spawn.with_shell(cmd)
if cmd and cmd ~= "" then
cmd = { util.shell, "-c", cmd }
return capi.awesome.spawn(cmd, false)
end
end
So, why would one want that? Some things are done by shells. For example, output redirections (echo hi > some_file), command sequences (echo 1; echo 2) or pipes (echo hello | grep ell) are all done by a shell. None of these work when starting a process correctly.
Why would one not want a shell? For example, argument escaping is way more complicated when a shell is involved. When you e.g. want to start print a pipe symbol (no idea why one would need that), then awful.spawn({"echo", "|"}) just works, while with a shell you need to escape the pipe symbol the appropriate number of times. I guess that awful.spawn.with_shell("echo \\\|") would work, but I am not sure and this is the point.
Also, a shell that does nothing is an extra process and is a tiny bit slower than without a shell, but this difference is really unimportant.

ZSH: prompt expansion return code greater than

The problem is (theoretically) simple. All I want is for my zsh prompt to print the return code if it is less than or equal to 128 and the corresponding signal when greater than 128. I cannot find any example of this being done and the zsh docs only specify how to do it
if the exit status of the last command was n
The only version I have got (somewhat) working is the following (which only works for SIGINT):
PROMPT='%130(?.[$(kill -l $?)].$?)> '
I have also tried using precmd but completely failed with that (it appears the return code is interfered with when zsh is executing the function but don't quote me on that).
The solution was indeed simple and just involved creating a different function (to which I passed the return code) rather than using precmd. Below is the final version of my zsh prompt, including the return code / signal behaviour:
code () {
if (( $1 > 128 )); then
echo "SIG$(kill -l $1)"
else
echo $1
fi
}
setopt promptsubst
PROMPT='%F{green}%n%f#%m %F{cyan}%~%f> '
RPROMPT='%(?..%F{red}[$(code $?)]%f'

Zsh update prompt with time

I'm trying to update the previous command prompt with the time when the command was written.
With the code I wrote, typing the following command
[--:--] costam:~ $ echo "Wrote this at 10:20"
and launching it after five minutes, results in this output:
[10:25] costam:~ $ echo "Wrote this at 10:20"
Wrote this at 10:20
The code in the zsh-theme to achieve this, is the following
PROMPT='[--:--] %{$fg[red]%}$USER:%{$reset_color%}%{$fg[green]%}%c%{$reset_color%}$(git_prompt_info) %(!.#.$) '
preexec () {
DATE=`date +"%H:%M"`
echo -e "\r\033[1A[${DATE}]"
}
The problem is when the command exceed one line or there's an activated virtual env. In such cases, the prompt is not overridden where it should, and results in this:
# Multiline
[--:--] costam:~ (master) $ cat ~/.oh-my-zsh/custom/themes/davever
[10:20]-theme
# Virtualenv
[10:20]env) [--:--] costam:~ (master) $ echo "Broken"
Broken
A solution I was thinking of, was of finding the current prompt in preexec() and then replace [--:--] with the current time, but I don't know how or if is even possible to get the current prompt.
Any other solution is welcome, as long as the time is printed in the same way (or very similar) and not, for example, printing on the right side using RPROMT or printing in the new prompt.
Thank you!
You can just use %T to get the current time in your prompt sequence.
Additionally, rather than put raw escape codes in your prompt, you can simplify your prompt string by using %F{<color>} and %f instead.
So, remove that preexec function and use this as your prompt:
PROMPT='[%T] %F{red}$USER:%F{green}%c%f$(git_prompt_info) %(!.#.$) '
What if you asked ZSH to refresh the terminal prompt with the current time periodically?:
PROMPT='[%D{%L:%M}] %{$fg[red]%}$USER:%{$reset_color%}%{$fg[green]%}%c%{$reset_color%}$(git_prompt_info) %(!.#.$) '
TMOUT=1
TRAPALRM() {
zle reset-prompt
}
Sample output which tracks the current time on the computer, down to the second:
[11:41] costam:~ $
%D{%L:%M} will have the same effect as your use of date +"%H:%M". TMOUT suggestion found here. Since you only want the hour and minute, you might be able to increase the TMOUT value to 10 seconds or more.

sleep inside inotifywait in a shell function not working

I am trying to run the following function
foo () {
sleep 1
echo "outside inotify"
(inotifywait . -e create |
while read path action file; do
echo "test"
sleep 1
done)
echo "end"
}
Until inotifywait it runs correctly; I see:
>> foo
outside inotify
Setting up watches.
Watches established.
However as soon as I create a file, I get
>>> fooo
outside inotify
Setting up watches.
Watches established.
test
foo:6: command not found: sleep
end
Any idea why? Plus do I need to spawn the subprocess ( ) around inotifywait? what are the benefits?
thank you.
Edit
I realized I am running on zsh
The read path is messing you up, because unlike POSIX-compliant shells -- which guarantee that only modification to variables with all-uppercase names can have unwanted side effects on the shell itself -- zsh also has special-cased behavior for several lower-case names, including path.
In particular, zsh presents path as an array corresponding to the values in PATH. Assigning a string to this array will overwrite your PATH as well.

zsh - iterate over files matched using parameter in the script

I have this code executed fine and correctly in the zsh shell:
for f in ./A*.html; do; echo $f; done
output
./Aliasing.html
./Alternate-Forms-For-Complex-Commands.html
./Alternative-Completion.html
./Arguments.html
./Arithmetic-Evaluation.html
./Arithmetic-Expansion.html
./Array-Parameters.html
./Author.html
./Availability.html
However when I use this code, but sending a matching string (./A*.html) as a parameter in the zsh function, it will display only the first file
script:
displayy() {
for f in $1; do; echo $f; done
}
command:
%displayy ./A*.html
output
./Aliasing.html
I would rather expect the same files are printed out as when executing for loop in shell (first example). Any ideas what I'm doing wrong? Grazie
The problem with displayy ./A*.html command is that * is expanded by zsh before it is passed to the dispayy function. So in fact your command looks like this:
$ displayy ./Aliasing.html ./Alternate-Forms-For-Complex-Commands.html ...
Then in displayy you print just first argument: ./Aliasing.html.
The easiest way to solve this issue is to change one char (1=>#) in you displayy definition:
displayy() {
for f in "$#"; do; echo "$f"; done
}
This way iteration of loop goes through all display arguments. Additionally I recommend to put double quotes around variables as a good habit, even if in this example there are no whitespaces in file names.

Resources