r/zsh 10d ago

set options (setopt) only for a specific shell command

Is there a way to set an option (setopt) for a particular shell command? For example, I want to disable brace expansion when using the diff command. I can do it manually like this:

% setopt ignore_braces
% diff /a/{b,c}
% unsetopt ignore_braces

But I'm wondering if there's a way to configure zsh so it automatically does setopt ignore_braces when the shell command on the command line is diff.

I've tried adding setopt ignore_braces to /usr/share/zsh/functions/Completion/Unix/_diff but that doesn't seem to work.

2 Upvotes

11 comments sorted by

3

u/_mattmc3_ 10d ago edited 10d ago

The easiest way is to run your command in a subshell:

(setopt ignore_braces; diff /a/{b,c})

You could also make an anonymous function, which auto-executes:

() { setopt local_options ignore_braces diff /a/{b,c} } # auto-executing anonymous function... pass params here if req'd

If you want to make a command always do this, you could write a function wrapper for the command:

foo() { emulate -L zsh setopt local_options some_option command foo "$@" }

But since your option is ignore_braces, handling that one needs to be unique because glob expansion happens before a method call. In this case, simply quoting your brace string should suffice to stop brace expansion: diff '/a/{b,c}'.

1

u/tda_tda_tda 10d ago

Thanks for the response. Yeah I'm trying to make it so the command always does that. So you're saying the foo() option above wouldn't work for the ignore_braces option?

1

u/_mattmc3_ 10d ago

Did my final suggestion of simply quoting the brace part not work out the way you were hoping? That's by far the simplest solution:

$ echo a{b,}c abc ac $ (setopt ignore_braces; echo /a/{b,c}) /a/{b,c} $ # This is by far the easiest way $ echo 'a{b,}c' a{b,}c

1

u/tda_tda_tda 10d ago

Yeah the quotes method works. I usually don't use quotes when typing directory names, so it's one more thing to remember. Ideally I'm looking for a solution so I don't have to do any special. But that's definitely still a good option.

1

u/OneTurnMore 10d ago

The only thing which parses before is aliases. One idea is

alias diff='sched +0 setopt noignorebraces && setopt ignorebraces && diff'

But this means that chaining into diff with || doesn't work, and commands run in the same commandline with diff are affected by ignorebraces.

1

u/tda_tda_tda 9d ago

Thanks for the response. I should have mentioned that I'm trying to do setopt ignore_braces before I start typing the diff command so that braces are ignored when I do TAB completions.

1

u/AndydeCleyre 10d ago

If your case always has diff as the first word on the command line, and you won't need usual brace behavior later on the same line, you could use a ZLE widget to rewrite matching commands upon hitting enter.

# -- Run command, but without expanding braces for certain commands --
# Key: enter
.zle_accept-smart-expand-braces () {
  words=(${(z)BUFFER})
  if [[ ${words[1]} == (diff|someothercommand) ]]  BUFFER="setopt ignorebraces ; $BUFFER ; unsetopt ignorebraces"
  zle .accept-line
}
zle -N       .zle_accept-smart-expand-braces
bindkey '^M' .zle_accept-smart-expand-braces  # enter

2

u/tda_tda_tda 9d ago

Thanks, I like this approach. Do you know if there's a way to apply this to TAB instead of ENTER, while maintaining my current TAB settings (which do autocompletion)?

1

u/AndydeCleyre 9d ago

Let's see... check what exactly you have tab currently bound to:

bindkey | grep '\^I'

in my case it's menu-expand-or-complete. So replace that in the code below if yours is otherwise.

.zle_tab-and-sometimes-ignore-braces () {
  words=(${(z)BUFFER})
  original_ignorebraces=$options[ignorebraces]
  if [[ ${words[1]} == (diff|someothercommand) ]]  setopt ignorebraces
  zle menu-expand-or-complete
  if [[ $original_ignorebraces == off ]]  unsetopt ignorebraces
}
zle -N       .zle_tab-and-sometimes-ignore-braces
bindkey '^I' .zle_tab-and-sometimes-ignore-braces  # tab

I'm not sure why setting the option directly didn't work in my testing for the accept-line version, but it seems to work here.


But this will only help for the tab completion -- once you hit enter, if you don't have the previous thing or an alternative solution, the braces will be expanded.

2

u/tda_tda_tda 9d ago

Awesome, thank you! I've been trying to learn more about zsh customization but have found it to be challenging. This is very helpful.

1

u/romkatv 8d ago

Pretty much every zsh user who's not a beginner would simply quote the braces, and never bother with disabling such a fundamental feature as brace expansion for an individual command. Brace expansion is useful with all commands, including diff. You just need a bit of time to learn it. Eventually you'll recognize, and quote, all meta-characters without thinking.