Variable incremented in bash while loop resets to 0 when loop finishes [duplicate] - bash

This question already has answers here:
Local variables after loop exit
(5 answers)
Closed 6 years ago.
I'm writing a bash script that uses a while loop to process over the rows outputted from a specific command. I also increment a variable (adding 1) for each row found.
Heres an example of the section of the script in question:
#!/bin/bash
count=0
ls | while read f
do
count=$(($count+1))
echo "Count is at ${count}"
done
echo "Found total of ${count} rows"
Basically, it increments the $count variable just fine, but then when I print $count after the while loop.. its reset to 0..
Example output:
Count is at 1
Count is at 2
Count is at 3
Count is at 4
Count is at 5
Found total of 0 rows
Any idea why the $count would reset after the loops done?
I also tried adding the last echo statement using the && operator on the loop, like so:
count=0
ls | while read f
do
count=$(($count+1))
echo "Count is at ${count}"
done && echo "Found total of ${count} rows"
With no success.
Any help would be appreciated

A pipe spawns a subshell, use a process substitutions instead:
while read -r f
do
count=$(($count+1))
echo "Count is at ${count}"
done < <(ls)
Also note that you shouldn't parse the output of ls.
And your example seems to count numbers of files and directories in current directory, which can be done with find and wc:
find -maxdepth 1 -mindepth 1 -printf "\n" | wc -l
or you can avoid ls with a for loop and globbing:
for f in * .*; do
[ -e "$f" ] || continue
count=$((count + 1))
echo "Count is at ${count}"
done

Related

loop over output of command and print both index and value

I have checked Looping over arrays, printing both index and value. The issue is I want to loop over output of command and not an array.
The code i came up with is:
array=($(seq 1 10))
for i in "${!array[#]}"; do
printf "%s\t%s\n" "$i" "${array[$i]}"
done
or,
ITER=0
for I in $(seq 1 10)
do
echo ${I} ${ITER}
ITER=$(expr $ITER + 1)
done
What i want to know is, is it possible to do it within the loop only (without array or ITER) outside the loop?
What i am looking for is something like:
for index,value in $(seq 1 10); do
echo $index $value
done
Let us know your actual requirement:
01)
#!/bin/bash
index=0
for filename in $(ls -atr)
do
indx=$(($indx+1))
echo "Index: $indx $filename"
done
output:
$ ./73412398.sh
Index: 1 ..
Index: 2 73412398.sh
Index: 3 .
One more try:
for index in $(ls -atr | grep -n $)
do
echo $index | sed "s/\([0-9]*\):/\1 /;"
done
output:
1 ..
2 73412398.sh
3 .
after modifying murugesan openssl's answer, the solution for me is:
for indexval in $(ls -atr | grep -n $)
do
echo index is "${indexval%%:*}"
echo value is "${indexval#*:}"
done

Increment Number in ZSH script

