Numbered options for bash completion - bash

Can we get numbered completion options with bash when we have started typing part of the option? For example with ksh I am able to get a numbered list of options and then when I press 3 followed by 3 I get the 3rd option completed.
> cat local.
1) local.cshrc
2) local.login
3) local.profile
> cat local.profile
Is this kind of completion possible using bash programmable completion?
With something like this I am able to give numbers for all available options, but this breaks when the user starts to type the option and presses tab so a fallback to normal options without numbers has to be used.
(( COMP_CWORD > 2 )) && return #This stops completion at third word for z oe
if [[ "${COMP_WORDS[2]}" = "" || $( echo "${COMP_WORDS[2]}" | perl -ne 'use Scalar::Util qw(looks_like_number); if (looks_like_number($_)){print 1}else{print 0}' ) -eq 1 ]]; then
#Show all instances oratab + running
local suggestions=($( compgen -W "$( { z ot | grep -v ^$ | grep -v SIDs | grep -v : && z p1; } | perl -lpe 's/\s+$//' | sort | uniq | perl -ne 'print "$.) $_"' )" -- "${COMP_WORDS[2]}" ))
if [ "${#suggestions[#]}" == "1" ]; then #One match found
local cmd=$( echo "${suggestions[0]}" | perl -pe 's/[1-9]+\)[\s]+//' )
COMPREPLY=("$cmd ") #Space needed here to handle -o nospace
return
else
#More than one suggestions resolved, respond with the suggestions intact
COMPREPLY=("${suggestions[#]}")
return
fi
else
local suggestions=( $( compgen -W "$( { z ot | grep -v ^$ | grep -v SIDs | grep -v : && z p1; } | perl -lpe 's/\s+$//' | sort | uniq )" -- "${COMP_WORDS[2]}" ) )
if [ "${#suggestions[#]}" == "1" ]; then #One match found
local cmd="${suggestions[0]}"
COMPREPLY=("$cmd ") #Space needed here to handle -o nospace
return
else
#More than one suggestions resolved, respond with the suggestions intact
COMPREPLY=("${suggestions[#]}")
return
fi
fi
Is there way to do the ksh like completion number + tab with bash completion?

Related

Bash script says: "}" unexpected (expected "done") in function call in .xinitrc

I'm writing the .xinitrc for my system, but the code (below) crashes on line 17 (the ending brace of the `freespace' function) with the error
"}" unexpected (expected "done")
Is there actually a problem with the function declaration, or did I mess up somewhere else in the script?
while true; do
# WiFi connectivity status
if [ -f /sbin/iwgetid ]; then
result="$(/sbin/iwgetid -r )";
if [ -z "$result" ]; then
result="Not connected";
fi
else
result="Not installed";
fi
wifi_status="WiFi: $result";
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
# Miscellaneous
user_str="$(whoami)#$(hostname)";
date_str="$(date +"%a %b %d %R")";
# Battery status and CPU temperature
if [ -f /usr/bin/acpi ]; then
battery_status="Battery: $(acpi -b '{split($0, a, ": "); print a[2]'})";
cpu_temp="CPU temperature: $(acpi -t -f | sed 's/Thermal 0: ok, //g')";
acpi_status="$battery_status | $cpu_temp";
else
acpi_status="ACPI not available";
fi
# System performance
uptime_str=$(uptime -p);
num_cpus="CPUs: $(grep "processor" /proc/cpuinfo | wc -l)";
num_procs="$(ps -e | wc -l) active processes";
load_avgs="Load averages: $(cat /proc/loadavg | awk '{split($0, a, " "); print a[1], a[2], a[3]}')";
# Update top and bottom status bar
top_bar="$wifi_status | $uptime_str | $user_str | $date_str";
bottom_bar="$acpi_status | $num_cpus | $num_procs | $load_avgs | $root_free | $home_free";
xsetroot -name "$top_bar;$bottom_bar";
sleep 30;
done &
xbindkeys
( ( sleep 5 && /usr/bin/xscreensaver -no-splash -display :0.0 ) & )
( ( create-random-gradient && feh --bg-center ~/.wallpaper.png ) & )
( redshifter & )
wmname LG3D
exec /home/ma/build/dwm/dwm
If I slim down the code to just that section, i.e.
while true; do
# Free space on / and /home
function freespace {
local space="$(df -lh --output=avail $1 | tail -n 1 | sed -e 's/^[ \t]*//') free on $1";
echo "$space";
}
root_free=$(freespace /);
home_free=$(freespace /home);
echo "$root_free | $home_free";
sleep 2
done
and run it from the terminal directly (instead of calling startx after logging in), it works just fine.
This error will happen if the shell running this code is not actually bash. It's noteworthy that your script doesn't include a shebang -- without #!/usr/bin/env bash or another shebang, the interpreter used is unspecified. (Explicitly specifying sh yourscript will override the shebang and force sh, just as bash yourscript will ignore the shebang and use bash, but that's neither here nor there).
function is not a keyword in standard POSIX sh -- it's a ksh extension which bash adopted. Consequently, to a POSIX shell, the { in function freespace { is not syntax -- it's an argument to be passed to a command named function.
By contrast, the } is parsed as syntax, but it doesn't match with any syntactic opening brace; hence, your error.
In standards-compliant syntax, the declaration would be:
freespace() {
df -lh --output=avail "$1" | awk -v fs="$1" 'NR == 2 { print $1 " free on " fs }'
}
Note the elimination of the unnecessary command substitution. echo "$(...)" is self-defeating: $() goes to significant overhead to capture stdout of its command into a string, and then echo emits that back to stdout again. Why do any of that when you could just run the command directly?

