cobra: shell completions do not expand ~ or environment variables

When using fish completions with a cobra.Command.ValidArgsFunction to specify custom completions, leading ~s and environment variables are not expanded before the argument to complete is passed to the ValidArgsFunction.

For example, if the user types the following to trigger shell completion:

$ command arg ~/.z<tab>

ValidArgsFunction is called with the toComplete argument equal to ~/.z. The tilde expansion should be done before this, i.e. the toComplete argument should be /home/user/.z where /home/user is the user’s home directory.

Similarly, if the user types:

$ command arg $SOME_VAR/.z

then ValidArgsFunction is called with the toComplete argument equal to $SOME_VAR/.z. Instead, toComplete should have the value with $SOME_VAR expanded.

This is different to other shell completions (e.g. zsh), where ~ and environment variables are expanded by the shell before being passed to ValidArgsFunction.

The expansion of ~ and environment variables must be done in the fish shell itself. It cannot be done by the ValidArgsFunction because:

  • The value of environment variables might not be known. For example, if toComplete contains an unexported environment variable then its value is not available to the process executing ValidArgsFunction.
  • The exact interpretation of ~ depends on the shell. Example common interpretations include ~ for the user’s home directory, ~/file for a file in the user’s home directory, ~username for a different user’s home directory, and ~username/file for a file in a different user’s home directory. It is unreasonable for the ValidArgsFunction to emulate fish’s behavior exactly.
  • As the fish completions behave differently to other shell completions, implementing fish’s logic in ValidArgsFunction would break other shell completions.

Refs https://github.com/twpayne/chezmoi/issues/1796#issuecomment-1008247269.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (9 by maintainers)

Most upvoted comments

Sorry for getting back late. The problem is in the fact that cobra’s fish script purposefully escapes the arguments, preventing them from being expanded by the shell. See here: https://github.com/spf13/cobra/blob/e04ec725508c760e70263b031e5697c232d5c3fa/fish_completions.go#L36

We made this choice willingly but I didn’t realize the impact it would have. See this comment https://github.com/spf13/cobra/pull/1249#discussion_r563348012

I’ll have to look into alternatives.

@twpayne could you point me to the chezmoi code that has problems with this? I’m asking because although variables and ~ are not expanded, file completion still works from the user’s perspective. I’m assuming the problem is for the program receiving this unexpanded argument.

I don’t think this issue is stale. On the contrary, it communicates a genuine problem that still exists and should not be forgotten.

This issue is being marked as stale due to a long period of inactivity

Which version of fish are you using and on which platform? I’m using fish 3.3.1 on MacOS.

I’m also using fish 3.3.1 on macOS installed with Homebrew.

Here is a program to try to reproduce. I built it and then ran the following, which seems to expand properly:

This does not reproduce the problem because it does not invoke the tab completion logic in fish.

To reproduce the problem, run the following with the modified Go code below. The modified Go code just adds a completion command to generate the fish completion file. Ensure that prog is in your $PATH.

$ fish
$ prog completion > prog.fish                           # generate fish completion for prog
$ . prog.fish                                           # source fish completion for prog
$ set -x BASH_COMP_DEBUG_FILE /tmp/bash-completion.log  # enable fish completion debugging
$ prog $HOME/<tab><Ctrl-C>                              # invoke tab completion logic
$ cat $BASH_COMP_DEBUG_FILE                             # print debug file

I see the following in $BASH_COMP_DEBUG_FILE:


========= starting completion logic ==========
Starting __prog_perform_completion
args: prog
last arg: '$HOME/'
Calling prog __completeNoDesc  '$HOME/'
[Debug] args: [], toComplete: $HOME/
Comps:
DirectiveLine: :0
flagPrefix:
Completion results: :0
Completions are:
Directive is: 0
nospace: 0, nofiles: 0
prefix: \$HOME/
Filtered completions are:
numComps: 0
Requesting file completion

Note that toComplete is $HOME/, i.e. the $HOME environment variable has not been expanded.

Modified Go code:

package main

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use: "prog",
	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
		cobra.CompDebugln(fmt.Sprintf("args: %v, toComplete: %v", args, toComplete), true)
		return nil, cobra.ShellCompDirectiveDefault
	},
	Run: func(cmd *cobra.Command, args []string) {},
}

var completionCmd = &cobra.Command{
	Use: "completion",
	RunE: func(cmd *cobra.Command, args []string) error {
		return rootCmd.GenFishCompletion(os.Stdout, false)
	},
}

func main() {
	rootCmd.AddCommand(completionCmd)
	rootCmd.Execute()
}