I am trying to create directories based off of the number of files I have in a directory and my approach is a loop.
I am keeping track of the number by a number variable and want to increment it as so after each even number, create a directory.
This is my code so far:
#!/bin/zsh
LOOP=$(ls 2021/*.xlsx)
totalFiles=$(ls 2021/*.xlsx | wc -l)
directoryCount=0
count=0
for FILE in ${LOOP[#]}; do
echo "BEFORE: $count"
count=$((count+1))
echo "AFTER: $count"
echo $FILE
done
echo "TOTAL FILE: $totalFiles"
echo $count
and the output I get is:
BEFORE: 0
AFTER: 1
2021/*Tanner2103.xlsx
2021/*Tanner2104.xlsx
2021/*Tanner2105.xlsx
2021/*Tanner2106.xlsx
TOTAL FILE: 4
1
I dont understand why count only increments once but the loop is obviously has more iterations than that.
So basically since there are 4 files, I want to split them up into 2 directories, Ill do the logic for that later. But for now Im just trying to get the directory code working.
Ok I feel silly. I thought that ls would come back as an iterable but its actual only return one iteration as a whole.
This is my updated code and it updating like I want.
totalFiles=$(ls 2021/*.xlsx | wc -l)
count=0
for FILE in 2021/**/*(.); do
echo "BEFORE $count"
echo "$FILE"
((count++))
echo "AFTER $count"
done
echo "TOTAL FILE: $totalFiles"
echo $count
result:
BEFORE 0
2021/*Tanner2103.xlsx
AFTER 1
BEFORE 1
2021/*Tanner2104.xlsx
AFTER 2
BEFORE 2
2021/*Tanner2105.xlsx
AFTER 3
BEFORE 3
2021/*Tanner2106.xlsx
AFTER 4
TOTAL FILE: 4
4

Loop over first 10 of 100 subdirectories

I have 100 subdirectories and I wanted to loop through the first ten of them in a bash for loop such like that:
for d in ./output/*[0..9];
do
echo $d
done
But the output seems not what I expected:
./output/405050
./output/405140
./output/405309
./output/405310
./output/405319
./output/500550
./output/500589
./output/500610
Why only 8 were printed and my question is how to select a fix number elements from this type of for loop.
*[0..9] loops over ones that end in a 0, 9, or .. If you had written *{0..9} that would loop over ones ending in a digit 0 through 9--closer, but still not right.
Try this loop, which reads the first 10 directory names in a loop. It's kinda obtuse. The primary idea is using while read ... < <(cmd) to read a command's output one line at a time. IFS= and -r are pedantic bits to handle directory names with whitespace and backslashes correctly.
while IFS= read -r dir; do
echo "$dir"
done < <(ls output/*/ | head -10)
Or use this more straightforward version with a counter:
i=0
for dir in output/*/; do
echo "$dir"
((++i < 10)) || break
done
Or this one storing the directories in an array:
dirs=(output/*/)
for dir in "${dirs[#]::10}"; do
echo "$dir"
done
You can make a counter:
#!/bin/bash
i=0;
for d in ./output/*/;
do
echo $d
echo ""
if [[ i == 10 ]]; then
break
fi
i+=1
done
With this you asure to get 10 folders.
I very important to do the last backslash to match only directories.

Echo showing wrong variable value in bash after a while block. Why? [duplicate]

This question already has answers here:
A variable modified inside a while loop is not remembered
(8 answers)
Closed 5 years ago.
I wanted to make a quick script to count the number of lines my .scala files have:
#!/bin/bash
counter=0;
find -iname "*.scala" | while read f; do
lc=$(cat $f | wc -l);
counter=$((counter+lc));
echo "$lc $counter";
done
echo "final result: $counter";
But unfortunately this prints
20 20
204 224
212 436
final result: 0
What's wrong here?
The issue is caused because you use a pipe before your while loop.
By doing so, bash automatically creates a new subshell. All the modifications you do will be executed in the new context, and will not be propagated when the context closes.
Use process substitution instead :
#!/bin/bash
counter=0;
while read f; do
lc=$(cat $f | wc -l);
counter=$((counter+lc));
echo "$lc $counter";
done < <(find -iname "*.scala")
echo "final result: $counter";

While loop resets a numeric variable in a Bash script [duplicate]

This question already has answers here:
Variables getting reset after the while read loop that reads from a pipeline
(3 answers)
Closed 6 years ago.
I'm trying to do a simple bash script to do something in one of each file in a set of folders. Also I like to count how many files the script read, but when the script pass of the loop, the numerical variable is reseted.
The code I'm using is like that
#!/bin/bash
let AUX=0
find . -type "f" -name "*.mp3" | while read FILE; do
### DO SOMETHING with $FILE###
let AUX=AUX+1
echo $AUX
done
echo $AUX
I can see that AUX is counting inside the loop, but the last "echo" prints a 0, and the variable seems to be really reseted. My console output is like that
...
$ 865
$ 866
$ 867
$ 868
$ 0
I would like to preserve in AUX the number of files proccesed. Any idea?
Do not use the pipe, it creates a subshell. Example below.
#!/bin/bash
declare -i AUX=0
while IFS='' read -r -d '' file; do
### DO SOMETHING with $file###
(( ++AUX ))
echo $AUX
done < <(find . -type "f" -name "*.mp3")
echo $AUX
If you have bash 4.0 or later, use the globstar option instead of find:
shopt -s globstar
aux=0
for f in **/*.mp3; do
# Just in case there is a directory name ending in '.mp3'
[[ -f $f ]] || continue
# Do something with $f
echo "$(( ++aux ))"
done

Resources