简体   繁体   中英

Bash completion sometimes meshes up my terminal when the completion function reads a file

So I've been having a problem with some cli programs. Sometimes when I kill the running process with Ctrl + C , it leaves the terminal in a weird state (eg echo is turned off). Now that is to be expected for many cases, as killing a process does not give it a chance to restore the terminal's state. But I've discovered that for many other cases, bash completion is the culprit. As an example, try the following:

  1. Start a new bash session as follows: bash --norc to ensure that no completions are loaded.
  2. Define a completion function: _completion_test() { grep -q foo /dev/null; return 1; } _completion_test() { grep -q foo /dev/null; return 1; } _completion_test() { grep -q foo /dev/null; return 1; } .
  3. Define a completion that uses the above function: complete -F _completion_test rlwrap .
  4. Type exactly the following: r l w r a p Space c a t Tab Enter (ie rlwrap cat followed by a Tab and then by an Enter ).
  5. Wait for a second and then kill the process with Ctrl + C .
  6. The echo of the terminal should have not been turned off. So if you type any character, it will not be echoed by the terminal.

What is really weird is that if I remove the seemingly harmless grep -q foo /dev/null from the completion function, everything works correctly. In fact, adding a grep -q foo /dev/null (or even something even simpler such as cat /dev/null ) to any completion function that was installed in my system, causes the same issue. I have also reproduced the problem with programs that don't use readline and without Ctrl + C (eg find /var Tab | head , with the above completion defined for find ).

Why does this happen?

Edit : Just to clarify, the above is a contrived example. In reality, what I am trying to do, is more like this:

_completion_test() {
    if grep -q "$1" /some/file; then
        #do something
    else
        #do something else
    fi
}

For a more concrete example, try the following:

_completion_test() {
    if grep -q foo /dev/null; then
        COMPREPLY=(cats)
    else
        return 1
    fi
}

But the mere fact that I am calling grep , causes the problem. I don't see why I can't call grep in this case.

Well, the answer to this is very simple; it's a bug :

This happens when a programmable completion function calls an external command during the execution of a completion function. Bash saves the tty state after every successful job completes, so it can restore it if a job is killed by a signal and leaves the terminal in an undesired state. In this case, we need to suppress that if the job that completes is run during programmable completion, since the terminal settings at that time are as readline sets them for line editing. This fix will be in the release version of bash-4.4.

You're just doing a wrong implementation of the completion function. See the manual

-F function

The shell function function is executed in the current shell environment. When it is executed, $1 is the name of the command whose arguments are being completed, $2 is the word being completed, and $3 is the word preceding the word being completed, as described above (see Programmable Completion). When it finishes, the possible completions are retrieved from the value of the COMPREPLY array variable.

for example the following implemenation:

_completion_test() { COMPREPLY=($(cat /dev/null)); return 1; }

doesn't break the terminal.

Regarding your original question why your completion function breaks terminal, I've played a little with strace and I saw that there are ioctl calls with -echo argument. I assume that when you are terminating it with Ctrl + C the ioctl with echo argument just isn't being called in order to restore the original state. Typing stty echo will bring the echo back.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM