0

I want to count how many times the digit "5" appears from the range 1 to 4321. For example, the number 5 appears 1 or the number 555, 5 would appear 3 times etc.

Here is my code so far, however, the results are 0, and they are supposed to be 1262.

#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
        echo ${n}
done | \
     while read -n1 digit ; do
        if [ `echo "${digit}" | grep 5` ] ; then
                count5=count5+1
        fi
     done | echo "${count5}"

P.s. I am looking to fix my code so it can print the right output. I do not want a completely different solution or a shortcut.

jenna
  • 9
  • 1
  • Perhaps this can help: https://stackoverflow.com/questions/16679369/count-occurrences-of-a-char-in-a-string-using-bash – Totem Nov 02 '22 at 00:23
  • BTW: `.... | echo "${count5}"` does not make any sense, since `echo` ignores standard input. – user1934428 Nov 02 '22 at 13:14
  • The reason why `count5` is 0 at the end is, that you set it to 0 initially, but increment it only in a childprocess (the body of the `while` loop is part of a pipe and therefore runs as child). This means that inside the main process, `count5` does not get incremented. – user1934428 Nov 02 '22 at 13:16

6 Answers6

3

What about something like this

seq 4321 | tr -Cd 5 | wc -c
    1262

Creates the sequence, delete everything but 5's and count the chars

Diego Torres Milano
  • 65,697
  • 9
  • 111
  • 134
  • This is a truly great answer (hence the upvote), but one thing I don't understand: the command `seq 4321` puts all numbers from 1 to 4321 on a new line, but when performing the `| tr -Cd 5`, everything gets concatenated into one line. Why is that? – Dominique Nov 02 '22 at 07:41
  • 1
    Because `tr` deletes everything (newlines included) except the `5`'s – Diego Torres Milano Nov 02 '22 at 07:59
  • That makes your answer even more useful (why can I only upvote once?) :-) – Dominique Nov 02 '22 at 08:01
2

The main problem here is http://mywiki.wooledge.org/BashFAQ/024. With minimal changes, your code could be refactored to

#!/bin/bash
typeset -i count5=0
for n in {1..4321}; do
        echo $n    # braces around ${n} provide no benefit
done |             # no backslash required here; fix weird indentation
while read -n1 digit ; do
        # prefer modern command substitution syntax over backticks
        if [ $(echo "${digit}" | grep 5) ] ; then
                count5=count5+1
        fi
        echo "${count5}"   # variable will not persist outside subprocess
done | head -n 1           # so instead just print the last one after the loop

With some common antipatterns removed, this reduces to

#!/bin/bash
printf '%s\n' {1..4321} |
grep 5 |
wc -l

A more efficient and elegant way to do the same is simply

printf '%s\n' {1..4321} | grep -c 5
tripleee
  • 175,061
  • 34
  • 275
  • 318
1

One primary issue:

  • each time results are sent to a pipe said pipe starts a new subshell; in bash any variables set in the subshell are 'lost' when the subshell exits; net result is even if you're correctly incrementing count5 within a subshell you'll still end up with 0 (the starting value) when you exit from the subshell

Making minimal changes to OP's current code:

 while read -n1 digit ; do
    if [ `echo "${digit}" | grep 5` ]; then
            count5=count5+1
    fi
 done < <(for n in {1..4321}; do echo ${n}; done)

 echo "${count5}"

NOTE: there are a couple performance related issues with this method of coding but since OP has explicitly asked to a) 'fix' the current code and b) not provide any shortcuts ... we'll leave the performance fixes for another day ...

markp-fuso
  • 28,790
  • 4
  • 16
  • 36
1

A simpler way to get the number for a certain n would be

nx=${n//[^5]/} # Remove all non-5 characters
count5=${#nx}  # Calculate the length of what is left
user1934428
  • 19,864
  • 7
  • 42
  • 87
0

Or using awk:

awk 'BEGIN { for(i=1;i<=4321;i++) {$0=i; x=x+gsub("5",""); } print x} '
j_b
  • 1,975
  • 3
  • 8
  • 14
0

A simpler method in pure bash could be:

printf -v seq '%s' {1..4321} # print the sequence into the variable seq
fives=${seq//[!5]}           # delete all characters but 5s
count5=${#fives}             # length of the string is the count of 5s
echo $count5                 # print it

Or, using standard utilities tr and wc

printf '%s' {1..4321} | tr -dc 5 | wc -c
M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17