62

I'm trying to write a small script that will count entries in a log file, and I'm incrementing a variable (USCOUNTER) which I'm trying to use after the loop is done.

But at that moment USCOUNTER looks to be 0 instead of the actual value. Any idea what I'm doing wrong? Thanks!

FILE=$1

tail -n10 mylog > $FILE

USCOUNTER=0

cat $FILE | while read line; do
  country=$(echo "$line" | cut -d' ' -f1)
  if [ "US" = "$country" ]; then
        USCOUNTER=`expr $USCOUNTER + 1`
        echo "US counter $USCOUNTER"
  fi
done
echo "final $USCOUNTER"

It outputs:

US counter 1
US counter 2
US counter 3
..
final 0
codeforester
  • 39,467
  • 16
  • 112
  • 140
maephisto
  • 4,952
  • 11
  • 53
  • 73
  • Possible duplicate of [A variable modified inside a while loop is not remembered](https://stackoverflow.com/questions/16854280/a-variable-modified-inside-a-while-loop-is-not-remembered) – codeforester May 02 '18 at 02:31

8 Answers8

65

You are using USCOUNTER in a subshell, that's why the variable is not showing in the main shell.

Instead of cat FILE | while ..., do just a while ... done < $FILE. This way, you avoid the common problem of I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?:

while read country _; do
  if [ "US" = "$country" ]; then
        USCOUNTER=$(expr $USCOUNTER + 1)
        echo "US counter $USCOUNTER"
  fi
done < "$FILE"

Note I also replaced the `` expression with a $().

I also replaced while read line; do country=$(echo "$line" | cut -d' ' -f1) with while read country _. This allows you to say while read var1 var2 ... varN where var1 contains the first word in the line, $var2 and so on, until $varN containing the remaining content.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • Note you can use $(($USCOUNTER+1)) to evaluate the expression – geert3 Dec 19 '13 at 14:27
  • 1
    Thanks @geert3, good one. Also `((USCOUNTER++))` as anubhava's solution is using. I am not adding it to my answer in case it is incompatible with OPs shell. – fedorqui Dec 19 '13 at 14:34
28
while read -r country _; do
  if [[ $country = 'US' ]]; then
    ((USCOUNTER++))
    echo "US counter $USCOUNTER"
  fi
done < "$FILE"
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39
26

minimalist

counter=0
((counter++))
echo $counter
geekzspot
  • 787
  • 7
  • 7
16

You're getting final 0 because your while loop is being executed in a sub (shell) process and any changes made there are not reflected in the current (parent) shell.

Correct script:

while read -r country _; do
  if [ "US" = "$country" ]; then
        ((USCOUNTER++))
        echo "US counter $USCOUNTER"
  fi
done < "$FILE"
fedorqui
  • 275,237
  • 103
  • 548
  • 598
anubhava
  • 761,203
  • 64
  • 569
  • 643
8

I had the same $count variable in a while loop getting lost issue.

@fedorqui's answer (and a few others) are accurate answers to the actual question: the sub-shell is indeed the problem.

But it lead me to another issue: I wasn't piping a file content... but the output of a series of pipes & greps...

my erroring sample code:

count=0
cat /etc/hosts | head | while read line; do
  ((count++))
  echo $count $line
done
echo $count

and my fix thanks to the help of this thread and the process substitution:

count=0
while IFS= read -r line; do
  ((count++))
  echo "$count $line"
done < <(cat /etc/hosts | head)
echo "$count"
fedorqui
  • 275,237
  • 103
  • 548
  • 598
jobwat
  • 8,527
  • 4
  • 31
  • 30
  • 1
    Nice to see my answer was helpful :) Note also that `cat file | head` can be simplified to `head file`. – fedorqui Mar 08 '17 at 14:49
1
USCOUNTER=$(grep -c "^US " "$FILE")
Walter A
  • 19,067
  • 2
  • 23
  • 43
0

Incrementing a variable can be done like that:

  _my_counter=$[$_my_counter + 1]

Counting the number of occurrence of a pattern in a column can be done with grep

 grep -cE "^([^ ]* ){2}US"

-c count

([^ ]* ) To detect a colonne

{2} the colonne number

US your pattern

trax
  • 739
  • 8
  • 21
  • 1
    Although this code may answer the question, providing additional context regarding _why_ and/or _how_ it answers the question would significantly improve its long-term value. Please [edit] your answer to add some explanation. – Toby Speight Apr 21 '16 at 15:23
  • 1
    The title is: "Bash incrementing variable in loop" My line does: "incrementing a variable in bash" So I think it does answer a part of the question (at least as much as the example from geekzspot) – trax Apr 23 '16 at 07:37
  • 1
    The `$[ ... ]` syntax for arithmetic expansion is [deprecated](http://wiki.bash-hackers.org/syntax/expansion/arith). Please use `$(( ... ))` instead. Both your answer and geekzspot's address an _incidental_ aspect of the OP's question that doesn't solve the problem (the variable-incrementing part of the OP's code was working (however inefficiently)). If you want to suggest an _incidental_ improvement without answering the question, please clearly state so. – mklement0 Oct 27 '16 at 23:03
0

Using the following 1 line command for changing many files name in linux using phrase specificity:

find -type f -name '*.jpg' | rename 's/holiday/honeymoon/'

For all files with the extension ".jpg", if they contain the string "holiday", replace it with "honeymoon". For instance, this command would rename the file "ourholiday001.jpg" to "ourhoneymoon001.jpg".

This example also illustrates how to use the find command to send a list of files (-type f) with the extension .jpg (-name '*.jpg') to rename via a pipe (|). rename then reads its file list from standard input.

Draken
  • 3,134
  • 13
  • 34
  • 54
thanh
  • 1