Reduce the number of lines by using Array

I have the following code to count the number of how many times the name occurred in one file. The code is working fine and the output is exactly what I want. The problem is the real code has more than 50 names in function listname which cause function name to have more than 50 case ,and function setname more than 50 lines.

is there a way to rewrite function name and function setname in short code? someone told me I can use Array but not sure how. My goal is to reduce the number of lines.

function listname {

echo "List of Name:-"

  echo "1-Kim    2-Lee     3-Jack     4-Sam"
  echo "5-Nick    6-Smith"

name;
 }

function name {
  echo "Which name ou want to change?"
        read iname
 
        case $iname in
        1 ) echo "Kim"

               read -p "Enter Kim: " Kim
               echo "Kim=($Kim)"
        ;;
        2 ) echo "Lee"

               read -p "Enter Lee: " Lee
               echo "Lee=($Lee)"
     
        ;;

        3 ) echo "Jack"

               read -p "Enter Jack: " Jack
               echo "Jack=($Jack)"
        ;;

        4 ) echo "Sam"

               read -p "Enter Sam: " Sam
               echo "Sam=($Sam)"
        ;;
        5 ) echo "Nick"

               read -p "Enter Nick: " Nick
               echo "Nick=($Nick)"
        ;;
        6 ) echo "Smith"

               read -p "Enter Smith: " Smith
               echo "Smith=($Smith)"

        ;;
   
        * ) echo "You did not enter a number"
                echo "between 1 and 6." 

esac
 echo "Do you want another change?"
 read anothchang
     case $anothchang in
        [yY] | [yY][Ee][Ss] )
        listname;
             ;;
       [nN] | [nN][Oo] )
        setname;

}


function setname {      
               [ -z "$Kim" ] && Kim="cat /tmp/list | grep -c Kim"
               [ -z "$Lee" ] && Lee="cat /tmp/list | grep -c Lee"
               [ -z "$Jack" ] && Jack="cat /tmp/list | grep -c Jack"
               [ -z "$Sam" ] && Sam="cat /tmp/list | grep -c Sam"
               [ -z "$Nick" ] && Nick="cat /tmp/list | grep -c Nick"
               [ -z "$Smith" ] && Smith="cat /tmp/list | grep -c Smith"
output;
    }

 function output {

 echo "Kim ($Kim)  
   Lee($Lee) 
   Jack($Jack) 
   Sam($Sam) 
   Nick($Nick)   
   Smith($Smith)"
  }
echo "Hello"
listname;


I don't think above code is working fine. Having corrected several logical and syntactical errors, I'm still not sure I understand what you are doing - you have a list of six name constants, and six variables named identically. The latter are used to hold either a character string alternative name to be read from stdin (user terminal) or, if that is missing, an integer word count for, again, text constants. In the end, if an alternate name is entered, you output that, otherwise the word count for the respective text constant in a /tmp file. On top, your recursive use of functions is somewhat unorthodox.

Assuming you have a recent bash, and names in a file like

Kim
Lee
Jack
Sam
Nick
Smith

, try this

mapfile -tO1 ORIG <file
mapfile -tO1 NAMES <file
select NAME in ${NAMES[@]}
        do [ $REPLY -eq 0 ] && break
           read -p "Enter ${NAMES[$REPLY]} " NAMES[$REPLY]
        done
for i in ${!ORIG[@]}
        do printf "%s(" ${ORIG[$i]}
           [ "${ORIG[$i]}" == "${NAMES[$i]}" ] && 
                printf "%s)\n" $(grep -c ${NAMES[$i]} /tmp/list) ||
                printf "%s)\n" "${NAMES[$i]}"
        done

to reproduce what I think you want to achieve.

1 Like

The use of numbers suggests you want to use a simple vector array, but why not skip the numbers and use just names and an associative array?

1 Like

Might be an option if the OP's intentions were clear...

1 Like

i apologize for the misunderstanding , this is not the really code. I just wrote the above code to gave you in idea of what I am trying to do.
I fixed the code above , and now should run. Let say here is what inside my list file:-

cat list 
Sam
Sam
Kim
Sam
Kim
Jack
Nick
Nick
Smith
Smith

when I run the code :-

List of Name:-
1-Kim    2-Lee     3-Jack     4-Sam
5-Nick    6-Smith
Which name ou want to change?
1
Kim
Enter Kim: 23
Kim=(23)
Kim (23)  
   Lee(0) 
   Jack(1) 
   Sam(3) 
   Nick(2)   
   Smith(2)

The code is running fine , I just want it to know if there is any way to rewrite it in short way.

here is the updated code


function listname {

echo "List of Name:-"

  echo "1-Kim    2-Lee     3-Jack     4-Sam"
  echo "5-Nick    6-Smith"

name;
 }

function name {
  echo "Which name ou want to change?"
        read iname

        case $iname in
        1 ) echo "Kim"

               read -p "Enter Kim: " Kim
               echo "Kim=($Kim)"
        ;;
        2 ) echo "Lee"

               read -p "Enter Lee: " Lee
               echo "Lee=($Lee)"

        ;;

        3 ) echo "Jack"

               read -p "Enter Jack: " Jack
               echo "Jack=($Jack)"
        ;;

        4 ) echo "Sam"

               read -p "Enter Sam: " Sam
               echo "Sam=($Sam)"
        ;;
        5 ) echo "Nick"

               read -p "Enter Nick: " Nick
               echo "Nick=($Nick)"
        ;;
        6 ) echo "Smith"

               read -p "Enter Smith: " Smith
               echo "Smith=($Smith)"

        ;;

        * ) echo "You did not enter a number"
                echo "between 1 and 6."

esac
setname;
}


function setname {
               [ -z "$Kim" ] && Kim=`cat list | grep -c Kim`
               [ -z "$Lee" ] && Lee=`cat list | grep -c Lee`
               [ -z "$Jack" ] && Jack=`cat list | grep -c Jack`
               [ -z "$Sam" ] && Sam=`cat list | grep -c Sam`
               [ -z "$Nick" ] && Nick=`cat list | grep -c Nick`
               [ -z "$Smith" ] && Smith=`cat list | grep -c Smith`
output;
    }

 function output {

 echo "Kim ($Kim)
   Lee($Lee)
   Jack($Jack)
   Sam($Sam)
   Nick($Nick)
   Smith($Smith)"
  }
echo "Hello"
listname;

Here is something to get started, to chew on :stuck_out_tongue:

#!/bin/bash
#
#
#	Variables
#
	declare -A AGE				# Create an associative array
	NAMES="Kim Lee Jack Sam Nick Smith"	# List with names
#
#	Fill the array
#
	select name in Back $NAMES;do		# Select an entry of the list NAMES
		[[ $name = Back ]] && break	# Exit the select-loop / break out
		
		echo "Hello $name, what is your age?"
		read age
		AGE[$name]=$age			# Set the arrays entry '$name' to value '$age'
	done
#
#	Output the content
#
	for n in $NAMES
	do
		echo "$n is ${AGE[$n]} years old"
	done
	echo "Have a nice day :)"

Hope this helps

1 Like

Thank you sea , it helped me with other code :slight_smile: , but it is not what I am looking here

An attempt to stay close to your original script...

#!/bin/sh

declare -A AGES                         # associative name/ages array
NAMES=($(cat names.data | sort -u))     # unique names array
SIZE=0

function output {
echo
for x in ${NAMES
[*]};
do
    echo "$x(${AGES[$x]})"
done
exit
}

function setname {
for x in ${NAMES
[*]};
do
    AGES[$x]=$(grep -c $x names.data)
done
}

function listname {
num=0
for x in ${NAMES
[*]};
do
    num=$(( $num + 1 ))
    printf "%d-%s(%s)" $num $x ${AGES[$x]}
    if [[ $(( $num % 4 )) -ne 0 ]]; then    # 4 column display
        printf "\t"
    else
        printf "\n"
    fi
    SIZE=$num
done
name;
}

function name {
echo
echo "Which name you want to change? "
read iname
[ $iname ] || output;    # No entry = exit
if [ $iname -gt $SIZE ] || [ $iname -lt 1 ]; then
    echo "Choice is out of range"
    listname;
fi
idx=$(( $iname - 1 ))   # array index count from zero
nam=${NAMES[$idx]}
echo $nam

read -p "Enter $nam: " age
AGES[$nam]=$age         # update AGES array
echo "$nam=(${AGES[$nam]})"

echo "Do you want another change?"
read anothchang
case $anothchang in
    [yY] | [yY][Ee][Ss] )
        listname;
        ;;
    [nN] | [nN][Oo] )
        output;         # display AGES array and exit
        ;;
    * )
        output;
        ;;
esac
}

echo "Hello"
setname;
listname;

exit

# eof #
1 Like

Hi ongoto
It does not likethese two lines:-

idx=$(( $iname - 1 )) # array index count from zero AGES[$nam]=$age # update AGES array
I am getting the following error :-
bad array subscript

