[pacman-dev] [PATCH] [bash_completion] Undeclared local vars/filenames with spaces/other

Andres Perera andres87p at gmail.com
Wed Jan 20 00:26:52 EST 2010


Fix:
3 Undeclared local vars with common enough names to warrant breakage

Performance issues with _pacman trying to replicate /usr/bin/pacman
with find and other slow tools.

Performance issues with expanding an array (with sometimes hundreds of
items) over three times.

Expanding said array to remove already completed entries had the side
effect of braking filenames with spaces and or \n.

The full description of fixes are already posted at:
http://bugs.archlinux.org/task/16630#comment55779
along with time diffs.

Signed-off-by: Andres P <andres87p at gmail.com>
---
 contrib/bash_completion |  535 ++++++++++++++++++-----------------------------
 1 files changed, 202 insertions(+), 333 deletions(-)

diff --git a/contrib/bash_completion b/contrib/bash_completion
index 62e5bc9..65051f5 100644
--- a/contrib/bash_completion
+++ b/contrib/bash_completion
@@ -1,365 +1,234 @@
-# vim: set ft=sh ts=2 sw=2 et:
-# file: /etc/bash_completion.d/pacman
-
-# Bash completion for pacman
-# Original: Manolis Tzanidakis <mtzanidakis at freemail.gr>
+# pacman/makepkg completion by Andres Perera <andres87p gmail>
 #
-# Distributed under the terms of the GNU General Public License, v2 or later.
+# Distributed under the terms of the GNU General Public License v3 or
+# later.
 #
