【问题标题】:Variable iterates twice (only supposed to once) in a single loop in bash变量在 bash 的一个循环中迭代两次(只应该是一次)
【发布时间】:2025-12-14 05:15:01
【问题描述】:

我有一个问题,一个数字在某些输出之前只应该打印一次,但它打印了两次。我将突出显示最相关的内容,但为了提供上下文,我将提供我的整个脚本。

#!/bin/bash

我将ps aux 的输出加载到一个数组中。

N=0
for i in $(ps aux | awk '{print $11,$4}' | grep -v 0.0 | grep -v MEM | sort) ; do

array[$N]="$i"

let "N= $N + 1"
done

这里我把数组分成两个数组

N=0

for i in `seq 0 ${#array[@]}`; do

if [ $(( $i % 2 )) -eq 0 ]; then arrayb[$N]=${array[$i]} ; else arrayc[$N]=${array[$i]} ;fi

let "N= $N + 1"

done

现在我希望合并重复的条目(请原谅一些令人费解的变量命名;我在黑暗中拍摄试图排除故障)。

N=0
c=0

for i in `seq 0 ${#arrayb[@]}`; do

let "B= $N - 1"

A=`echo ${arrayb[$B]} | tr -d '\n'`
B=`echo ${arrayb[$N]} | tr -d '\n'`

#A=${arrayb[$B]}
#B=${arrayb[$N]}

我在这里检查前一个数组元素是否等于后者。如果是这样,合并并删除。

#if [ "$A" = "$B" ]; then 
#arrayc[$N]=$(bc <<< ${arrayc[$N]}+${arrayc[$B]}); unset arrayb[$B];unset arrayc[$B]; echo trololo
#echo derp
#fi

但重要的部分是这个。 echo $c 然后我 echo $A $B。然而,我的输出显示如下:

0
 awk
1
awk 
2
 -bash
3
-bash 
4
 -bash

什么时候应该是这样的:

0
 awk
awk 
2
 -bash
-bash 
3

echo $c
echo -n "$A" "$B"

echo ""
let "c=$c+1"


let "N= $N + 1"
done

我无法理解的是它如何打印a,然后是c,然后是b,然后是c,当它都是同一个循环时。任何帮助将不胜感激。

【问题讨论】:

  • 欢迎来到 Stack Overflow。请尽快阅读About 页面。这个问题明显缺乏的是对脚本目的的解释或对所需输出的描述。两个grep -v 操作可以用awk 完成。数组工作最好由while read name memory 处理,然后将$name$memory 分配给适当的数组。如果代码是缩进的,并且完整的脚本都在一个地方,这也会有所帮助——而不是强迫我们从不相交的片段中组装一个脚本。

标签: arrays bash shell loops for-loop


【解决方案1】:

第一个for循环可以简化为:

array=( $(ps aux | awk '$4 != 0.0 && $4 !~ /MEM/ {print $11,$4}' | sort) )

当写成单个片段时,第二个循环是:

N=0
for i in `seq 0 ${#array[@]}`
do
    if [ $(( $i % 2 )) -eq 0 ]
    then arrayb[$N]=${array[$i]}
    else arrayc[$N]=${array[$i]}
    fi
    ((N++))
done

您每次迭代都会增加 N,因此您有包含名称的 arrayb[0] 和包含相应内存使用情况的 arrayc[1]。您可能应该使用:

for i in `seq 0 ${#array[@]}`
do
    if [ $(( $i % 2 )) -eq 0 ]
    then arrayb[$((i/2))]=${array[$i]}
    else arrayc[$((i/2))]=${array[$i]}
    fi
done

但是,您可能会再次将第一个分配与第二个循环结合起来做得更好:

ps aux | awk '$4 != 0.0 && $4 !~ /MEM/ {print $11,$4}' | sort |
while read name memory
do
     arrayb+=($name)
     arrayc+=($memory)
done

这里唯一的障碍是数组设置在一个退出的子shell中。幸运的是,bash 提供了Process Substitution 来规避这个问题:

arrayb=()
arrayc=()
while read name memory
do
     arrayb+=($name)
     arrayc+=($memory)
done < <(ps aux | awk '$4 != 0.0 && $4 !~ /MEM/ {print $11,$4}' | sort)

那么你的代码目前写成:

N=0
c=0

for i in `seq 0 ${#arrayb[@]}`; do

    let "B= $N - 1"

    A=`echo ${arrayb[$B]} | tr -d '\n'`
    B=`echo ${arrayb[$N]} | tr -d '\n'`
    echo $c
    echo -n "$A" "$B"
    echo ""
    let "c=$c+1"
    let "N= $N + 1"

done

您遇到了问题,因为您访问了两次arrayb 并忽略了arrayc。您似乎也在通过扭曲来撤消第二个循环的原始代码中不需要的增量。

使用反引号通常是个坏主意;请改用$(...)tr -d '\n' 应该是不必要的。使用echo -n "$A" "$B" 紧跟echo "" 很奇怪:echo "$A" "$B" 就足够了吗?

for i in $(seq 0 ${#arrayb[@]})
do
    echo "${arrayb[$i]}" "${arrayc[$i]}"
done

因此,我们可以将目前提供的脚本简化为:

arrayb=()
arrayc=()
while read name memory
do
     arrayb+=($name)
     arrayc+=($memory)
done < <(ps aux | awk '$4 != 0.0 && $4 !~ /MEM/ {print $11,$4}' | sort)

for i in $(seq 0 ${#arrayb[@]})
do
    echo "${arrayb[$i]}" "${arrayc[$i]}"
done

从这里,您可以继续使用两个数组,arraybarrayc,它们的组织方式使匹配的索引包含可比较的值。

【讨论】:

  • 我知道它说不要使用 cmets 表示感谢,但你的代码很简洁,你的解释很冗长。虽然我仍在审查您在此处提供的一些概念,但我想说声谢谢。
最近更新 更多