bash: sed: unexpected behavior: displays everything

I wrote what I thought was a quick script I could run on a bunch of machines. Instead it print what looks like might be directory contents in a recursive search:
version=$(mysql Varnish -B --skip-column-names -e "SELECT value FROM sys_param WHERE param='PatchLevel'" | sed -n 's/^.*\([0-9]\.[0-9]*\).*$/\1/p')
if [[ $(echo "if($version == 6.10) { print 1; } else { print 0; }" | bc) -eq 1 ]]; then
status=$(dpkg-query -l | awk '{print $2}' | grep 'sg-status-polling');
cons=$(dpkg-query -l | awk '{print $2}' | grep 'sg-consolidated-poller');
if [[ "$status" != "" && "$cons" != "" ]]; then
echo "about to change /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm"; echo;
cp /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm.bkup;
sed -ir '184s!\x91\x93!\x91\x27--timeout=35\x27\x93!' /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm;
sed -n 183,185p /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm; echo;
else
echo "packages not found. Assumed to be not applicable";
fi
else
echo "This is 4.$version, skipping";
fi
The script is supposed to make sure Varnish is version 4.6.10 and has 2 custom .deb packages installed (not through apt-get). then makes a backup and edits a single line in a perl module from [] to ['--timeout=35']
it looks like its tripping up on the sed replace one liner.
There are two major problems (minor ones addressed in comments). The first is that you use the decimal code for [] instead of the hexa, so you should use \x5b\x5d instead of \x91\x93. The second problem is that if you do use the proper codes, sed will still interpret those syntactically as []. So you can't escape escaping. Here's what you should call:
sed -ri'.bkup' '184s!\[\]![\x27--timeout=35\x27]!' /var/www/Varnish/lib/Extra/SG/ObjectPoller2.pm
And this will create the backup for you (but you should double check).

Dynamic Patch Counter for Shell Script

