0

In my script I want to count how many directories and files I have inside different directories. Inside "assignments" I have a lot of directories named "repo1", "repo2" etc. Here is my code:

ls -1 assignments | while read -r repoDir
do
    find assignments/"$repoDir" | grep -v .git | grep "$repoDir"/ | while read -r aPathOfRepoDir
    do
        BASENAME=`basename "$aPathOfRepoDir"`
        if [[ -d "$aPathOfRepoDir" ]]; then
            totalDirectories=$((totalDirectories+1))
        elif [[ -f "$aPathOfRepoDir" ]] && [[ "$BASENAME" == *".txt" ]]; then
            totalTextFiles=$((totalTextFiles+1))
        else
            totalOtherFiles=$((totalOtherFiles+1))
        fi
    done
    echo "total directories: $totalDirectories"
    echo "total text files: $totalTextFiles"
    echo "total other files: $totalOtherFiles"
    totalDirectories=0
    totalTextFiles=0
    totalOtherFiles=0;
done

When the while-loop is finished I lose the values of those 3 variables. I know that this is happening because the while-loop is a sub-shell but I don't know how can I somehow "store" the values of the variables for the parent shell. I thought about printing those messages inside the while-loop when I know that it's the last "aPathOfRepoDir" but that's kinda a "cheap" way to do it and won't be efficient. Is there another way?

Thanks in advance

Savvas Th
  • 65
  • 5

2 Answers2

2

Right hand side of a pipe runs in a subshell. Changes in the subshell variables don't propagate to the parent shell. Use process substitution instead:

while read -r aPathOfRepoDir
    do
        BASENAME=`basename "$aPathOfRepoDir"`
        if [[ -d "$aPathOfRepoDir" ]]; then
            totalDirectories=$((totalDirectories+1))
        elif [[ -f "$aPathOfRepoDir" ]] && [[ "$BASENAME" == *".txt" ]]; then
            totalTextFiles=$((totalTextFiles+1))
        else
            totalOtherFiles=$((totalOtherFiles+1))
        fi
    done < <(find assignments/"$repoDir" | grep -v .git | grep "$repoDir"/ )
choroba
  • 231,213
  • 25
  • 204
  • 289
0

How about this:

classify() {
    local -A types=([dir]=0 [txt]=0 [other]=0)
    local n=0
    while read -r type path; do 
        if [[ $type == d ]]; then
            (( types[dir] ++ ))
        elif [[ $type == f && $path == *.txt ]]; then
            (( types[txt] ++ ))
        else
            (( types[other] ++ ))
        fi
        ((n++))
    done
    if [[ $n -gt 0 ]]; then
        echo "$1"
        echo "total directories: ${types[dir]}"
        echo "total text files: ${types[txt]}"
        echo "total other files: ${types[other]}"
    fi
}

for repoDir in assignments/*; do 
    find "$repoDir" \
        \( ! -path "$repoDir" -a ! -path '*/.git' -a ! -path '*/.git/*' \) \
        -printf '%y %p\n' \
    | classify "$repoDir"
done
  • find can exclude the files you don't want to see
  • find will also emit the file type to simplify the classification
  • the "classify" function will loop over the find output to count the various categories.
  • not parsing ls
  • the function helps to localize all the variables in a single subshell
glenn jackman
  • 238,783
  • 38
  • 220
  • 352