4

I'm trying to debug auto completion script located in /usr/share/bash-completion/bash_completion (bash completion package on every distro)
I did

export PS4='+'$'\t''$LINENO'$'\t    # \t for proper indentation
set -x

in the current shell, then typed a command (following by space), and pressed tab to call auto completion.
This is a part of the result:

+       2205    for dir in "${dirs[@]}"
+       24      [[ -d /usr/share/bash-completion/completions ]]
+       2207    for compfile in "$cmd" "$cmd.bash" "_$cmd"
+       26      compfile=/usr/share/bash-completion/completions/bittch
+       28      [[ -f /usr/share/bash-completion/completions/bittch ]]
+       2207    for compfile in "$cmd" "$cmd.bash" "_$cmd"

As you see the first line number is 2205 which is the absolute line number inside the script. Surprisingly, the number of the next line (which is a nested block) is 24, that is the line number relative to the function itself (see below)

__load_completion()
{
    local -a dirs=(${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions)
    local ifs=$IFS IFS=: dir cmd="${1##*/}" compfile
    [[ -n $cmd ]] || return 1
    for dir in ${XDG_DATA_DIRS:-/usr/local/share:/usr/share}; do
        dirs+=($dir/bash-completion/completions)
    done
    IFS=$ifs

    if [[ $BASH_SOURCE == */* ]]; then
        dirs+=("${BASH_SOURCE%/*}/completions")
    else
        dirs+=(./completions)
    fi

    local backslash=
    if [[ $cmd == \\* ]]; then
        cmd="${cmd:1}"
        # If we already have a completion for the "real" command, use it
        $(complete -p "$cmd" 2>/dev/null || echo false) "\\$cmd" && return 0
        backslash=\\
    fi
    for dir in "${dirs[@]}"; do
        [[ -d $dir ]] || continue
        for compfile in "$cmd" "$cmd.bash" "_$cmd"; do
            compfile="$dir/$compfile"
            # Avoid trying to source dirs; https://bugzilla.redhat.com/903540
            if [[ -f $compfile ]] && . "$compfile" &>/dev/null; then
                [[ $backslash ]] && $(complete -p "$cmd") "\\$cmd"
                return 0
            fi
        done
    done

    # Look up simple "xspec" completions
    [[ -v _xspecs[$cmd] ]] &&
        complete -F _filedir_xspec "$cmd" "$backslash$cmd" && return 0

    return 1
}

I know that LINENO represents The line number in the script or shell function currently executing. But these are in the same function, so either both should be relative (to the beginning of __load_completion()) or absolute.
Why is this?

3
  • It looks like it depends on the kind of statement. control-flow statements (e.g. for) get absolute line numbers, ordinary statements get relative. Commented Oct 12, 2021 at 19:15
  • @Barmar It makes sense, but this behavior is undesired I believe. At least it's not documented. I'll go through source code this weekend. Commented Oct 13, 2021 at 7:10
  • I wasn't trying to explain it, just noticing a pattern. Commented Oct 13, 2021 at 13:57

1 Answer 1

2

From one of the dustier corners of POSIX:

The expansion of $LINENO inside a shell function is only relative to the
function start if the shell is interactive -- if the shell is running a
script, $LINENO expands to the line number in the script.  This is as
POSIX-2001 requires.

(text from the bash CHANGES file).

What you are observing is exactly that, or rather an incomplete implementation of that, since completion functions are effectively interactive. The cause is that the various nestable control structures cache a line number (relative to the file) and this is used when LINENO is printed for that type of command.

As commands are executed, e.g. (bash-5.2) execute_simple_command() we have:

/* If we're in a function, update the line number information. */
if (variable_context && interactive_shell && sourcelevel == 0)
{
  line_number -= function_line_number;
  if (line_number < 0)
    line_number = 0;
}

which implements the different LINENO behaviour for interactive or script modes.

But, for those more complex functions the logic seems to be either incomplete or missing:

  • arithmetic for ((..;..;..)) has partial logic and gets the first line correct but then the 2nd and 3rd expression are mis-numbered
  • normal for NAME in WORD .. has no special handling
  • while and until and case also have no special handling

Based on historic CHANGES, the partially correct behaviour of the arithmetic-for may have been due to fixes for various bug reports.

The good news is that this has been reworked in bash-5.3 with the ADJUST_LINE_NUMBER macro used across multiple execute handlers. The bad news is it has not been added to execute_for_command(), yet. If you're feeling brave you can add a call to ADJUST_LINE_NUMBER () at this line to fix the issue (though I have not tested this heavily, YMMV).

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.