Counting last reported version in shell Struggling to write a simple tool to quickly sum up the last reported versions in a bunch of files. I'm using a named pipe to get the count out the loop for the final value, but the $version isn't populated which I don't understand.

mkfifo mypipe
for i in stats/*
do
    tail -1 $i
done | sort -n &gt; mypipe &amp;

while read version
do

    if test "$version" = "$last"
    then
        count=$(($count + 1))
    else
        echo $last $count
        count=1
        last=$version
    fi

done &lt; mypipe

# $version is not printed, what's the workaroud?
echo version $version count $count


Perhaps people can suggest a better cleaner more suck less solution?
Your immediate problem is that the exit condition of the loop is when read version returns an error status. This happens when the loop reaches the end of its input file. This call to read also sets version to the empty string.

You can salvage this code by keeping the last known-good value of version in another variable — which you already do here, in last. So change the last line to echo version $last count $count. But there are simpler ways of doing this; uniq -c, which has already been suggested being the simplest, and awk as a general purpose text processing tool being something to keep in mind as well.

While I'm at it, some general notes on shell programming.


Always put double quotes around variable substitutions and command substitutions, unless you can explain why it is safe to leave them off in a particular instance. For example, tail -1 $i should be tail -1 "$i": if you leave off the quotes, this command will fail if the file name contains whitespace or globbing characters.
In fact, this line should be

tail -n 1 -- "$i"


The -- is there in case $i begins with a -; without the -- it would be treated as an option to tail. In this specific case $i always begins with s so the -- is unnecessary, but it's a good habit to get into.
The form -1 is obsolete, and some modern implementations of tail don't support it. -n 1 is the standard form.
To read a line from a file, don't use plain read, but IFS= read -r. See How can I read line by line from a variable in bash? Plain read expands backslashes (expecting a continuation line if a line ends with a \), and strips off leading and trailing whitespace.
Using a named pipe requires a lot of management: you need to create it with a unique name (use mktemp) and make sure the permissions are correct. You may have tried and failed with the following structure:

for i in stats/*; do … done | while read version; do … count=…; done
echo $count


In most shells (Bourne, ash, bash, pdksh), each component of a pipe is executed in its own subshell, so any assignment to count in the while loop doesn't affect the parent shell. Only in a few shells (ATT ksh, zsh) does the rightmost command in a pipeline run in the parent shell. There's a simple workaround for that: run all the code that depends on the assignments performed by a pipeline component inside that pipeline component.

for i in stats/*; do … done | sort -n | {
  while read version; do … count=…; done;
  echo $count
}