Bash Completion for Path in Argument (With Equals Sign Present)

Bash completion for path in argument (with equals sign present)

I removed all bash completion scripts and started adding them one by one to if any of them cause the problem.

In my case it turned out to be the npm completion script was the cause of this problem.

Not sure (yet) what the problem is, but this is the completion script which caused equal sign values not working as before:

    ###-begin-npm-completion-###
#
# npm command completion script
#
# Installation: npm completion >> ~/.bashrc (or ~/.zshrc)
# Or, maybe: npm completion > /usr/local/etc/bash_completion.d/npm
#

COMP_WORDBREAKS=${COMP_WORDBREAKS/=/}
COMP_WORDBREAKS=${COMP_WORDBREAKS/@/}
export COMP_WORDBREAKS

if type complete &>/dev/null; then
_npm_completion () {
local si="$IFS"
IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
COMP_LINE="$COMP_LINE" \
COMP_POINT="$COMP_POINT" \
npm completion -- "${COMP_WORDS[@]}" \
2>/dev/null)) || return $?
IFS="$si"
}
complete -F _npm_completion npm
elif type compdef &>/dev/null; then
_npm_completion() {
si=$IFS
compadd -- $(COMP_CWORD=$((CURRENT-1)) \
COMP_LINE=$BUFFER \
COMP_POINT=0 \
npm completion -- "${words[@]}" \
2>/dev/null)
IFS=$si
}
compdef _npm_completion npm
elif type compctl &>/dev/null; then
_npm_completion () {
local cword line point words si
read -Ac words
read -cn cword
let cword-=1
read -l line
read -ln point
si="$IFS"
IFS=$'\n' reply=($(COMP_CWORD="$cword" \
COMP_LINE="$line" \
COMP_POINT="$point" \
npm completion -- "${words[@]}" \
2>/dev/null)) || return $?
IFS="$si"
}
compctl -K _npm_completion npm
fi
###-end-npm-completion-###

Bash Tab Completion of Filenames after Arguments

After googling (and help from friends) i've found the solution!

To fix filename auto completion add the following to your ~/.bashrc file:

complete -D -o default

Reference: Bash completion for path in argument (with equals sign present)

How to reset COMP_WORDBREAKS without affecting other completion script?

Modifying $COMP_WORDBREAKS in your completion script is not the recommended way (as it is a global variable and it could affect the behavior of other completion scripts - for example ssh).

However, bash completion offers some helper methods which you can use to achieve your goal.