I am developing a script on a Solaris 10 SPARC machine to calculate how many patches got installed successfully during a patch delivery. I would like to display to the user:
(X) of 33 patches were successfully installed
I would like my script to output dynamically replacing the "X" so the user knows there is activity occurring; sort of like a counter. I am able to show counts, but only on a new line. How can I make the brackets update dynamically as the script performs its checks? Don't worry about the "pass/fail" ... I am mainly concerned with making my output update in the bracket.
for x in `cat ${PATCHLIST}`
do
if ( showrev -p $x | grep $x > /dev/null 2>&1 ); then
touch /tmp/patchcheck/* | echo "pass" >> /tmp/patchcheck/$x
wc /tmp/patchcheck/* | tail -1 | awk '{print $1}'
else
touch /tmp/patchcheck/* | echo "fail" >> /tmp/patchcheck/$x
wc /tmp/patchcheck/* | tail -1 | awk '{print $1}'
fi
done
The usual way to do that is to emit a \r carriage return (CR) at some point and to omit the \n newline or line feed (LF) at the end of the line. Since you're using awk, you can try:
awk '{printf "\r%s", $1} END {print ""}'
For most lines, it outputs a carriage return and the data in field 1 (without a newline at the end). At the end of the input, it prints an empty string followed by a newline.
One other possibility is that you should place the awk script outside your for loop:
for x in `cat ${PATCHLIST}`
do
if ( showrev -p $x | grep $x > /dev/null 2>&1 ); then
touch /tmp/patchcheck/* | echo "pass" >> /tmp/patchcheck/$x
wc /tmp/patchcheck/* | tail -1
else
touch /tmp/patchcheck/* | echo "fail" >> /tmp/patchcheck/$x
wc /tmp/patchcheck/* | tail -1
fi
done | awk '{ printf "\r%s", $1} END { print "" }'
I'm not sure but I think you can apply similar streamlining to the rest of the repetitious code in the script:
for x in `cat ${PATCHLIST}`
do
if showrev -p $x | grep -s $x
then echo "pass"
else echo "fail"
fi >> /tmp/patchcheck/$x
wc /tmp/patchcheck/* | tail -1
done | awk '{ printf "\r%s", $1} END { print "" }'
This eliminates the touch (which doesn't seem to do much), and especially not when the empty output of touch is piped to echo which ignores its standard input. It eliminates the sub-shell in the if line; it uses the -s option of grep to keep it quiet.
I'm still a bit dubious about the wc line. I think you're looking to count the number of files, in effect, since each file should contain one line (pass or fail), unless you listed some patch twice in the file identified by ${PATCHLIST}. In which case, I'd probably use:
for x in `cat ${PATCHLIST}`
do
if showrev -p $x | grep -s $x
then echo "pass"
else echo "fail"
fi >> /tmp/patchcheck/$x
ls /tmp/patchcheck | wc -l
done | awk '{ printf "\r%s", $1} END { print "" }'
This lists the files in /tmp/patchcheck and counts the number of lines output. It means you could simply print $0 in the awk script since $0 and $1 are the same. To the extent efficiency matters (not a lot), this is more efficient because ls only scans a directory, rather than having wc open each file. But it is more particularly a more accurate description of what you are trying to do. If you later want to count the passes, you can use:
for x in `cat ${PATCHLIST}`
do
if showrev -p $x | grep -s $x
then echo "pass"
else echo "fail"
fi >> /tmp/patchcheck/$x
grep '^pass$' /tmp/patchcheck/* | wc -l
done | awk '{ printf "\r%s", $1} END { print "" }'
Of course, this goes back to reading each file, but you're getting more refined information out of it now (and that's the penalty for the more refined information).
Here is how I got my patch installation script working the way I wanted:
while read pkgline
do
patchadd -d ${pkgline} >> /var/log/patch_install.log 2>&1
# Create audit file for progress indicator
for x in ${pkgline}
do
if ( showrev -p ${x} | grep -i ${x} > /dev/null 2>&1 ); then
echo "${x}" >> /tmp/pass
else
echo "${x}" >> /tmp/fail
fi
done
# Progress indicator
for y in `wc -l /tmp/pass | awk '{print $1}'`
do
printf "\r${y} out of `wc -l /patchdir/master | awk '{print $1}'` packages installed for `hostname`. Last patch installed: (${pkgline})"
done
done < /patchdir/master

bash script and greping with command line

new to bash scripting so just wondering if i am doing this code right at all. im trying to search /etc/passwd and then grep and print users.
usage ()
{
echo "usage: ./file.sk user"
}
# test if we have two arguments on the command line
if [ $# != 1 ]
then
usage
exit
fi
if [[ $# < 0 ]];then
usage
exit
fi
# Search for user
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
#check if there. if name is founf: print msg and line entry
not sure as how to this or if im doing this right...
am i doing this right?
grep $1 /etc/passwd | while IFS=: read -r username passwd uid gid info home shell
do
echo $username: $info
done
This might work for you:
fullname=$(awk -F: '/'$1'/{print $5}' /etc/passwd)
firstname=${fullname/ *}
You're on the right track.
But I think the 2nd if [[ $# < 0 ]] .... fi block doesn't get you much. Your first test case gets the situation right, 'This script requires 1 argument or quits'.
Also, I don't see what you need firstname for, so a basic test is
case "${fullname:--1}" in
-[1] ) printf "No userID found for input=$1\n" ; exit 1 ;;
* )
# assume it is OK
# do what every you want after this case block
;;
esac
You can of course, duplicate this using "${firstname}" if you really need the check.
OR as an equivalent if ... fi is
if [[ "${fullname}" == "" ]] ; then
printf "No userID found for input=$1\n" ; exit 1
fi
note to be more efficient, you can parse ${fullname} to get firstname without all the calls to grep etc, i.e.
firstname=${fullname%% *}
Let me know if you need for me to explain :--1} and %% *} variable modifiers.
I hope this helps.
Instead of this:
fullname=`grep $1 /etc/passwd | cut -f 5 -d :`
firstname=`grep $1 /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`
Try this:
fullname=$(cut -f5 -d: /etc/passwd | grep "$1")
if [[ $? -ne 0 ]]; then
# not found, do something
fi
firstname=${fullname%% *} # remove the space and everything after
Note that I changed my answer to cut before grep so that it doesn't get false positives if some other field matches the full name you are searching for.
You can simply by reading your input to an array and then printing out your desired fields, something like this -
grep $1 /etc/passwd | while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
Test:
jaypal:~/Temp] echo "root:*:0:0:System Administrator:/var/root:/bin/sh" |
while IFS=: read -a arry; do
echo ${arry[0]}:${arry[4]};
done
root:System Administrator

CVS branch name from tag name

I have a number of modules in CVS with different tags. How would I go about getting the name of the branch these tagged files exist on? I've tried checking out a file from the module using cvs co -r TAG and then doing cvs log but it appears to give me a list of all of the branches that the file exists on, rather than just a single branch name.
Also this needs to be an automated process, so I can't use web based tools like viewvc to gather this info.
I have the following Korn functions that you might be able to adjust to run in bash. It should be apparent what it's doing.
Use get_ver() to determine the version number for a file path and given tag. Then pass the file path and version number to get_branch_name(). The get_branch_name() function relies on a few other helpers to fetch information and slice up the version numbers.
get_ver()
{
typeset FILE_PATH=$1
typeset TAG=$2
TEMPINFO=/tmp/cvsinfo$$
/usr/local/bin/cvs rlog -r$TAG $FILE_PATH 1>$TEMPINFO 2>/dev/null
VER_LINE=`grep "^revision" $TEMPINFO | awk '{print $2}'`
echo ${VER_LINE:-NONE}
rm -Rf $TEMPINFO 2>/dev/null 1>&2
}
get_branch_name()
{
typeset FILE=$1
typeset VER=$2
BRANCH_TYPE=`is_branch $VER`
if [[ $BRANCH_TYPE = "BRANCH" ]]
then
BRANCH_ID=`get_branch_id $VER`
BRANCH_NAME=`get_tags $FILE $BRANCH_ID`
echo $BRANCH_NAME
else
echo $BRANCH_TYPE
fi
}
get_minor_ver()
{
typeset VER=$1
END=`echo $VER | sed 's/.*\.\([0-9]*\)/\1/g'`
echo $END
}
get_major_ver()
{
typeset VER=$1
START=`echo $VER | sed 's/\(.*\.\)[0-9]*/\1/g'`
echo $START
}
is_branch()
{
typeset VER=$1
# We can work out if something is branched by looking at the version number.
# If it has only two parts (i.e. 1.123) then it's on the trunk
# If it has more parts (i.e. 1.2.2.4) then it's on the branch
# We can error detect if it has an odd number of parts
POINTS=`echo $VER | tr -dc "." | wc -c | awk '{print $1}'`
PARTS=$(($POINTS + 1))
if [[ $PARTS -eq 2 ]]
then
print "TRUNK"
elif [[ $(($PARTS % 2)) -eq 0 ]]
then
print "BRANCH"
else
print "ERROR"
fi
}
get_branch_id()
{
typeset VER=$1
MAJOR_VER=`get_major_ver $VER`
MAJOR_VER=${MAJOR_VER%.}
BRANCH_NUMBER=`get_minor_ver $MAJOR_VER`
BRANCH_POINT=`get_major_ver $MAJOR_VER`
echo ${BRANCH_POINT}0.${BRANCH_NUMBER}
}
get_tags()
{
typeset FILE_PATH=$1
typeset VER=$2
TEMP_TAGS_INFO=/tmp/cvsinfo$$
cvs rlog -r$VER $FILE_PATH 1>${TEMP_TAGS_INFO} 2>/dev/null
TEMPTAGS=`sed -n '/symbolic names:/,/keyword substitution:/p' ${TEMP_TAGS_INFO} | grep ": ${VER}$" | cut -d: -f1 | awk '{print $1}'`
TAGS=`echo $TEMPTAGS | tr ' ' '/'`
echo ${TAGS:-NONE}
rm -Rf $TEMP_TAGS_INFO 2>/dev/null 1>&2
}

Resources