4

I'm trying to imitate the bash file completion.
Suppose I have the following files:

test1
test2

With an input string of "te" I would like to get the output "test"

This is my current attempt ($c is the input string):

l=1
q="$c"
for j in $(ls -A | grep "^$c"); do
    if [ "${j/$c}" != "$j" ]; then 
            n=$(ls -A | grep ^$j | wc -l)
            if [ $n -gt $l ]; then 
                    q="$j"  
            fi      
    fi
done
c="$q"
echo $c

Thanks for any help

1
  • 1
    quoting errors everywhere, ls parsing... I don't see how this could ever work. But the question is good! It would be nice to know how can you get bash autocompletion to work inside of your script without reinventing the wheel Commented Dec 29, 2013 at 17:34

3 Answers 3

1

I tend to think there is no a way to get this from completion engine since it’s not a part of GNU Bash but Readline. But at least we can get list of possible completions with compgen. And an inmplementaion of finding longest common prefix should not be problem. So...

#!/bin/bash

SCRIPTNAME="${0##*/}"
USAGE="Usage: $SCRIPTNAME <prefix>

Print common prefix of possible file name completions. Like <TAB> but to
stdout."

(( $# == 1 )) || { printf >&2 '%s\n' "$USAGE"; exit 1; }

PREFIX="$1"

commonprefix() {
    (( $# >= 2 )) || { 
        echo "$1"
        return 0
    }
    local -i i N M
    for ((i=0; i<=${#1}; i++)); do 
        for ((N=1; N<=$#-1; N++)); do
            let M=$N+1
            [[ ${!N:i:1} == ${!M:i:1} ]] || break 2
        done
    done
    echo "${1:0:i}"
}

readarray -t COMPLETIONS < <(compgen -f "$PREFIX")
commonprefix "${COMPLETIONS[@]}"
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer but it doesn't work quite correctly. I tried it over my bin folder using "ui" - but I get "uim-" as output although there is another file called "uic". The exclamation mark thing is very cool - could be useful for the future.
@Yggi Yes, you are right. I don’t use default TAB-completion so I misunderstood task. Fixed. I hope now it works quite as you expect.
1

Although Dmitry Alexandrov already provided a better solution, I still would like to post my own one which I made while waiting for the answers:

l=1
while [ -n $l ]; do
    l=${#c}
    a=$(ls -A | grep "^$c" | wc -l)
    q=$c
    for i in $(ls -A | grep "^$q"); do
            if [ $i == $q ]; then 
                    unset l
                    break
            else
                    v=$(ls -A | grep "^$q${i:$l:1}" | wc -l)
                    if [ $v == $a ]; then 
                            q="$c${i:$l:1}"
                            break
                    fi
            fi
    done
    if [ $c == $q ]; then break; fi
    c=$q
done
echo $c

It works with all of my tests, but it's slow (although it could be optimized).

Comments

0

Just to show that you were thinking in correct direction, I made your code work:

#!/bin/bash

c=$1
q="$c"
for j in $c*; do
    if [ "${j/$c}" != "$j" ]; then
        startn=$(ls -1A | grep -c "^${j:0:$((${#c} + 1))}")
        for (( i=${#c}; i <= ${#j}; i++ )); do
            n=$(ls -1A | grep -c "^${j:0:$i}")
            if [ "$n" -lt "$startn" ]; then
                q="${j:0:$((i - 1))}"
            elif [ "$n" -le "$startn" ]; then
                q="${j:0:$i}"
            fi
        done
    fi
done
c="$q"
echo "$c"

But, it's just a proof of concept, don't use it. See answer by Dmitry Alexandrov for a good solution.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.