The recommended way to handle non-word-breaking characters in completion words is by using the two helper methods:

  • _get_comp_words_by_ref with the -n EXCLUDE option

    • gets the word-to-complete without considering the characters in EXCLUDE as word breaks
  • __ltrim_colon_completions
    • removes colon containing prefix from COMPREPLY items

      (a workaround for http://tiswww.case.edu/php/chet/bash/FAQ - E13)

So, here is a basic example of how to a handle a colon (:) in completion words:

_mytool()
{
local cur
_get_comp_words_by_ref -n : cur

# my implementation here

COMPREPLY=( $(compgen ..........my_implement......... -- $cur) )

__ltrim_colon_completions "$cur"
}
complete -F _mytool mytool

As a final tip, the helper methods are located in /etc/bash_completion. Take a look inside to read a detailed description of each method and to discover more helper methods.

python argparse autocompletion with file paths

For me using the equals sign ("=") after the argument name got the autocomplete to work and suggest all files matching the input path so far. For example, try:

python main.py -c=<your path>

When I didn't use the "=" sign the autocomplete worked only for folders and files ending with ".py" extension. The possible reason behind could be completion script installed for python. See here for more: https://www.reddit.com/r/bash/comments/b8oqeg/comment/ek1ds7t/?utm_source=share&utm_medium=web2x&context=3

Customizing bash completion output: each suggestion on a new line

bash prior to version 4.2 doesn't allow any control over the output format of completions, unfortunately.

Bash 4.2+ allows switching to 1-suggestion-per-line output globally, as explained in Grisha Levit's helpful answer, which also links to a clever workaround to achieve a per-completion-function solution.

The following is a tricky workaround for a custom completion.
Solving this problem generically, for all defined completions, would be much harder (if there were a way to invoke readline functions directly, it might be easier, but I haven't found a way to do that).

To test the proof of concept below:

  • Save to a file and source it (. file) in your interactive shell - this will:

    • define a command named foo (a shell function)
    • whose arguments complete based on matching filenames in the current directory.
    • (When foo is actually invoked, it simply prints its argument in diagnostic form.)
  • Invoke as:
    foo [fileNamePrefix], then press tab:

    • If between 2 and 9 files in the current directory match, you'll see the desired line-by-line display.
    • Otherwise (1 match or 10 or more matches), normal completion will occur.

Limitations:

  • Completion only works properly when applied to the LAST argument on the command line being edited.
  • When a completion is actually inserted in the command line (once the match is unambiguous), NO space is appended to it (this behavior is required for the workaround).
  • Redrawing the prompt the first time after printing custom-formatted output may not work properly: Redrawing the command line including the prompt must be simulated and since there is no direct way to obtain an expanded version of the prompt-definition string stored in $PS1, a workaround (inspired by https://stackoverflow.com/a/24006864/45375) is used, which should work in typical cases, but is not foolproof.

Approach:

  • Defines and assigns a custom completion shell function to the command of interest.
  • The custom function determines the matches and, if their count is in the desired range, bypasses the normal completion mechanism and creates custom-formatted output.
  • The custom-formatted output (each match on its own line) is sent directly to the terminal >/dev/tty, and then the prompt and command line are manually "redrawn" to mimic standard completion behavior.
  • See the comments in the source code for implementation details.
# Define the command (function) for which to establish custom command completion.
# The command simply prints out all its arguments in diagnostic form.
foo() { local a i=0; for a; do echo "\$$((i+=1))=[$a]"; done; }

# Define the completion function that will generate the set of completions
# when <tab> is pressed.
# CAVEAT:
# Only works properly if <tab> is pressed at the END of the command line,
# i.e., if completion is applied to the LAST argument.
_complete_foo() {

local currToken="${COMP_WORDS[COMP_CWORD]}" matches matchCount

# Collect matches, providing the current command-line token as input.
IFS=$'\n' read -d '' -ra matches <<<"$(compgen -A file "$currToken")"

# Count matches.
matchCount=${#matches[@]}

# Output in custom format, depending on the number of matches.
if (( matchCount > 1 && matchCount < 10 )); then

# Output matches in CUSTOM format:
# print the matches line by line, directly to the terminal.
printf '\n%s' "${matches[@]}" >/dev/tty
# !! We actually *must* pass out the current token as the result,
# !! as it will otherwise be *removed* from the redrawn line,
# !! even though $COMP_LINE *includes* that token.
# !! Also, by passing out a nonempty result, we avoid the bell
# !! signal that normally indicates a failed completion.
# !! However, by passing out a single result, a *space* will
# !! be appended to the last token - unless the compspec
# !! (mapping established via `complete`) was defined with
# !! `-o nospace`.
COMPREPLY=( "$currToken" )
# Finally, simulate redrawing the command line.
# Obtain an *expanded version* of `$PS1` using a trick
# inspired by https://stackoverflow.com/a/24006864/45375.
# !! This is NOT foolproof, but hopefully works in most cases.
expandedPrompt=$(PS1="$PS1" debian_chroot="$debian_chroot" "$BASH" --norc -i </dev/null 2>&1 | sed -n '${s/^\(.*\)exit$/\1/p;}')
printf '\n%s%s' "$expandedPrompt" "$COMP_LINE" >/dev/tty

else # Just 1 match or 10 or more matches?

# Perform NORMAL completion: let bash handle it by
# reporting matches via array variable `$COMPREPLY`.
COMPREPLY=( "${matches[@]}" )

fi

}

# Map the completion function (`_complete_foo`) to the command (`foo`).
# `-o nospace` ensures that no space is appended after a completion,
# which is needed for our workaround.
complete -o nospace -F _complete_foo -- foo

How to obtain the absolute path of a file via Shell (BASH/ZSH/SH)?

Use realpath

$ realpath example.txt
/home/username/example.txt

What does export variable without an equals sign do?

The two lines of code (better to use double brackets):

[[ -r /etc/java/java.conf ]] && . /etc/java/java.conf
export JAVA_HOME

...equates to this:

-r checks if /etc/java/java.conf exists and read permission is granted.

&& if the condition above is true then source the file.

export JAVA_HOME takes the previously-assigned value of $JAVA_HOME from the sourced file making it available to subprocesses rather than only within the shell.

  • What does "export" do in shell programming?
  • What does "source" do?
  • Conditional statements in bash


Related Topics



Leave a reply



Submit