1

I have an alias setup in my zshrc to rerun a command with sudo:

alias please='sudo $(fc -nl -1)'

It works admirably except for the case where I had used a wildcard in the previous command e.g. "rm *.o", where the command will return with an error like "rm: cannot remove '*.o': no such file or directory".

Is there a good way to get this alias to correctly use the wildcard?

4
  • What would be the correct result?
    – muru
    Jun 20, 2019 at 15:14
  • The intended result in the example would be to remove all files with a .o extension, instead of the * being taken as a literal
    – eirrw
    Jun 20, 2019 at 15:27
  • Do you have read and execute permissions for that directory?
    – muru
    Jun 20, 2019 at 15:29
  • 3
    Ah, I see, you're using zsh. Why do you have "bash fc" in the title then?
    – muru
    Jun 20, 2019 at 15:59

3 Answers 3

2

No, there's no good way to “get this alias to correctly use the wildcard”, because “correctly” means different things in different cases. For example, if the command line included a command substitution, did you mean to run that other command as root or not? The kind of automation you're after is very convenient to do the wrong thing fast. It's like closing your eyes when you cross the street because watching for cars is too annoying.

My advice is to type Up Home s u d o Space and take the time to re-read the command to be sure that what you're doing is what you meant to do.

That being said, if you like risk, sure, you can make your computer do something. Here are two solutions that do different things.

The following function assumes that the previous command was a call to an external command. It calls the external command with the same arguments, but with sudo prefixed. This means that things like wildcard expansions, variable substitutions and command substitutions are called in the context of your shell.

please () {
  setopt local_options err_exit
  local args
  eval "args=($history[$((HISTCMD-1))])"
  print -lr Running sudo "$args[@]"
  sleep 2
  sudo "$args[@]"
}

The following command assumes that the previous command didn't modify the shell environment and didn't rely on any environment variable. It runs the entire command line as root, including wildcard expansions, command substitutions, etc.

please () {
  sudo zsh -c "$history[$HISTCMD]"
}
0

The only solution seems to be:

alias please='eval sudo $(fc -nl -1)'
0

Zsh disables globbing on the output of command substitution unless GLOB_SUBST is set:

If the option GLOB_SUBST is set, the result of any unquoted command substitution, including the special form just mentioned, is eligible for filename generation.

You can enable it, and then your alias should work. You can also use the history array:

history
This associative array maps history event numbers to the full history lines. Although it is presented as an associative array, the array of all values (${history[@]}) is guaranteed to be returned in order from most recent to oldest history event, that is, by decreasing history event number.

And then use pattern substitution flags to enable GLOB_SUBST selectively:

${=spec}
Perform word splitting using the rules for SH_WORD_SPLIT during the evaluation of spec, but regardless of whether the parameter appears in double quotes; if the = is doubled, turn it off. This forces parameter expansions to be split into separate words before substitution, using IFS as a delimiter. [...]

${~spec}
Turn on the GLOB_SUBST option for the evaluation of spec; if the ~ is doubled, turn it off.

Example:

% alias s='sudo ${~=${history[@]}[1]}'
% echo *                                   
a b c
% s
[sudo] password for muru: 
a b c
% echo [a]
a
% s       
a
1
  • It works in this specific case, but it breaks a lot of things, including quoted words. E.g. mycommand --option="some stuff" *.txt passes --option="some and stuff" as arguments to the command. Jun 20, 2019 at 17:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .