如果值不在 Shell 脚本中的 Array2 中而不循环,则删除或替换 Array1 中的值?

Remove or Replace Value in Array1 If Value Isn't in Array2 in Shell Script Without Looping?

编辑:


我已将 allVals 的定义更改为有点 cleaner/simpler:

allVals=( $( printf '%s\n' \
               $( printf '%s\n' \
                    {0..9}{0..9}{0,3,5,7} | \
                    sed -e 's#^0*##g' ) | \
               awk '>='"$valMin"' && <='"$valMax" ) \
            ${exptVals[@]} )

我有一个简短的 BASH script 用于为辅助可执行文件生成 space 分隔的配置文件。脚本部分正在确定将哪些值打印到第 1 列。

为此,我的脚本使用 brace expansion 创建具有以下规则的整数数组:

我使用 sed 来清理第二个最重要数字为空的情况(我可能会用 '0' 替换 ' ' 并通过删除前导 '0' 来写一个更简单的等价物,当我解决它...)。

无论如何,这些是我输入到第二个程序中的值,用于为某些属性生成计算预测。我还想确保包括与我拥有的某些实验值相对应的数字...所以我通过创建一个实验值数组然后将两个数组合并在一起,对它们进行排序并删除冗余值来做到这一点。

下面给出了脚本(这是一个单行本 -- 为了便于阅读,我已将其编辑为脚本形式):

#!/bin/bash
lineItem5=61
valMax=433
valMin=260
exptVals=( 257 261 265 269 273 277 281 285 289 293 297 \
           301 305 309 313 317 321 325 329 333 337 341 \
           345 349 353 357 361 365 369 373 377 381 385 \
           389 393 397 401 405 409 413 417 421 425 429 \
           433 )
allVals=( $( printf '%s\n' \
            $( printf '%s\n' {' ',{1..9}}{' ',{1..9}}{0,3,5,7} | \
                 sed -e 's# \([1-9]\) 0 [1-9] 3 [1-9] 5 [1-9] 7 # 0 3 5 7 #g' ) | \
            awk '>='"$valMin"' && <='"$valMax" ) \
         ${exptVals[@]} )
sortVals=( $( printf '%s\n' ${allVals[@]} | sort -nr | uniq ) )
for ((t=0;t<${#sortVals[@]};t++)); do
  printf '%s\n' "${sortVals[t]}"'     -4000   -4000   200        '"${lineItem5}"'   -1.0'
done
unset exptVals allVals sortVals

它有效,但我想减少行数(这相当于评估的点,因此等于计算成本)并改善值的间距(这提高了我的统计准确性,因为输出属性的每个点都取决于根据之前的计算)。

具体来说,如果遇到序列 ##7 ##8,我想删除值 ##7,同样,如果遇到序列 ##2 ##3...,我想删除值 ##3...如果在我的实验值列表中找不到 ##3##7 值。我还想将 ##3 ##4 更改为 ##2 ##4 并将 ##6 ##7 更改为 ##6 ##8 以改善间距 - 但前提是 ##3##7不在实验序列中。

到目前为止,我能想到的最好的方法是做类似

的事情
valStart=0
for ((e=0; e<${#exptVals[@]}; e++)); do
  for ((v=valStart; v<${#allT[@]}; v++)); do
     if [[ ${allVals[v]} -ge ${exptVals[$((e+1))]} ]]; then
       valStart=v
       break
    else
       #Do edits to list here... 
     fi
  done
done

代码尚未完成,但我认为它的效率适中,因为我不必完全遍历第二个列表...只是其中的一小部分(我的实验列表是有序的) .

但我觉得有更简单的方法 delete 'Y' from 'X Y' if 'Y' is not in array $valschange 'Y' to 'Z' for 'X Y' if 'Y' is not in array $vals?

有没有一种简单的方法可以在单个表达式中使用某种内置完成:

...这不涉及在 bash 式循环(我的蛮力方法)中遍历值?

为什么不使用 awk 将数字生成为数字序列,而不是按模式生成数字?

例如,

$ awk -v from=100 -v to=200 -v ORS=' ' 'BEGIN{for(i=from;i<=to-10;i+=10)
                                 print i,i+3,i+5,i+7; ORS="\n"; print""}'

100 103 105 107 110 113 115 117 120 123 125 127 130 133 135 137 140 143 145 147 150 153 155 157 160 163 165 167 170 173 175 177 180 183 185 187 190 193 195 197

您的脚本会调用 sed 和 awk 来删除由您使用的大括号扩展创建的空格。一个更简单的大括号扩展是:

$ echo {0..9}{0..9}{0,3,5,7}

前导 0 的问题很容易用 printf '%3.0f' 解决。
将创建一个较短的列表(作为示例):

$ printf '%3.0f ' {0..1}{0..9}{0,3,5,7}
  0   3   5   7  10  13  15  17  20  23  25  27  30  33  35  37  40  43  
 45  47  50  53  55  57  60  63  65  67  70  73  75  77  80  83  85  87  
 90  93  95  97 100 103 105 107 110 113 115 117 120 123 125 127 130 133  
135 137 140 143 145 147 150 153 155 157 160 163 165 167 170 173 175 177  
180 183 185 187 190 193 195 197

解决此问题后,我们需要将值限制在 valMinvalMax 之间。

与其调用外部 awk 来处理短列表,不如使用循环。通过排序(仅调用一次)和打印,此脚本的功能与您的脚本大致相同,但外部调用却少得多:

#!/bin/bash
lineItem5=61  valMin=260  valMax=433  

exptVals=( 257 261 265 269 273 277 281 285 289 293 297 \
           301 305 309 313 317 321 325 329 333 337 341 \
           345 349 353 357 361 365 369 373 377 381 385 \
           389 393 397 401 405 409 413 417 421 425 429 \
           433 )

for    v   in $( printf '%3.0f\n' {0..9}{0..9}{0,3,5,7} )
do     (( v>=valMin && v<=valMax )) && allVals+=( "$v" )
done

sortVals=( $(printf '%s\n' "${allVals[@]}" "${exptVals[@]}"|sort -nu) )
printf '%s ' "${sortVals[@]}"

现在我们进入您问题的核心。如何:

remove the value ##7 if the sequence ##7 ##8 is encountered

通常的做法是调用 sed。类似于:

printf '%s ' "${sortVals[@]}" | sed -e 's/\(..7 \)\(..8\)//g'

这会将 ..7 ..8 转换为 ..8(反向引用 \2)。

然后,您可以添加更多过滤器以进行更多更改。类似于:

printf '%s ' "${sortVals[@]}"          |
    sed -e 's/\(..7 \)\(..8\)//g'    |
    sed -e 's/\(..\)3\( ..4\)//g' 
echo

这将解决 ..7 ..8..8..3 ..4..2 ..4 项。

但是您的要求是:

but only if the ##3 or ##7 value is not found in my list

见面比较复杂。我们需要使用 grep 扫描所有值,并为每个选项执行不同的代码。一种常用的解决方案是使用 grep:

if printf '%s ' "${sortVals[@]}" | grep -Eq '..3|..7'; then
        cmd2=(cat)
else
        cmd2=(sed -e 's/\(..2\)\( ..3\)//g')
fi

但这意味着针对每个条件使用 grep 扫描所有值。
创建的命令:cmd2 是一个数组,可以这样使用:

printf '%s ' "${sortVals[@]}"          |
    sed -e 's/\(..7 \)\(..8\)//g'    |  "${cmd2[@]}"  | 
    sed -e 's/\(..\)3\( ..4\)//g' |  "${cmd4[@]}"
echo

没有 grep

您正在测试的值只是最后一位,可以使用模 10 数学运算轻松提取。并制作 easier/faster 值的测试,我们可以像这样创建一个索引数组:

unset indexVals; declare -A indexVals
for v in "${sortVals[@]}"; do  indexVals[$((v%10))]=1; done

这只是一次值扫描,没有调用外部工具,并且大大简化了值测试(例如,对于 ..2..3):

(( ${indexVals[2]-0} || ${indexVals[3]-0} ))

包含所有更改的脚本是:

#!/bin/bash
lineItem5=61  valMin=260  valMax=433  

exptVals=( 257 261 265 269 273 277 281 285 289 293 297 \
           301 305 309 313 317 321 325 329 333 337 341 \
           345 349 353 357 361 365 369 373 377 381 385 \
           389 393 397 401 405 409 413 417 421 425 429 \
           433 )

for    v   in $( printf '%3.0f\n' {0..9}{0..9}{0,3,5,7} )
do     (( v>=valMin && v<=valMax )) && allVals+=( "$v" )
done

sortVals=( $(printf '%s\n' "${allVals[@]}" "${exptVals[@]}" | sort -nu) )

unset indexVals; declare -A indexVals
for v in "${sortVals[@]}"; do  indexVals[$((v%10))]=1; done

cmd1=( sed -e 's/\(..7 \)\(..8\)//g' )

(( ${indexVals[2]-0} || ${indexVals[3]-0} )) &&
    cmd2=( cat ) ||
    cmd2=( sed -e 's/\(..2\)\( ..3\)//g' )

cmd3=( sed -e 's/\(..\)3\( ..4\)//g' )

(( ${indexVals[3]-0} || ${indexVals[7]-0} )) &&
    cmd4=( cat ) ||
    cmd4=( sed -e 's/\(..6 ..\)7//g' )

printf '%s ' "${sortVals[@]}" | "${cmd1[@]}" | "${cmd2[@]}" | 
                                "${cmd3[@]}" | "${cmd4[@]}" ; echo