Seeking Help with a Shell Issue

Note:
This topic has been translated from a Chinese forum by GPT and might contain errors.

Original topic: 请教1个shell 问题

| username: Raymond

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

done
echo $count

still 0

| username: 像风一样的男子 | Original post link

Are you trying to read the number of lines in the result of df -PT? You can do it like this: df -PT | wc -l.

| username: ShawnYan | Original post link

Are you writing this shell script to calculate the number of lines or to count a specific column?

| username: redgame | Original post link

count=0
df -PT | while read line
do
echo $line
((count++))
done
echo $count

| username: tidb菜鸟一只 | Original post link

The count=df -PT|wc -l is fine, as it uses a pipeline to a subshell, which cannot change the variables of the parent shell.

| username: MrSylar | Original post link

#!/bin/bash
declare -x count=0
while IFS= read -r line
do
echo $line
count=$(expr $count + 1)
done <<< “$(df -PT)”
echo “Count: $count”

| username: buptzhoutian | Original post link

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.

| username: Raymond | Original post link

This method won’t work either. After the while loop ends, the value of count is still 0.

| username: Raymond | Original post link

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.

| username: Raymond | Original post link

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.

| username: Raymond | Original post link

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.

| username: tidb菜鸟一只 | Original post link

Does this mean df -PT | awk '{if ($4>0) {print $4}}' | wc -l?

| username: buptzhoutian | Original post link

Are you using bash?
You can test the performance of HereString in the command line like this, it shouldn’t be just one line:

cat <<< $(df)
| username: ffeenn | Original post link

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:

  1. 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.

  2. If you must use the pipe symbol, you can create a temporary file to store the output of the shell.

| username: Raymond | Original post link

Thank you for your reply, teacher.

| username: Raymond | Original post link

[root@tidb01 ~]# cat <<< $(df)
Filesystem 1K-blocks Used Available Use% Mounted on /dev/mapper/rhel-root 49250820 23256508 25994312 48% / devtmpfs 2150452 0 2150452 0% /dev tmpfs 2162688 0 2162688 0% /dev/shm tmpfs 2162688 11996 2150692 1% /run tmpfs 2162688 0 2162688 0% /sys/fs/cgroup /dev/sda1 1038336 132920 905416 13% /boot /dev/mapper/datavg-lv_deploy 30832548 796296 28447004 3% /tidb-deploy /dev/mapper/datavg-lv_data 51474912 1377720 47459368 3% /tidb-data tmpfs 432540 0 432540 0% /run/user/0

| username: buptzhoutian | Original post link

I don’t know your environment and version, it should be an IFS configuration issue.

To let the shell correctly recognize the special character \n, you can add double quotes.

cat <<< "$(df)"
| username: Raymond | Original post link

Got it, thank you, master. Thank you, teacher.

| username: system | Original post link

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.