├── @ ├── README ├── externals ├── extract ├── github ├── gnome-wallpaper ├── hello-world └── replicate └── extra └── bash_completion /@: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # @, aka monkey-tail 3 | # Licenced under the GNU GPLv2 4 | 5 | ########################################################################### 6 | # Globals 7 | 8 | SELF=$( basename $0 ) 9 | SELF_LONG=$0 10 | SELF_VERSION="0.01" 11 | SELF_AUTHOR="Lucas Martin-King" 12 | 13 | ########################################################################### 14 | # Embedded help 15 | 16 | # |start||start a program detached from a terminal| 17 | # |run-once||only start a program if it isn't running| 18 | 19 | # |config|[ []]|manage embedded settings| 20 | # |list-variables||list all embedded settings| 21 | # |set-variable| |set variable to new value| 22 | # |get-variable||get variable value| 23 | 24 | # |list-externals||list the names of all embedded externals| 25 | # |external-add||add an external from origin| 26 | # |external-update| []|update an external's code| 27 | # |external-remove||remove external's code, keeping header| 28 | # |external-append| |append code to external| 29 | # |external-code||output external's code, without headers| 30 | # |external-extract||output external's code, including headers| 31 | 32 | # |list-requirements||list required system commands| 33 | # |check-requirements||check if system has required commands| 34 | 35 | # |disk-usage||show disk usage| 36 | # |disk-hogs||show top 10 disk hogs| 37 | # |disk-free||show free space| 38 | # |mem-used||show used memory| 39 | # |mem-free||show free memory| 40 | # |mem-total||show total memory| 41 | # |proc||show process info| 42 | # |proc-tree||show process tree| 43 | # |proc-files||show process files| 44 | 45 | ########################################################################### 46 | # Embedded settings 47 | 48 | # :proc.fields=uname,tty=TTY,pid,pcpu,pmem,rss,comm: 49 | # :disk.hogs.number=10: 50 | 51 | ########################################################################### 52 | # Subcommands 53 | 54 | _start () { 55 | require nohup 56 | [[ -z "$1" ]] && return 57 | ( nohup $* > /dev/null 2>&1 & ) > /dev/null 2>&1 58 | } 59 | 60 | _run-once () { 61 | require pidof 62 | pidof $1 || _start $* 63 | } 64 | 65 | _disk-usage () { 66 | require du 67 | local dir="." 68 | [[ ! -z "$1" ]] && dir=$1 69 | du -chs $1 | tail -n 1 70 | } 71 | 72 | _disk-hogs () { 73 | require du sort head cut printf xargs 74 | IFS="$(printf '\n\t')" 75 | if [[ ! -z "$1" ]] ; then 76 | [[ ! -d $1 ]] && exit 1 77 | [[ -d $1 ]] && cd $1 78 | fi 79 | dirs="$( ls -A . )" 80 | du -sk $dirs | sort -nr | head -n $( _get-variable disk.hogs.number ) | cut -f 2 | 81 | xargs -i% du --max-depth=0 -h "%" 82 | } 83 | 84 | _disk-free () { 85 | require df 86 | local dir="." 87 | [[ ! -z "$1" ]] && dir=$1 88 | df -h $dir 89 | } 90 | 91 | _mem-free () { 92 | require free 93 | free -m | sed -n 3p | awk '{ print $4"M" }' 94 | } 95 | 96 | _mem-used () { 97 | require free 98 | free -m | sed -n 3p | awk '{ print $3"M" }' 99 | } 100 | 101 | _mem-total () { 102 | require free 103 | free -m | awk '/Mem:/ { print $2"M" }' 104 | } 105 | 106 | _proc () { 107 | require ps 108 | local opt="-C $1" 109 | local fields=$( _get-variable proc.fields ) 110 | is_numeric $1 && opt="-p $1" 111 | [[ -z "$1" ]] && opt="-U $USER" 112 | ps -H -o $fields $opt 113 | } 114 | 115 | _proc-tree () { 116 | require pstree pidof 117 | for pid in $(pidof $1) ; do 118 | pstree -l $pid 119 | done 120 | } 121 | 122 | _proc-files () { 123 | require lsof 124 | local opt="-c $1" 125 | is_numeric $1 && opt="-p $1" 126 | lsof -f $opt | grep -v "/lib\|/usr/lib\|/usr/local/lib\| socket\| pipe\| anon_inode" 127 | } 128 | 129 | ########################################################################### 130 | # Helper commands 131 | 132 | _help () { 133 | echo "Usage: $SELF " 134 | echo " or: $SELF list-commands" 135 | echo " or: $SELF help-on " 136 | exit 1 137 | } 138 | 139 | _version () { 140 | echo "$SELF, version $SELF_VERSION, by $SELF_AUTHOR" 141 | exit 0 142 | } 143 | 144 | _list-commands () { 145 | declare -F | cut -d ' ' -f 3 | grep "^_.*" | sed 's/^_//' | column 146 | } 147 | 148 | _help-on () { 149 | local cmd=$1 150 | grep "^# |$cmd|" $SELF_LONG | sed -e 's/# |//' -e 's/|$//' -e 's/|/\t/g' 151 | } 152 | 153 | _get-variable () { 154 | local var=$1 155 | grep "^# :$var=.*:$" $SELF_LONG | sed -e 's/# ://' -e 's/:$//' | cut -d '=' -f 2- | head -n 1 156 | } 157 | 158 | _set-variable () { 159 | local var val oldval file 160 | [[ ! -w $SELF_LONG ]] && die "$SELF is read only" 161 | var=$1 162 | val=$2 163 | var_exists $var || die "$var doesn't exist" 164 | oldval=$( _get-variable $var ) 165 | 166 | [[ "$oldval" == "$val" ]] && exit 0 167 | 168 | [[ -z "$val" ]] && warn "new value is empty" 169 | 170 | echo "var: $var old: $oldval new: $val" 171 | 172 | sed -i.bak -e "s/# :$var=$oldval:/# :$var=$val:/" $SELF_REAL 173 | } 174 | 175 | _list-variables () { 176 | grep "^# :.*=.*:$" $SELF_LONG | sed -e 's/# ://' -e 's/:$//' | uniq 177 | } 178 | 179 | _config () { 180 | if [[ -z "$1" ]] ; then 181 | _list-variables 182 | fi 183 | if [[ ! -z "$1" ]] && [[ ! -z "$2" ]] ; then 184 | _set-variable ${1} ${2} 185 | fi 186 | _get-variable ${1} 187 | } 188 | 189 | _list-externals () { 190 | grep -n "^# {begin:.*:.*}" $SELF_LONG | cut -d ":" -f 3 191 | } 192 | 193 | _external () { 194 | local begins ends origin loc 195 | origin=$( ext_origin $1 ) 196 | begins=$( ext_begins $1 ) 197 | ends=$( ext_ends $1 ) 198 | [[ "$begins" == "" || "$ends" == "" ]] && return 1 199 | loc=":" 200 | [[ $begins -gt $ends ]] && begins=$ends && loc="|" 201 | echo "${begins}${loc}${ends} ${origin}" 202 | } 203 | 204 | _external-code () { 205 | ext_code $1 206 | } 207 | 208 | _external-extract () { 209 | ext_header $1 $( ext_origin $1 ) 210 | ext_code $1 211 | ext_footer $1 212 | } 213 | 214 | _external-remove () { 215 | local starts ends file 216 | 217 | starts=$( ext_begins $1 ) 218 | ends=$( ext_ends $1 ) 219 | 220 | [[ -z "$starts" || -z "$ends" ]] && die "Cannot find start or end of $1" 221 | is_numeric $starts || die "something is not right" 222 | is_numeric $ends || die "something is not right" 223 | [[ $ends -lt $starts ]] && return 1 224 | 225 | echo "Removing code of '$1' from $SELF_REAL" 226 | sed -i.bak -e "${starts},${ends}d" $SELF_REAL 227 | } 228 | 229 | _external-append () { 230 | local line=$( ext_ends $1 ) 231 | local file=$2 232 | echo "Appending code from $file to $SELF_REAL" 233 | [[ -f "$file" && "$line" != "" ]] && ext_insert "$file" $line 234 | } 235 | 236 | _external-add () { 237 | local origin name tmpfile offset 238 | origin=$1 239 | [[ -f $origin ]] && origin=$( readlink -f $origin ) 240 | name=$( basename $origin ) 241 | 242 | echo $name 243 | 244 | ext_begins $name > /dev/null && die "An external already exists by that name" 245 | ext_ends $name > /dev/null && die "An external already exists by that name" 246 | 247 | echo "..." 248 | 249 | tmpfile=/tmp/$SELF.$$.ext.add.tmp 250 | 251 | echo $tmpfile 252 | 253 | echo > $tmpfile 254 | ext_header $name $origin >> $tmpfile 255 | ext_footer $name >> $tmpfile 256 | offset=$( grep -n "^# {end:.*}$" $SELF_LONG | tail -n 1 | cut -d ":" -f 1 ) 257 | if [[ -z "$offset" ]] ; then 258 | offset=$( wc -l $SELF_LONG | cut -d ' ' -f 1 ) 259 | let "offset = $offset - 5" 260 | fi 261 | ext_insert $tmpfile $offset && _external-update $name 262 | } 263 | 264 | _external-update () { 265 | local ext=$1 266 | local origin 267 | 268 | if ext_begins $ext > /dev/null && ext_ends $ext > /dev/null ; then 269 | origin=$( ext_origin $ext ) 270 | [[ ! -z "$2" ]] && origin=$2 271 | [[ -z "$origin" ]] && die "Origin not set" 272 | 273 | echo "Using $origin as origin" 274 | 275 | local ofile 276 | 277 | if echo $origin | grep -q '\(http\(s\?\)\|ftp\)://' ; then 278 | ofile=/tmp/$SELF.$$.ext.tmp 279 | echo "Fetching from $origin, saving to $ofile" 280 | wget --no-verbose -nc --progress=dot -O $ofile $origin || die "Could not fetch properly" 281 | else 282 | ofile=$origin 283 | fi 284 | 285 | if [[ -f "$ofile" ]] ; then 286 | sed '1p' $ofile | grep -q "^# @$" || warn "$ofile doesn't have '# @' on the first line, is it an external?" 287 | else 288 | die "Cannot load $origin" 289 | fi 290 | 291 | local backup=/tmp/$SELF.$$.tmp 292 | cp $SELF_REAL $backup && echo "Backup of $SELF_REAL is at $backup" 293 | 294 | _external-remove $ext 295 | _external-append $ext $ofile 296 | else 297 | die "Cannot update '$ext' because I can't find it" 298 | fi 299 | } 300 | 301 | _list-requirements () { 302 | require uniq 303 | local reqs=$(grep '\srequire \(\w*\)\(\(\s*\)\(\w*\)\)*' $SELF | sed -e 's/require//g' -e 's/ /\n/g' | sort | uniq) 304 | echo $reqs 305 | } 306 | 307 | _check-requirements () { 308 | local reqs=$(_list-requirements) 309 | 310 | for req in ${reqs} ; do 311 | eval "require $req" 312 | done 313 | } 314 | 315 | ########################################################################### 316 | # Helper functions 317 | 318 | catch_all () { 319 | case "$1" in 320 | '-v' | '-version' | '--version') _version ;; 321 | '-h' | '-help' | '--help') _help ;; 322 | esac 323 | 324 | [[ ! -z "$1" ]] && echo "Command '$1' does not exist" 325 | 326 | _help 327 | } 328 | 329 | fn_exists () { 330 | type $1 2> /dev/null | grep -q 'function' 331 | } 332 | 333 | is_numeric () { 334 | echo "$@" | grep -q -v "[^0-9]" 335 | } 336 | 337 | var_exists () { 338 | local var=$1 339 | grep -q "^# :$var=.*:$" $SELF_LONG 340 | } 341 | 342 | die () { 343 | echo "error: $1" 1>&2 && exit 1 344 | } 345 | 346 | warn () { 347 | echo "warning: $1" 1>&2 348 | } 349 | 350 | file_type () { 351 | [[ ! -f "$1" ]] && return 1 352 | file -b "$1" | cut -d "," -f 1 353 | } 354 | 355 | file_mime () { 356 | [[ ! -f "$1" ]] && return 1 357 | file -b -i "$1" | cut -d ";" -f 1 358 | } 359 | 360 | require () { 361 | for cmd in $@ ; do 362 | if [[ ! "$( command -v "$cmd" )" ]] ; then 363 | die "require $cmd" 364 | fi 365 | done 366 | } 367 | 368 | main () { 369 | require sed awk grep cut head tail readlink file 370 | 371 | SELF_REAL=$( readlink -f $SELF_LONG ) 372 | [[ -z "$SELF_REAL" ]] && SELF_REAL=$SELF_LONG 373 | 374 | if fn_exists _$(basename $0) ; then 375 | CMD=$(basename $0) 376 | elif fn_exists _$1 ; then 377 | CMD=$1 378 | shift 379 | else 380 | catch_all $1 381 | fi 382 | 383 | _$CMD "$@" 384 | } 385 | 386 | ext_begins () { 387 | local ln=$( grep -n "# {begin:$1:.*}" $SELF_LONG | cut -d ':' -f 1 ) 388 | [[ -z "$ln" ]] && return 1 389 | expr $ln + 1 390 | } 391 | 392 | ext_ends () { 393 | local ln=$( grep -n "# {end:$1}" $SELF_LONG | cut -d ':' -f 1 ) 394 | [[ -z "$ln" ]] && return 1 395 | expr $ln - 1 396 | } 397 | 398 | ext_origin () { 399 | sed -n 's/# {begin:'${1}':\(.*\)}/\1/p' $SELF_LONG 400 | } 401 | 402 | ext_code () { 403 | local start end 404 | start=$( ext_begins $1 ) 405 | end=$( ext_ends $1 ) 406 | [[ -z "$start" || -z "$end" ]] && die "Cannot find start or end of $1" 407 | [[ $end -lt $start ]] && return 1 408 | sed -n "${start},${end}p" $SELF_LONG 409 | } 410 | 411 | ext_insert () { 412 | local file start 413 | file=$1 414 | start=$2 415 | sed -i.bak -e "${start} r ${file}" $SELF_REAL 416 | } 417 | 418 | ext_header () { 419 | echo "# {begin:$1:$2}" 420 | } 421 | 422 | ext_footer () { 423 | echo "# {end:$1}" 424 | } 425 | 426 | ########################################################################### 427 | # External commands 428 | 429 | ########################################################################### 430 | # Program 431 | 432 | main "$@" 433 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | README for @, also known as monkey-tail 3 | ------------------------------------------------------------------------------- 4 | 5 | What? 6 | ----- 7 | 8 | @ is a swiss army knife for the *Nix user, and is self-contained and portable. 9 | 10 | 11 | Why? 12 | ---- 13 | 14 | Well, I feel that it's nice to have a collection of simple commands in a 15 | separate utility, rather than "polluting" the shell namespace. 16 | 17 | You can hang @ on your belt, along with your shell, editor and your C compiler. 18 | 19 | 20 | How? 21 | ---- 22 | 23 | Copy @ into your usual directory for scripts(make sure it is in your $PATH), etc 24 | 25 | Test if @ can run: 26 | $ @ 27 | 28 | If everything is ok, you should see: 29 | $ @ 30 | Usage: @ 31 | or: @ list-commands 32 | or: @ help-on 33 | 34 | Otherwise, if you see this: 35 | $ @ 36 | bash: @: command not found 37 | 38 | You need to add your scripts directory to your $PATH (via your .bashrc or .bash_profile). Assuming it's $HOME/tools: 39 | export PATH=$PATH:$HOME/tools: 40 | 41 | Now that everything is set, explore. 42 | 43 | Simply run: 44 | 45 | @ list-commands 46 | 47 | For example, to show disk usage of the current directory: 48 | 49 | @ disk-usage 50 | 51 | Or, find the disk hogs in your downloads directory: 52 | 53 | @ disk-hogs ~/Downloads 54 | 55 | 56 | Externals 57 | --------- 58 | 59 | @ can merge pieces of code into itself, to provide extra commands, etc. 60 | Additionally, "externals" can then be updated from their source. 61 | 62 | For example: 63 | 64 | @ external-add https://github.com/lmartinking/monkey-tail/raw/master/externals/hello-world 65 | 66 | Will import the "hello-world" external, and all commands contained within will 67 | be available. For instance: 68 | 69 | @ hello-world 70 | 71 | To remove "hello-world", simply use: 72 | 73 | @ external-remove hello-world 74 | 75 | If you have modified an external which is embedded into @, you can extract 76 | the code using: 77 | 78 | @ external-code 79 | 80 | You can append code to an external using: 81 | 82 | @ external-append 83 | 84 | 85 | Creating Your Own External 86 | -------------------------- 87 | 88 | An external is simply a kind of bash script with "# @" on the first line 89 | 90 | Each command in @ is a bash function, prefixed with _ (underscore). For example: 91 | 92 | # @ 93 | _my-command () { 94 | echo "Hello there!" 95 | } 96 | 97 | Once merged into @ (using external-add ), can be accessed by: 98 | 99 | @ my-command 100 | 101 | @ provides a few built in functions, and eventually there will be more. 102 | Please do take a look at the source code! 103 | 104 | For more examples of externals, take a look at: 105 | 106 | < http://github.com/lmartinking/monkey-tail/tree/master/externals > 107 | 108 | 109 | By Who? 110 | ------- 111 | 112 | @ is written by Lucas Martin-King and licenced under the GPLv2 113 | -------------------------------------------------------------------------------- /externals/extract: -------------------------------------------------------------------------------- 1 | # @ 2 | # Easily extract files from a number of formats 3 | 4 | _extract () { 5 | local file ftype fname 6 | 7 | for file in "$@" ; do 8 | [ ! -f "$file" ] && warn "$file is not a file" && continue 9 | 10 | ftype=$( file_mime "$file" ) 11 | fname="${file%%.*}" 12 | 13 | case "$ftype" in 14 | application/zip) require unzip && unzip "$file" ;; 15 | application/x-bzip2) _extract-bz2 "$file" ;; 16 | application/x-gzip) _extract-gz "$file" ;; 17 | application/rar) require unrar && unrar x "$file" ;; 18 | application/x-7z-compressed) require 7z && 7z x "$file" ;; 19 | application/octet-stream) 20 | local ft="$( file_type "$file" )" 21 | case "$ft" in 22 | "Debian binary package"*) _extract-deb "$file" ;; 23 | *"cpio archive"*) _extract-cpio "$file" ;; 24 | esac 25 | ;; 26 | *) warn "don't know how to extract '$file' ($ftype)" ;; 27 | esac 28 | done 29 | } 30 | 31 | _extract-cpio () { 32 | require cpio 33 | local file odir bname 34 | file="$( readlink -m $1 )" 35 | bname=$( basename "$file" ) 36 | odir=$bname 37 | 38 | echo $file 39 | 40 | [[ ! -f "$file" ]] && return 1 41 | [[ -f $odir ]] && odir="${bname}.tmp" 42 | [[ ! -d "$odir" ]] && mkdir "$odir" && cd "$odir" && cpio -i < "$file" 43 | } 44 | 45 | _extract-deb () { 46 | require ar tar 47 | local file fname bname 48 | file=$1 49 | bname=$( basename "$file" ) 50 | fname=${bname%.*} 51 | 52 | [ ! -f "$file" ] && return 1 53 | 54 | mkdir "$fname" 55 | 56 | for part in control data ; do 57 | mkdir "$fname/$part" 58 | ar p "$file" "${part}.tar.gz" | tar xzv -C "$fname/$part" 59 | done 60 | } 61 | 62 | _extract-bz2 () { 63 | require bzip2 tar 64 | local file fname 65 | file=$1 66 | fname=${file%.*} 67 | 68 | [ ! -f "$file" ] && return 1 69 | 70 | case "$file" in 71 | *.tar.bz2|*.tbz2) tar -xjvf "$file" ;; 72 | *) bzip2 -dc "$file" > $fname && _extract "$fname" ;; 73 | esac 74 | } 75 | 76 | _extract-gz () { 77 | require tar gzip 78 | local file bname fname tmpfile 79 | file=$1 80 | bname=$( basename "$file" ) 81 | fname=${bname%.*} 82 | 83 | case "$file" in 84 | *.tar.gz|*.tgz) tar -xzvf "$file" ;; 85 | *) [[ ! -f "$fname" ]] && gzip -S "" -dc "$file" > "$fname" && _extract "$fname" ;; 86 | esac 87 | } 88 | -------------------------------------------------------------------------------- /externals/github: -------------------------------------------------------------------------------- 1 | # @ 2 | # An external to make github setups easier 3 | 4 | # |github-setup||set up git repo for github| 5 | # |github-remote| |set up git repo with github as remote| 6 | # |github-clone|/|clone user's repository from github| 7 | # |git-enable-color||enable color git for or global for globally| 8 | 9 | # :git.user.name=--------: 10 | # :git.user.email=----@------: 11 | # :git.github.uri=git@github.com: 12 | # :git.github.username=--------: 13 | # :git.github.token=--------------------------------: 14 | 15 | _github-setup () { 16 | require git 17 | [[ ! -z "$1" ]] && cd $1 18 | for var in github.username github.token user.name user.email ; do 19 | echo git config $var $( _get-variable git.$var ) 20 | done 21 | } 22 | 23 | _github-remote () { 24 | require git 25 | [[ -d "$1" ]] && cd "$1" && shift 26 | local user=$1 27 | local repo=$2 28 | [[ -z "$user" ]] && die "User not specified" 29 | [[ -z "$repo" ]] && die "Repository not specified" 30 | git remote add origin $( _get-variable git.github.uri ):$user:$repo.git 31 | } 32 | 33 | _github-clone () { 34 | require git 35 | local url="git://github.com/${1}.git" 36 | git clone $url 37 | } 38 | 39 | _git-enable-color () { 40 | require git 41 | local flag 42 | if [[ "$1" == "global" ]] ; then 43 | flag="--global" 44 | else 45 | [[ -d "$1" ]] && cd $1 46 | [[ ! -d "./.git" ]] && die "'$( pwd )' is not a git repository" 47 | fi 48 | for type in diff status branch ; do 49 | git config $flag "color.${type}" auto 50 | done 51 | } 52 | -------------------------------------------------------------------------------- /externals/gnome-wallpaper: -------------------------------------------------------------------------------- 1 | # @ 2 | # Get and set the gnome wallpaper 3 | 4 | # |gnome-wallpaper-set||set the wallpaper to file| 5 | function _gnome-wallpaper-set () { 6 | require gconftool-2 7 | local file="$1" 8 | [[ -z "$file" ]] && die "filename not specified" 9 | [[ -f $file ]] || die "$file is not a file" 10 | gconftool-2 -t str --set /desktop/gnome/background/picture_filename "$1" 11 | } 12 | 13 | # |gnome-wallpaper-get||get the currently set wallpaper| 14 | function _gnome-wallpaper-get () { 15 | require gconftool-2 16 | gconftool-2 --get /desktop/gnome/background/picture_filename 17 | } 18 | -------------------------------------------------------------------------------- /externals/hello-world: -------------------------------------------------------------------------------- 1 | # @ 2 | # |hello-world||a simple command which does nothing| 3 | _hello-world () { 4 | echo "Hello, World" 5 | } 6 | -------------------------------------------------------------------------------- /externals/replicate: -------------------------------------------------------------------------------- 1 | # @ 2 | # An external to copy @ to a remote host via SSH 3 | 4 | # |replicate||replicate @ to remote host| 5 | # :replicate.path=~/bin: 6 | _replicate () { 7 | require ssh scp 8 | local ssh_login="$1" 9 | local install_dir=$( _get-variable replicate.path ) 10 | echo "Replicating $SELF_LONG to $ssh_login:$install_dir" 11 | ssh $ssh_login "/bin/bash -l -c '[[ ! -d $install_dir ]] && mkdir $install_dir'" 12 | scp -C $SELF_LONG $ssh_login:$install_dir || die "Could not replicate" 13 | } 14 | -------------------------------------------------------------------------------- /extra/bash_completion: -------------------------------------------------------------------------------- 1 | # bash completion for @ 2 | # very basic at the moment 3 | 4 | #have @ && 5 | _monkey-tail() 6 | { 7 | local cmds cur prev 8 | cmds=$( @ list-commands | tr '\n' ' ' ) 9 | 10 | COMPREPLY=() 11 | 12 | cur=${COMP_WORDS[COMP_CWORD]} 13 | prev=${COMP_WORDS[COMP_CWORD-1]} 14 | 15 | if [[ "$prev" == "@" ]]; then 16 | COMPREPLY=( $( compgen -W "$cmds" -- "$cur" ) ) 17 | else 18 | case "${prev}" in 19 | help-on) 20 | COMPREPLY=( $( compgen -W "$cmds" -- "$cur" ) ) 21 | ;; 22 | esac 23 | 24 | _filedir 25 | fi 26 | } && 27 | complete -F _monkey-tail -o filenames @ 28 | --------------------------------------------------------------------------------