How do I do a pattern-match for this: 4,7,9....n - a comma delimited string of digits as user input? I'm using case statement for that and I believe, case rather use pattern-matching than regex. This is what user gets:
Do you want to delete any of these?
[ 1 ] launch-EOsgR4
[ 2 ] launch-SWZQdJ
[ 3 ] launch-tHAdIm
[ 4 ] launchd-235.z4KTVx
[ 5 ] launchd-257.nM2wOZ
[ 6 ] progress.log
[ 7 ] ssh-8pISGGnlZ5
----------------------------------------
Single: 4; Multiple: 2,3; Range: 4..7
a to delete all; n to cancel and exit
----------------------------------------
( [1][2][3][4][5][6][7] | a | n ):
And, as the showed above, users got the option for a single number (easy), a range: 6..9 (also not so hard) or multiple: 3,5,6 (find a bit hard with 'single number' option). This is what I've done so far....
#!/usr/bin/env bash
NL=$(echo -e "\033[0;0m")
BD=$(echo -e "\033[0;1m")
ERR=$(printf "\n%-25s" "$(echo -e "\033[1;31m[ ERROR ]\033[0m")")
WRN=$(printf "\n%-25s" "$(echo -e "\033[1;33m[ WARN ]\033[0m")")
ls /tmp | tail -n9 > list_of_file
s=$(printf "%-40s" "-")
function lstFile()
{
local file=$1
if [[ -s $file ]]
then
LINES=( $(cat $file) ); echo ""
for ix in ${!LINES[@]}
do
printf "%-5s%-14s%s\n" "" "${BD}[ $(( ix+1 )) ]" "${LINES[$ix]}${NL}"
done
else
exit 0
fi
LST=$(echo ${!LINES[@]}|awk -v ORS=']' '{for (i=1; i<=NF; i++) print "["($i+1)}')
}
function delOpt()
{
echo "${s// /-}"
echo -e "Single: 4; Multiple: 2,3; Range: 4..7"
echo -e "${BD}a${NL} to delete all; ${BD}n${NL} to cancel and exit"
echo "${s// /-}"
echo -n "( $1 | a | n ): "
}
echo -e "\nDo you want to delete any of these?"
lstFile list_of_file
ANS=
until [[ "${ANS}" == "N" || "${ANS}" == "n" || "${ANS}" == "e" ]]
do
delOpt $LST
read ANS && echo ""
ANS=$( tr '[:upper:]' '[:lower:]' <<< "$ANS" )
[[ -n $(echo $ANS|grep -E -w "^[aen0-9,]{1,}") ]] && : || ANS="X"
case ${ANS} in
[0-9]..[0-9] )
for ix in $(eval echo \{$ANS\}); do
LINE=${LINES[(( $ix-1 ))]}
echo -e "Deleting: ${BD}${LINE}${NL}"
sed -i -c "/$LINE/d" list_of_file
done
unset LINE
lstFile list_of_file
;;
[0-9,]* )
ANS=$(echo $ANS | awk -F' |,' '{for (i=1; i<=NF; i++) print $i}')
for ix in $ANS; do
if [[ $ix -gt ${#LINES[@]} ]]
then
echo "${ERR}Out-of-range value: ${bd}$ix${NL}"
else
LINE=${LINES[(( $ix-1 ))]}
echo -e "Deleting: ${BD}${LINE}${NL}"
sed -i -c "/$LINE/d" list_of_file > /dev/null 2>&1
fi
done
unset LINE
lstFile list_of_file
;;
a )
for ix in ${LINES[@]}; do
echo "Deleting: ${BD}${ix}${NL}"
sed -i -c "/$ix/d" list_of_file
done
exit 0
;;
n|e ) exit 0
;;
* ) echo "${WRN}Invalid entry! Should be digit or ${BD}a${NL} for All."
printf "%-14s%s\n\n" "" "Otherwise, enter ${UL}n${NL}o or ${UL}e${NL}xit to quit"
;;
esac
done
which is working fine (sort of) but there are some race conditions. e.g.
2,d,7 - throws in: bad array subscript
6..10 - throws in: (( 6..11-1 )): syntax error: invalid arithmetic operator (error token is "..11-1 ))")
but,
6..9 - throws in: first RE may not be empty
Is there any way to have separate options to catch 'single' and 'multiple' number input? Also, any suggestions on overall improvement?
Any help greatly appreciated. Cheers!!
list_of_file
according to the user input.
function chkINPUT()
{
local I_PUT=( "$@" )
local MAX=${#LINES[@]}
#IFS=', ' read -a SPLITTED <<< "$I_PUT"
local SPLITTED=( $(echo "${I_PUT[@]}" | awk -F',| ' '{for (i=1; i<=NF; i++) print $i}') )
for idx in "${!SPLITTED[@]}"
do
SPLTD=${SPLITTED[idx]}
# Check if it's a range [4..7]
if [[ "${SPLTD}" =~ ^[0-9]{1,2}\.\.[0-9]{1,2}$ ]]
then
for ix in $(eval echo \{$SPLTD\}); do
if (( ${ix} <= $MAX )); then
LINE=${LINES[(( ix-1 ))]}
echo -e "Deleting: ${BD}${LINE}${NL}"
sed -i -e "/$LINE/d" list_of_file 2>&1 > /dev/null
unset LINE
else
echo -e "\t${ix} => Out of range"
break
fi
done
# Check if it's a single input
elif [[ "${SPLTD}" =~ ^[[:digit:]]+$ ]]
then
if (( ${SPLTD} <= $MAX )); then
LINE=${LINES[(( SPLTD-1 ))]}
echo -e "Deleting: ${BD}${LINE}${NL}"
sed -i -e "/$LINE/d" list_of_file 2>&1 > /dev/null
unset LINE
else
echo "${ERR}Out-of-range value: ${bd}$SPLTD${NL}"
fi
else
echo "${ERR}Invalid entry: ${bd}$SPLTD${NL}; must be an integer from the list!"
fi
done
}
and then, use it like this:
ANS=
until [[ "${ANS}" == "N" || "${ANS}" == "n" || "${ANS}" == "e" ]]
do
delOpt $LST
read ANS && echo ""
ANS=$( tr '[:upper:]' '[:lower:]' <<< "$ANS" )
case ${ANS} in
[0-9]* )
chkINPUT ${ANS}
;;
Hope it helps. Cheers!!
I think the best option could be to split string into array, then cycle on the array. The next question could be how to split string, here is an option:
#!/bin/sh
ANS="1,2,3..4"
echo ans = ${ANS}
IFS=', ' read -a SPLITTED <<< "$ANS"
for index in "${!SPLITTED[@]}"
do
echo "$index ${SPLITTED[index]}"
done
result
alepac@mypc:/tmp$ . test.sh
ans = 1,2,3..4
0 1
1 2
2 3..4
in this way you could even some like 1,3..5,8,n
and parse it correctly