-
-## initial functions
-
-rem_selected ()
-{
-  # (Adapted from bash_completion by Ian Macdonald <ian at caliban.org>)
-  # This removes any options from the list of completions that have
-  # already been specified on the command line.
-  COMPREPLY=($(/bin/echo "${COMP_WORDS[@]}" | \
-    (while read -d ' ' i; do
-      [ "${i}" == "" ] && continue
-      # flatten array with spaces on either side,
-      # otherwise we cannot grep on word boundaries of
-      # first and last word
-      COMPREPLY=" ${COMPREPLY[@]} "
-      # remove word from list of completions
-      COMPREPLY=(${COMPREPLY/ ${i%% *} / })
+#   Local variables: common core cur glob list long m o prev query r
+#                    remove s short sync upgrade w
+#
+# vim: ft=sh sts=2 sw=2 et:
+
+# Removes packages/files already present in the line
+_arch_rem_selected() {
+  local w r
+
+  for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do
+    # Don't touch operands
+    [[ ${COMP_WORDS[w]} == -* ]] && continue
+    for r in "${!COMPREPLY[@]}"; do
+      # Only deal with full matches
+      if [[ ${COMP_WORDS[w]} == ${COMPREPLY[r]} ]]; then
+        unset 'COMPREPLY[r]'; break
+      fi
     done
-    /bin/echo ${COMPREPLY[@]})))
-  return 0
-}
-
-_available_repos ()
-{
-  COMPREPLY=( $( compgen -W "$(/bin/grep '\[' /etc/pacman.conf |
/bin/grep -v -e 'options' -e '^#' | tr -d '[]' )" -- $cur ) )
-}
-
-_installed_pkgs ()
-{
-  local installed_pkgs
-  installed_pkgs=$( /bin/ls /var/lib/pacman/local/ )
-  COMPREPLY=( $( compgen -W "$( for i in $installed_pkgs; do
/bin/echo ${i%-*-*}; done )" -- $cur ) )
+  done
 }

-_available_pkgs ()
-{
-  #find balks easilly on a find /foo/*/* type dir, especially one like
-  #   /var/lib/pacman/*/*
-  # This little change-up removes the find *and* only uses enabled repos
-  local available_pkgs
-  local enabled_repos
-  enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e
'options' -e '^#' | tr -d '[]' )
-  available_pkgs=$( for r in $enabled_repos; do /bin/echo
/var/lib/pacman/sync/$r/*; done )
-  COMPREPLY=( $( compgen -W "$( for i in $available_pkgs; do
j=${i##*/}; echo ${j%-*-*}; done )" -- $cur ) )
-}
+# makepkg purge functions
+_makepkg_deselect() {
+  local o

-_installed_groups ()
-{
-  local installed_groups
-  installed_groups=$( /bin/find /var/lib/pacman/local -name desc
-exec /bin/sed -ne '/%GROUPS%/,/^$/{//d; p}' {} \; | /bin/sort -u )
-  COMPREPLY=( $( compgen -W "$( for i in $installed_groups; do
/bin/echo ${i%-*-*}; done )" -- $cur ) )
+  if [[ $1 == l ]]; then
+    for o in "${!long[@]}"; do
+      if [[ ${COMP_WORDS[w]} == ${long[o]} ]]; then
+        unset 'long[o]'; break
+      fi
+    done
+  else
+    for o in "${!short[@]}"; do
+      if [[ ${COMP_WORDS[w]} == -*([^- ])${short[o]}* ]]; then
+        unset 'short[o]'
+      fi
+    done
+  fi
 }

-_available_groups ()
-{
-  #find balks easilly on a find /foo/*/* type dir, especially one like
-  #   /var/lib/pacman/*/*
-  # This little change-up removes the find *and* only uses enabled repos
-  local available_groups
-  local enabled_repos
-  enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e
'options' -e '^#' | tr -d '[]' )
-  available_groups=$( for r in $enabled_repos; do /bin/sed
'/%GROUPS%/,/^$/{//d; p}' /var/lib/pacman/sync/$r/*/desc | /bin/sort
-u; done )
-  COMPREPLY=( $( compgen -W "$( for i in $available_groups; do
/bin/echo ${i%-*-*}; done )" -- $cur ) )
+_makepkg_count_words() {
+  local w
+
+  for (( w=0; w<${#COMP_WORDS[@]}; w++)); do
+    [[ ${COMP_WORDS[w]} == @(-*([^- ])[hC]*|--@(help|cleancache)) ]]
&& return 0
+    _makepkg_deselect "$1"
+  done
 }

-## makepkg completion
-
-_makepkg ()
-{
-  local cur prev
+# makepkg main
+_makepkg() {
   COMPREPLY=()
-  cur=${COMP_WORDS[COMP_CWORD]}
-  prev=${COMP_WORDS[COMP_CWORD-1]}

-  case "$prev" in
-    -p)
-      _filedir
-      return 0
-    ;;
-    --help|--cleancache)
-      COMPREPLY=''
-      return 0
-    ;;
-  esac
-
-  if [[ "$cur" == -* ]]; then
-    COMPREPLY=( $( compgen -W '\
-      -A --ignorearch \
-      -b --builddeps \
-      -c --clean \
-      -C --cleancache \
-      -d --nodeps \
-      -e --noextract \
-      -f --force \
-      -g --geninteg \
-      -h --help \
-      -i --install \
-      -L --log \
-      -m --nocolor \
-      -o --nobuild \
-      -p \
-      -r --rmdeps \
-      -s --syncdeps \
-      --asroot \
-      --source \
-      --noconfirm \
-      --noprogressbar' -- $cur ) )
+  local prev cur short long parse glob
+  prev=${COMP_WORDS[COMP_CWORD-1]}
+  cur=`_get_cword`
+
+  short=( A C L R c d e f g h i m o p r s )
+
+  long=(  --allsource --asroot --clean --cleancache --config --force
+          --geninteg --help --holdver --ignorearch --install --log
+          --nobuild --nocolor --noconfirm --nodeps --noextract
+          --noprogressbar --repackage --rmdeps --skipinteg --source
+          --syncdeps )
+
+  if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then
+  # First word check
+    COMPREPLY=($(compgen -W "$(echo "${short[@]/#/-}" "${long[@]}")"
-- "$cur"))
+  elif [[ $prev == @(--config|-*([^- ])p*) ]]; then
+  # Complete filenames for selected options
+    _arch_rem_selected
+    return 0
+  else
+  # Generate the list depending on current word
+    case $cur in
+      -+($(IFS='|'; echo "${short[*]}")))
+        _makepkg_count_words s
+        COMPREPLY=($(compgen -W "$(echo "${short[@]/#/$cur}")" -- "$cur"))
+      ;;
+      -*)
+        # If $cur isn't a combination option, fall back to displaying
+        # long options only. These are the prime target for completion
+        _makepkg_count_words l
+        COMPREPLY=($(compgen -W "$(echo "${long[@]}")" -- "$cur"))
+      ;;
+    esac
   fi
-
-  rem_selected
 }
-complete -o default -F _makepkg makepkg

-## pacman completion
-
-_instring ()
-{
-  str="${1}"
-  shift 1
-  for c in "${@}"; do
-    if [ $(/bin/expr index "${str}" "${c}") -gt 0 ]; then
-      return 0
-    fi
-  done
-  return 1
+# pacman packages
+_pacman_pkg() {
+  COMPREPLY=($(compgen -W "$(if [[ $2 ]]; then
+                               command pacman -"$1"  |\
+                               command sed 's, .*,,' |\
+                               command sort -u
+                             else
+                               command pacman -"$1"
+                             fi)"     --     "$cur" ))
 }

-_pacman ()
-{
-  local a arg toparse op mod cur
+# pacman main
+_pacman() {
   COMPREPLY=()

-  # This argument parsing is done so we can check for flag existance later
-  #  right now it's a tad crappy, but does the job
-  for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do
-    a=${COMP_WORDS[i]}
-    arg="${a:0:2}"
-    toparse="${a:2}"
+  local prev cur query remove sync upgrade core common list w m o s=()
+  prev=${COMP_WORDS[COMP_CWORD-1]}
+  cur=`_get_cword`

-    case "${arg}" in
-      -@(U|R|S|Q|h|V))
-        op="${arg/-}"
-        mod="${mod}${a:2}"
-        ;;
-      --)
-        arg="${a:2}"
-        case "${arg}" in
-          remove) op="R" ;;
-          upgrade) op="U" ;;
-          query) op="Q" ;;
-          sync) op="S" ;;
-          help) op="h" ;;
-          version) op="V" ;;
-          verbose) mod="${mod}v" ;;
-          root) mod="${mod}r" ;;
-          dbpath) mod="${mod}b" ;;
-          nodeps) mod="${mod}d" ;;
-          force) mod="${mod}f" ;;
-          groups) mod="${mod}g" ;;
-          info) mod="${mod}i" ;;
-          list) mod="${mod}l" ;;
-          print-uris) mod="${mod}p" ;;
-          search) mod="${mod}s" ;;
-          sysupgrade) mod="${mod}u" ;;
-          upgrades) mod="${mod}u" ;;
-          downloadonly) mod="${mod}w" ;;
-          refresh) mod="${mod}y" ;;
-          changelog) mod="${mod}c" ;;
-          deps) mod="${mod}d" ;;
-          explicit) mod="${mod}e" ;;
-          unrequired) mod="${mod}t" ;;
-          foreign) mod="${mod}m" ;;
-          owns) mod="${mod}o" ;;
-          file) mod="${mod}p" ;;
-          search) mod="${mod}s" ;;
-          upgrades) mod="${mod}u" ;;
-          cascade) mod="${mod}c" ;;
-          check) mod="${mod}k" ;;
-          dbonly) mod="${mod}k" ;;
-          nosave) mod="${mod}n" ;;
-          recursive) mod="${mod}s" ;;
-          unneeded) mod="${mod}u" ;;
-        esac ;;
-      *) toparse="${a}" ;;
-    esac
+  # These are split in a subshell with IFS=' '. No point in listing
+  # them by \n since compgen -W can't handle spaces or new lines in
+  # operands
+  query=( '--changelog --check --deps --explicit --file --foreign
--groups --info
+           --list --owns --quiet --search --unrequired --upgrades' 'c
e g i k l m o p q s t u' )

-    arglen=$(( ${#toparse}-1 ))
-    for c in $(/bin/seq 0 "${arglen}"); do
-      arg=${toparse:$c:1}
-      [ "${arg}" != "-" ] && mod="${mod}${arg}"
-    done
-  done
+  remove=( '--cascade --dbonly --nodeps --nosave --recursive
--unneeded' 'c k n s u' )

-  cur=${COMP_WORDS[COMP_CWORD]}
+  sync=( '--asdeps --asexplicit --clean --downloadonly --force
--groups --ignore --ignoregroup
+          --info --list --needed --nodeps --print-uris --quiet
--refresh --search --sysupgrade'
+         'c f g i l p q s u w y' )

-  if [ $COMP_CWORD -eq 1 ] && [[ "$cur" == -* ]]; then
-    COMPREPLY=( $( compgen -W '\
-      -h --help \
-      -Q --query \
-      -R --remove \
-      -S --sync \
-      -U --upgrade \
-      -V --version \
-      ' -- $cur ) )
-    rem_selected
-    return 0
-  fi
+  upgrade=( '--asdeps --asexplicit --force --nodeps' f )

-  if [[ "$cur" == -* ]]; then
-    case "${op}" in
-      U)
-        COMPREPLY=( $( compgen -W '\
-          --asdeps \
-          --asexplicit \
-          -d --nodeps \
-          -f --force \
-          -h --help \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      R)
-        COMPREPLY=( $( compgen -W '\
-          -c --cascade \
-          -d --nodeps \
-          -h --help \
-          -k --dbonly \
-          -n --nosave \
-          -s --recursive \
-          -u --unneeded \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      S)
-        COMPREPLY=( $( compgen -W '\
-          --asdeps \
-          --asexplicit \
-          -c --clean \
-          -d --nodeps \
-          -f --force \
-          -g --groups \
-          -h --help \
-          -i --info \
-          -l --list \
-          -p --print-uris \
-          -s --search \
-          -u --sysupgrade \
-          -w --downloadonly \
-          -y --refresh \
-          --needed \
-          --ignore \
-          --ignoregroup \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      Q)
-        COMPREPLY=( $( compgen -W '\
-          -c --changelog \
-          -d --deps \
-          -e --explicit \
-          -g --groups \
-          -h --help \
-          -i --info \
-          -k --check \
-          -l --list \
-          -m --foreign \
-          -o --owns \
-          -p --file \
-          -s --search \
-          -t --unrequired \
-          -u --upgrades \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-    esac
-    rem_selected
+  common=( '--cachedir --config --dbpath --debug --help --logfile --noconfirm
+            --noprogressbar --noscriptlet --root --verbose' 'b d h r v' )
+
+  core=( --query --remove --sync --upgrade --version -Q -R -S -U -V )
+
+  if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then
+    COMPREPLY=($(compgen -W "$(echo "${core[@]}") '--help' '-h'" -- "$cur"))
+    return 0
+  elif [[ $prev == @(-*([^-
])[br]*|--@(config|logfile|root|dbpath|cachedir)) ]]; then
+    _arch_rem_selected
+    return 0
   else
-    case "${op}" in
-      U)
-        COMPREPLY=( $( compgen -d -- "$cur" ) \
-                    $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) )
+  # Main option check
+    for m in 'Q query' 'R remove' 'S sync' 'U upgrade' 'null'; do
+      if [[ $m == null ]]; then
+      # Go back to core if null
+        COMPREPLY=($(compgen -W "$(echo "${core[@]}")" -- "$cur"))
         return 0
-        ;;
-      h|V)
-        COMPREPLY=''
-        return 0
-        ;;
-      Q)
-        if _instring $mod g; then
-          _installed_groups
-        elif _instring $mod o; then
-          COMPREPLY=( $( compgen -d -- "$cur" ) \
-                      $( compgen -f -- "$cur" ) )
-        elif _instring $mod p; then
-          COMPREPLY=( $( compgen -d -- "$cur" ) \
-                      $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) )
-        elif _instring $mod u; then
-          COMPREPLY=''
+      fi
+      # Break on match
+      [[ $COMP_LINE == @(* -*([^- ])${m% *}*|* --${m#* } *) ]] && break
+    done
+  fi
+
+  # See if $cur is an operand before calling _pacman_pkg
+  case $cur in
+
+    # Check if it's a combination option first
+    -"${m% *}"*)
+      # Generate the list from the option arrays. eval is being using on
+      # possible matches between query, remove, sync, upgrade and common
+      # arrays; completely sanitized input
+      eval "list=($(command sed 's,.*,\${&[1]},' <<<"${m#* }"; IFS='
'; echo ${common[1]}))"
+      # Parse previous arguments
+      for (( w=0; w<${#COMP_WORDS[@]}; w++)); do
+        if [[ ${COMP_WORDS[w]} == -*([^-])[hV]* ]]; then
           return 0
-        else
-          _installed_pkgs
         fi
-        return 0
+        for o in "${!list[@]}"; do
+          if [[ ${COMP_WORDS[w]} == -*([^-])*${list[o]}* ]]; then
+            unset 'list[o]'
+          fi
+        done
+      done
+      # Allow special double letters
+      case ${m% *} in
+        Q)  [[ $cur == *([^i])i*([^i]) ]] && s+=(i) ;;
+        R)  [[ $cur == *([^s])s*([^s]) ]] && s+=(s) ;;
+        S)  [[ $cur == *([^c])c*([^c]) ]] && s+=(c)
+            [[ $cur == *([^u])u*([^u]) ]] && s+=(u)
+            [[ $cur == *([^y])y*([^y]) ]] && s+=(y) ;;
+      esac
+      list=("${list[@]/#/$cur}" "${s[@]/#/$cur}")
+    ;;
+
+    # Use long array instead
+    -*)
+      eval "list=($(command sed 's,.*,\${&[0]},' <<<"${m#* }"; IFS='
'; echo ${common[0]}))"
+      for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do
+        if [[ ${COMP_WORDS[w]} == --@(help|version) ]]; then
+          return 0
+        fi
+        for o in "${!list[@]}"; do
+          if [[ ${COMP_WORDS[w]} == ${list[o]} ]]; then
+            unset 'list[o]'; break
+          fi
+        done
+      done
+    ;;
+
+    # The word isn't an operand; use pacman or _filedir
+    *)
+      case ${m% *} in
+        # The weak extglobs are due to a previous match already
+        # confirming validity
+        Q)
+          case $COMP_LINE in
+            @(* -*([^- ])g*|* --groups *))   _pacman_pkg Qgq sort    ;;
+            @(* -*([^- ])o*|* --owns *))                             ;;
+            @(* -*([^- ])p*|* --file *))     _filedir 'pkg.tar.gz'   ;;
+            @(* -*([^- ])u*|* --upgrades *)) _pacman_pkg Quq         ;;
+            *)                               _pacman_pkg Qq          ;;
+          esac
         ;;
-      R)
-        _installed_pkgs
-        return 0
+        R)
+          _pacman_pkg Qq
         ;;
-      S)
-        if _instring $mod l; then
-          _available_repos
-        else
-          _available_pkgs
-        fi
-        return 0
+        S)
+          case $COMP_LINE in
+            @(* -*([^- ])l*|* --list *))   _pacman_pkg Sl sort ;;
+            @(* -*([^- ])g*|* --groups *)) _pacman_pkg Sg      ;;
+            *)                             _pacman_pkg Slq     ;;
+          esac
         ;;
-    esac
-  fi
-
-  rem_selected
+        U)
+          _filedir 'pkg.tar.gz'
+        ;;
+      esac
+      _arch_rem_selected
+      return 0
+    ;;
+  esac
+  COMPREPLY=($(compgen -W "$(echo "${list[@]}")" -- "$cur"))
 }
-complete -o filenames -F _pacman pacman
+
+complete -F _makepkg -o default -o filenames -o plusdirs makepkg
+complete -F _pacman  -o default -o filenames -o plusdirs pacman
-- 
1.6.6


More information about the pacman-dev mailing list