I would like to ask the teachers a shell question. Why is it that when I add a variable in a while loop, the variable is still 0 after the loop ends? I guess it’s because of the pipe (|) issue, but I don’t know how to avoid this problem.
[tidb@tidb01 tmp]$ cat test.sh
count=0
df -PT|while read line
do
echo $line
count=expr $count + 1
You didn’t mention which shell you are using, but in most shells, commands in a pipeline are often executed in a subshell. Changes made to variables in a subshell cannot be accessed outside of the subshell.
Different shells behave differently. In BASH, a subshell is created only if the loop is the last part of the pipeline (which is the scenario in your script).
The solution is to remove the pipeline. For example, in BASH:
1. Use a file as input
Write the output of df -PT to a temporary file and redirect the content of the temporary file using <.
2. Command grouping
Group all processing except df -PT together:
df -PT |
{
while read line
do
linecount=$((linecount + 1))
done
echo "$linecount"
}
3. Process Substitution
while read line
do
linecount=$((linecount + 1))
done < <(df -PT)
echo "$linecount"
4. Here String
while read line
do
linecount=$((linecount + 1))
done <<< $(df -PT)
echo "$linecount"
5. Named pipe
mkfifo p
df -PT > p &
while read line
do
linecount=$((linecount + 1))
done < p
echo "$linecount"
There are certainly more methods to bypass this issue.
My requirement is to loop through each line in df -PT, make a judgment, and if the file system meets a certain condition, increment by 1. However, treating df -PT as a whole directly does not loop and does not achieve the desired effect.
My requirement is to loop through each line in df -PT, make a judgment, and if the file system meets a certain condition, increment by 1. It is not directly counting how many lines df -PT has.
Thanks, master.
df -PT |
{
while read line
do
linecount=$((linecount + 1))
done
echo “$linecount”
}
This method is OK.
But the following won’t work
while read line
do
linecount=$((linecount + 1))
done <<< $(df -PT)
echo “$linecount”
Because $(df -PT) is treated as one whole line, it doesn’t loop, so the result is 1.
First of all, pipes are non-built-in commands. When Linux executes a shell, it creates a “subshell” to run the commands in the shell. When it runs into a non-built-in command, it creates a “shell” to run the non-built-in command. The scope of variables is effective within each shell, so the variables defined in non-built-in commands are only effective in the shell and not in the subshell. Therefore, even if the count is assigned within the while loop, the subshell will not get this value. This is a shell variable scope issue, which is different from other languages.
There are two ways to solve this problem:
If it is not necessary to use the pipe symbol in the while loop, you can use redirection. In this way, the variables inside the loop are effective in the subshell, which is relatively simple.
If you must use the pipe symbol, you can create a temporary file to store the output of the shell.