You have to be careful not to enter a number that is out of range. (zero or greater than the size of the list)

I made a few changes to catch that error and another bug I discovered...

function name {
echo
echo "Which name you want to change? "
read iname
[ $iname ] || output;    # No entry = exit
if [ $iname -gt $SIZE ] || [ $iname -lt 1 ]; then
    echo "Choice is out of range"
    listname;
fi
function listname {
num=0
for x in ${NAMES
[*]};
do
    num=$(( $num + 1 ))
    printf "%d-%s" $num $x
    if [[ $(( $num % 4 )) -ne 0 ]]; then    # 4 column display
        printf "\t"
    else
        printf "\n"
    fi  
    SIZE=$num
done
NAMES=($(cat names.data | sort -u))     # unique names array
SIZE=0

function output {
echo
for x in ${NAMES
[*]};
do
    echo "$x(${AGES[$x]})"
done
exit
}
1 Like

ongoto ,

I really appreciate your help , I am still getting the following error:-

$ ./test2
Hello
1-Jack(12)    2-Kim(22)    3-Nick(89)    4-Sam(78)
5-Smith(7)    
Which name you want to change? 
4
Enter : 21
./test2: line 49: AGES[$nam]: bad array subscript

Here is how the code like:-

#!/bin/sh

declare -A AGES                         # associative name/ages array
NAMES=($(cat list | sort -u))     # unique names array
SIZE=0
function output {
echo
for x in ${NAMES
[*]};
do
    echo "$x(${AGES[$x]})"
done
exit
}

function setname {
for x in ${NAMES
[*]};
do
    AGES[$x]=$(grep -c $x list)
done
}

function listname {
num=0
for x in ${NAMES
[*]};
do
    num=$(( $num + 1 ))
    printf "%d-%s" $num $x
    if [[ $(( $num % 4 )) -ne 0 ]]; then    # 4 column display
        printf "\t"
    else
        printf "\n"
    fi
SIZE=$num
done
name;
}

function name {
echo
echo "Which name you want to change? "
read iname
[ $iname ] || output;    # No entry = exit
if [ $iname -gt $SIZE ] || [ $iname -lt 1 ]; then
    echo "Choice is out of range"
    listname;
fi

read -p "Enter $nam: " age
AGES[$nam]=$age         # update AGES array
echo "$nam=(${AGES[$nam]})"

Nothing major.
It looks like part of your code (in red) is missing...

read iname
[ $iname ] || output;
if [ $iname -gt $SIZE ] || [ $iname -lt 1 ]; then
    echo "Choice is out of range"
    listname;
fi
idx=$(( $iname - 1 ))   # array index count from zero
nam=${NAMES[$idx]}
echo $nam

read -p "Enter $nam: " age
AGES[$nam]=$age         # update AGES array
echo "$nam=(${AGES[$nam]})"

Another difference I noticed is your name list is displaying ages in parentheses.

1-Jack(12)    2-Kim(22)    3-Nick(89)    4-Sam(78)
5-Smith(7) 

I modified this line in my example to achieve that...

function listname {
num=0
for x in ${NAMES
[*]};
do
    num=$(( $num + 1 ))
    printf "%d-%s(%s)" $num $x ${AGES[$x]}
1 Like

Excellent ongoto, thank you. it works now :):):):):):):b:

A more complete example doesn't reduce the size of the script much. Sea and Chubler_XL have the best solutions as far as I can see.
Good luck.

Here is another array implementation that might be closer to your original script:

NAMES=( Kim Lee Jack Sam Nick Smith )

function listname {

  echo "List of Name:-"

  for((i=1;i<=${#NAMES[@]};i++))
  do
      printf "%d-%-8s" $i "${NAMES[i-1]}"
      ((i%4==0)) && printf "\n"
  done
  printf "\n"
}

function name {

    printf "\n"
    read -p "Which name you want to change? " iname

    if [ -n "$iname" -a $iname -gt 0 -a $iname -le ${#NAMES[@]} ] 2> /dev/null
    then

        nam="${NAMES[iname-1]}"

        read -p "Enter $nam: " AGE[$iname-1]

    else
         echo "You did not enter a number"
         echo "between 1 and ${#NAMES[@]}."
    fi

    echo
    read -p "Do you want another change? " anothchang
    [[ $anothchang = [yY] || $anothchang = [yY][Ee][Ss] ]]
}

function output {
  for((i=0;i<${#NAMES[@]};i++))
  do
      printf "%s(%d)\n" "${NAMES}" ${AGE}
  done
}


listname
while name
do
  output
  listname
done
output