/r/zsh
Zsh is a shell designed for interactive use and it is also a powerful scripting language.
Zsh is a shell designed for interactive use, although it is also a powerful scripting language.
Please check on the zshwiki.org FAQ and the official FAQ before asking questions.
If your script behaves strange, please check it with shellcheck - it is made for bash scripts but also works with zsh for the most part.
Learning resources can be found in our wiki.
Read zsh FAQ Chapter 02 for more info.
/r/zsh
Guys, could you explain, is it correct to put all of the following lines in .zshrc
? Or some of them should be put in other zsh configuration files, such as .zshenv
or .zprofile
?
path+=$HOME/foo/bar
setopt extended_glob
autoload -Uz zmv
alias zcp='zmv -C'
alias zln='zmv -L'
I really like both.
but is there a way to make the fzf-tab's menu popup automatically as you type without pressing tab like how it is in zsh-autocomplete ?.
also is there a way to make the recent branches in git come up on top like in zsh-autocomplete , because in fzf-tab its so cluttered.
It's Friday, and what's a nerd to do. Yes, I'm reading the zsh guide as linked here. I'm currently on Chapter 2, "What to put in your startup files".
RTFM with me, folks! If you have any moments of awe, inspiration, or interesting finds, let us know. Happy reading.
I'll start ...
The difference between if [
and if [[
...
The reason is that ‘[[’ is treated specially, which allows the shell to do some extra checks and allows more natural syntax. For example, you may know that in sh it’s dangerous to test a parameter which may be empty: ‘[ $var = foo ]’ will fail if $var is empty, because in that case the word is missed out and the shell never knows it was supposed to be there; with ‘[[ ... ]]’, this is quite safe because the shell is aware there’s a word before the ‘=’, even if it’s empty. Also, you can use ‘&&’ and ‘||’ to mean logical ‘and’ and ‘or’, which agrees with the usual UNIX/C convention; in sh, they would have been taken as starting a new command, not as part of the test, and you have to use the less clear ‘-a’ and ‘-o’. Actually, zsh provides the old form of test for backward compatibility, but things will work a lot more smoothly if you don’t use it.
I'm frequently on r/espanso and in other forums, assisting users with Espanso. In order to help those on macOS (I'm using Linux) I have installed ZSH, which I'm now exploring. I have to use an alias, pbpaste='xclip -selection clipboard -o'
to replicate macOS' pbpaste
, but my Espanso scripts only pick it up if I include the line source ~/.zshrc
or source /etc/zsh/zshrc
(the two locations where I have added the alias).
Rather than have that line, which the macOS users have to remove if I forget to, is there somewhere else where aliases can be specified, so that Espanso picks it up automatically?
I just installed MacTex and when I do which latex I get:
/Library/TeX/texbin/latex
And echo $PATH gives me:
/Users/amehac/.vscode/extensions/ms-python.python-2024.20.0-darwin-arm64/python_files/deactivate/zsh:/Users/amehac/Downloads/keyboard/bin:/Users/amehac/.vscode/extensions/ms-python.python-2024.20.0-darwin-arm64/python_files/deactivate/zsh:/Users/amehac/Downloads/keyboard/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Library/TeX/texbin:/usr/local/go/bin:/Users/amehac/development/flutter/bin:/Users/amehac/.vscode/extensions/ms-python.python-2024.20.0-darwin-arm64/python_files/deactivate/zsh:/Users/amehac/Downloads/keyboard/bin:/usr/local/platform-tools:/usr/local/platform-tools
Which you can see has /Library/TeX/texbin within it.
But then I installed Latex Workshop in VSCode and it doesn't work for me. The path it's using seems to be different as it prints the following:
[04:37:04.318][Commander] BUILD command invoked.
[04:37:04.319][Build] The document of the active editor: file://%WS1%/dw.tex
[04:37:04.319][Build] The languageId of the document: latex
[04:37:04.319][Root] Current workspace folders: ["file://%WS1%"]
[04:37:04.320][Root] Try finding root from magic comment.
[04:37:04.321][Root] Try finding root from active editor.
[04:37:04.321][Root] Found root file from active editor: %WS1%/dw.tex
[04:37:04.322][Root] Keep using the same root file: %WS1%/dw.tex
[04:37:04.322][Event] ROOT_FILE_SEARCHED
[04:37:04.322][Event] STRUCTURE_UPDATED
[04:37:04.322][Build] Building root file: %WS1%/dw.tex
[04:37:04.323][Build][Recipe] Build root file %WS1%/dw.tex
[04:37:04.327][Build][Recipe] Preparing to run recipe: latexmk.
[04:37:04.328][Build][Recipe] Prepared 1 tools.
[04:37:04.329][Build][Recipe] outDir: %WS1% .
[04:37:04.330][Build] Recipe step 1 The command is latexmk:["-synctex=1","-interaction=nonstopmode","-file-line-error","-pdf","-outdir=%WS1%","%WS1%/dw"].
[04:37:04.330][Build] env: {"TEXMFHOME":"/Library/TeX/texbin"}
[04:37:04.330][Build] root: %WS1%/dw.tex
[04:37:04.331][Build] cwd: %WS1%
[04:37:04.339][Build] LaTeX build process spawned with PID undefined.
[04:37:04.339][Build] LaTeX fatal error on PID undefined. Error: spawn latexmk ENOENT
[04:37:04.340]Error: spawn latexmk ENOENT
at Process.ChildProcess._handle.onexit (node:internal/child_process:286:19)
at onErrorNT (node:internal/child_process:484:16)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
[04:37:04.340][Build] Does the executable exist? $PATH: /opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/usr/local/go/bin:/Users/amehac/development/flutter/bin:/usr/local/platform-tools, $Path: undefined, $SHELL: /bin/zsh
[04:37:04.340][Build]
I've tried going into Latex Workshop settings and adding:
"TEXMFHOME": "/Library/TeX/texbin"
to the "env" variable but it didn't help. I also tried changing the "command" variable to use the full path. Any ideas what is wrong?
After making the config file and opening a new terminal it's showing this, its not even showing the default prompt
export POSH_THEME=$'/home/arkadeep/.config/oh-my-posh/base.toml'
export POSH_SHELL='zsh'
export POSH_SHELL_VERSION=$ZSH_VERSION
export POSH_SESSION_ID=$'d3bc70c0-f885-4045-b2a5-05a0e8df0997'
export POWERLINE_COMMAND='oh-my-posh'
export CONDA_PROMPT_MODIFIER=false
export ZLE_RPROMPT_INDENT=0
export OSTYPE=$OSTYPE
_omp_executable=$'/usr/bin/oh-my-posh'
_omp_tooltip_command=''
# switches to enable/disable features
_omp_cursor_positioning=0
_omp_ftcs_marks=0
# set secondary prompt
_omp_secondary_prompt=$($_omp_executable print secondary --shell=zsh)
function _omp_set_cursor_position() {
# not supported in Midnight Commander
# see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3415
if [[ $_omp_cursor_positioning == 0 ]] || [[ -v MC_SID ]]; then
return
fi
local oldstty=$(stty -g)
stty raw -echo min 0
local pos
echo -en '\033[6n' >/dev/tty
read -r -d R pos
pos=${pos:2} # strip off the esc-[
local parts=(${(s:;:)pos})
stty $oldstty
export POSH_CURSOR_LINE=${parts[1]}
export POSH_CURSOR_COLUMN=${parts[2]}
}
# template function for context loading
function set_poshcontext() {
return
}
function _omp_preexec() {
if [[ $_omp_ftcs_marks == 1 ]]; then
printf '\033]133;C\007'
fi
_omp_start_time=$($_omp_executable get millis)
}
function _omp_precmd() {
_omp_status=$?
_omp_pipestatus=(${pipestatus[@]})
_omp_stack_count=${#dirstack[@]}
_omp_execution_time=-1
_omp_no_status=true
_omp_tooltip_command=''
if [ $_omp_start_time ]; then
local omp_now=$($_omp_executable get millis)
_omp_execution_time=$(($omp_now - $_omp_start_time))
_omp_no_status=false
fi
if [[ ${_omp_pipestatus[-1]} != "$_omp_status" ]]; then
_omp_pipestatus=("$_omp_status")
fi
set_poshcontext
_omp_set_cursor_position
# We do this to avoid unexpected expansions in a prompt string.
unsetopt PROMPT_SUBST
unsetopt PROMPT_BANG
# Ensure that escape sequences work in a prompt string.
setopt PROMPT_PERCENT
PS2=$_omp_secondary_prompt
eval "$(_omp_get_prompt primary --eval)"
unset _omp_start_time
}
# add hook functions
autoload -Uz add-zsh-hook
add-zsh-hook precmd _omp_precmd
add-zsh-hook preexec _omp_preexec
# Prevent incorrect behaviors when the initialization is executed twice in current session.
function _omp_cleanup() {
local omp_widgets=(
self-insert
zle-line-init
)
local widget
for widget in "${omp_widgets[@]}"; do
if [[ ${widgets[._omp_original::$widget]} ]]; then
# Restore the original widget.
zle -A ._omp_original::$widget $widget
elif [[ ${widgets[$widget]} = user:_omp_* ]]; then
# Delete the OMP-defined widget.
zle -D $widget
fi
done
}
_omp_cleanup
unset -f _omp_cleanup
function _omp_get_prompt() {
local type=$1
local args=("${@[2,-1]}")
$_omp_executable print $type \
--save-cache \
--shell=zsh \
--shell-version=$ZSH_VERSION \
--status=$_omp_status \
--pipestatus="${_omp_pipestatus[*]}" \
--no-status=$_omp_no_status \
--execution-time=$_omp_execution_time \
--stack-count=$_omp_stack_count \
${args[@]}
}
function _omp_render_tooltip() {
if [[ $KEYS != ' ' ]]; then
return
fi
# Get the first word of command line as tip.
local tooltip_command=${${(MS)BUFFER##[[:graph:]]*}%%[[:space:]]*}
# Ignore an empty/repeated tooltip command.
if [[ -z $tooltip_command ]] || [[ $tooltip_command = "$_omp_tooltip_command" ]]; then
return
fi
_omp_tooltip_command="$tooltip_command"
local tooltip=$(_omp_get_prompt tooltip --command="$tooltip_command")
if [[ -z $tooltip ]]; then
return
fi
RPROMPT=$tooltip
zle .reset-prompt
}
function _omp_zle-line-init() {
[[ $CONTEXT == start ]] || return 0
# Start regular line editor.
(( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[1]
zle .recursive-edit
local -i ret=$?
(( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[2]
eval "$(_omp_get_prompt transient --eval)"
zle .reset-prompt
if ((ret)); then
# TODO (fix): this is not equal to sending a SIGINT, since the status code ($?) is set to 1 instead of 130.
zle .send-break
fi
# Exit the shell if we receive EOT.
if [[ $KEYS == $'\4' ]]; then
exit
fi
zle .accept-line
return $ret
}
# Helper function for calling a widget before the specified OMP function.
function _omp_call_widget() {
# The name of the OMP function.
local omp_func=$1
# The remainder are the widget to call and potential arguments.
shift
zle "$@" && shift 2 && $omp_func "$@"
}
# Create a widget with the specified OMP function.
# An existing widget will be preserved and decorated with the function.
function _omp_create_widget() {
# The name of the widget to create/decorate.
local widget=$1
# The name of the OMP function.
local omp_func=$2
case ${widgets[$widget]:-''} in
# Already decorated: do nothing.
user:_omp_decorated_*) ;;
# Non-existent: just create it.
'')
zle -N $widget $omp_func
;;
# User-defined or builtin: backup and decorate it.
*)
# Back up the original widget. The leading dot in widget name is to work around bugs when used with zsh-syntax-highlighting in Zsh v5.8 or lower.
zle -A $widget ._omp_original::$widget
eval "_omp_decorated_${(q)widget}() { _omp_call_widget ${(q)omp_func} ._omp_original::${(q)widget} -- \"\$@\" }"
zle -N $widget _omp_decorated_$widget
;;
esac
}
function enable_poshtooltips() {
local widget=${$(bindkey ' '):2}
if [[ -z $widget ]]; then
widget=self-insert
fi
_omp_create_widget $widget _omp_render_tooltip
}
# legacy functions
function enable_poshtransientprompt() {}
_omp_create_widget zle-line-init _omp_zle-line-init
I want tab to simply cycle completions and don't want anything printed into my shell on other lines than where I type.
This is what it does know. But I do not want the list. I want tab to simply cycle the options shown, but not show them. (Tab -> Arch-Hyprland, Tab -> Desktop ... Which it is doing, but it is showing the list)
I have tried a various combination of setopt, unsetopt but have been unable to achieve this goal.
(Sorry but I have to rant a bit)
What is it with you all and having SO MUCH information in your terminals? The fucking two line prompts where the first line completely fills the screen and then another new line to separate them. And then when they write commands and press tab they get lists of commands and other things. Why?
I want things to be as clean as possible until I decide I NEED the information. Am I mental for this opinion or why does it seem so unpopular?
I am a macOS user, and I use Zsh not because I really need its extensivetely and power (quite the opposite: I prefer to keep any configurations as barebone as possible), but simply because Zsh is a default macOS shell nowadays.
Here is my .zshrc. What you think about it? Did I miss really useful things that can be enabled by just few lines?
autoload -Uz compinit && compinit
autoload -U colors && colors
alias ls='ls -G'
# history
setopt share_history
bindkey '^[[A' history-beginning-search-backward
bindkey '^[[B' history-beginning-search-forward
# globbing
setopt extended_glob
# zmv
autoload -Uz zmv
alias zcp='zmv -C'
alias zln='zmv -L'
# fewer keystrokes
setopt auto_cd auto_pushd
setopt menu_complete
# fewer distractions
unsetopt beep nomatch notify
# $PATH
path+=$HOME/bin
# the end
Are there any good examples of how to get a block cursor in vi normal mode and a bar cursor otherwise?
Right now i am using this:
function zle-keymap-select {
if [[ $KEYMAP = vicmd ]]; then
echo -ne '\e[2 q'
else
echo -ne '\e[6 q'
fi
}
zle -N zle-keymap-select
zle-line-init() {
echo -ne "\e[5 q"
}
zle -N zle-line-init
It looks like it should work, it does not reliably however, as often the cursor gets stuck in the wrong shape when exiting vim, fzf and sometimes even from just using the prompt. How bad it is also varies depending on the terminal...
Thanks for your help!
Hello everyone,
I'm using the zdharma-continuum version of zinit and using the bin-gem-node annex to avoid having my PATH cluttered with too much stuff. I have been using the "sbin" ice which creates shims, then I noticed the "fbin" ice which does the same thing except it creates functions instead of a shim on disk.
My issue is for things where the binary is in a subdirectory of the release package on GH (usually it's in a directory named after the specific version of the release). When I use **/binary_name syntax, the sbin ice works just fine, but the fbin ice won't find the binary. As an example:
from"gh-r" sbin"**/lnav" atload:'alias -s "log"="lnav"'\
tstack/lnav
This works, but if I replace sbin with fbin, I get:
==> Downloading tstack/lnav
==> Requesting lnav-0.12.2-x86_64-macos.zip
##################################################################################################################################################### 100.0%
[ziextract] Unpacking the files from: `lnav-0.12.2-x86_64-macos.zip'…
[ziextract] Successfully extracted and assigned +x chmod to the file: lnav-0.12.2/lnav.
chmod: /Users/jstegeman/.zinit/plugins/tstack---lnav/**/lnav: No such file or directory
bin-gem-node annex: Something went wrong setting +x on the lnav binary
Is there something I'm doing wrong, or does this need to be fixed in the annex?
Hello. I wish to make a script or alias which edits the previous command. I haven't found a way to pull that up. Internet searches are fruitless, instead only mentioning interactive methods like using the double bang (!!) which is useless for this purpose. Here would be a short example (assuming double bang worked, which it doesn't, but let's just pretend it does):
alias repeat="until !! ; ; do ; ; done"
If I paste the text within the quotes, this will insert the previous line where the double bangs are, then a second enter would execute the line. It obviously doesn't work in a script or as an alias, however.
I have a simple question as a young noob.
Does zsh not have keybinds for control arrows by default? This seems surprising to me.
So, I have this situation:
❯ while true; do status=$(aws rds describe-db-instances --db-instance-identifier "some-instance" --region eu-west-1 --output json | jq -r '.DBInstances[0].DBInstanceStatus'); echo "Status: $status"; sleep 5; done
zsh: read-only variable: status
❯ while true; do xxx=$(date); echo $xxx; sleep 2; done
Tue Nov 12 13:58:56 CET 2024
Tue Nov 12 13:58:58 CET 2024
Why the first one doesn't work?
(The command assigned to the variable works and returns a simple string).
Prompts are being collapsed after resizing the window. This problem started after the powerlevel10k configuration.
Here is what I am aiming for:
PROMPT='%B%F{blue}%~%f%b %(?.%F{green}.%F{red})%(!.❯❯.❯)%f '
RPROMPT='%(!.ROOT.NOTROOT)'
Basically, just the double chevron ❯❯
for root sessions and a right prompt. But I am seeing extra spaces after the prompt character and between the right prompt and the right edge. I tried %(!.%{❯❯%}.%{❯%})
, which works for normal prompt. But the the T in ROOT gets bumped to the next line.
I am trying to use zsh
as main shell for the first time, although I have "tried" it before (like a couple of minutes or hours).
In my setup, I barely set up anything: I only installed oh-my-zsh, along with headline and syntax-highlight addon, and that's it. The thing is, as shown in the video attached, when I switch between different autocompletion options, it fades out. I never noticed this before when I first tried this.
Is it possible to turn this behaviour off and leave unselected options turned on permanently?
Been pushing towards the zsh-abbr v6 major version release. I expect only a small minority of users will have to do anything to migrate — the breaking changes are dropping internal things already deprecated in current v5. But just in case: migration guide at https://v6.zsh-abbr.olets.dev/migrating-between-versions.html#upgrading-from-v5-to-v6, and pointers for pinning your major install to 5.x at https://v5.zsh-abbr.olets.dev/installation.html
Hello everyone,
I think my zsh is either broken or that I simply don't understand it quite well :)
When I for example paste this:
sudo systemctl enable --now syncthing@<username>.service
I am met with the following in the shell:
sudo syssudo systemctl--now syncthing@<usernam<>.servic>
But all the text is white except:sudo systemctl
which is green.
It for some reason adds and changes a few things.
And if I press m
I see the suggestion mdadm -AsfR && vgchange -ay
but when I press TAB (which I pressume is "give me the suggestion" I get mMACHTYPMACHTYPE=
Is it supposed to work like this or is something broken?
Sorry for no images, just get errors when I add it in the post.
[Edit: solved https://www.reddit.com/r/zsh/comments/1gg10jp/comment/lum5vh1/ ]
I'm using ${(k)hash[(r)val]}
to look up keys by values in an associative array hash
.
% typeset -A hash=( [a]=b )
% val=b
% echo ${(k)hash[(r)$val]}
a # good
% val=c
% echo ${(k)hash[(r)$val]}
% # good
and ran into this problem:
% val=')'
% echo ${(k)hash[(r)$val]}
% b # bad
I'm guessing that it's related to
% )
zsh: parse error near `)'
I've found that I can guard against the false find by first checking whether the needle is one of the hash's values
% val=')'
% (( ${${(v)hash}[(Ie)$val]} )) && echo ${(k)hash[(r)$val]}
% # good
Anyone have a better way?
Fwiw my real use case is slightly different: my array has heavily-quoted values, ( ['"a"']='"b"' )
, and I'm really doing (( ${${(v)hash}[(Ie)${(qqq)val}]} )) && echo ${(k)hash[(r)${(qqq)val}]}
I have a basic custom function that wraps some NPM commands when in a particular repo:
function unittests() {
local path=$PWD;
local argc="$#"; #arg count
local argv=("$@"); #arg value
local modules; #modules to run
printf -v modules "A/B/%s," "${argv[@]}"
modules=${modules%,}
if [[ $path == "$HOME/code/my-cool-repo" ]]; then
if [[ $argc != 0 ]]; then
npx cross-env ... # run tests for modules, obfuscated for brevity
else
echo "Running tests for my-module...";
npx cross-env ... # run tests for modules, obfuscated for brevity
fi;
else
echo "Not currently in ../my-cool-repo; aborting..."
return 1;
fi;
}
This was working in bash no issue. I migrated to ZSH a few days ago and I get an error when running it: command not found: npx
.
I use NVM and source it (using below command) from my .zshrc
and can verify npm
is loaded with command like npm --version
, npx --version
, etc. It's definitely there.
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
This is my PATH: export PATH="/opt/homebrew/bin:$PATH"
Any clue what the issue could be?
I'm not sure what info would be relevant, so if I need to provide more please let me know.
Thanks!
Hi,
I keep my binaries and scripts in ~/.local/bin, but whenever I add something new to it, Zsh can't find it, and I get a "command not found" error. After a reboot, it works as expected.
I've looked through several articles, but most only cover setting the PATH variable. Maybe I'm missing something in my configuration. Any help would be appreciated. Thanks!
I've bound ctrl arrows
to move between words, but in zsh4humans you could also press ctrl shift arrows
and to move by entire words and alt shift backspace
to remove entire words including words in quotes as if WORDCHARS weren't set. Is this part of some zsh module or was it zsh4humans exclusive feature? I've tried reimplementing it (code) and failed spectacularly
WORDCHARS='*?[]~&;!#$%^(){}<>,|=+'
bindkey '^[[1;5D' backward-word # Ctrl + Left Arrow
bindkey '^[[1;5C' forward-word # Ctrl + Right Arrow
copy -x
- copies last x commands and their outputs in the terminal to the clipboard
I dont know much about shell scripting, I asked chatgpt to do this, but it could only copy the last x inputs (but not the outputs)
thank you. (terminal - default terminal on macOS)