如何从 $VAR1 中删除 $VAR2 值并将剩余值输出到 $VAR3?

如何从 $VAR1 中删除 $VAR2 值并将剩余值输出到 $VAR3?

操作系统:内核 2.6.x

壳:POSIX 兼容外壳

实用工具:忙碌盒1.25

问题:如何从 $VAR1 中删除 $VAR2 中的值并将剩余值输出到 $VAR3 ?变量中的每个值均以空格分隔。

逻辑:

VAR1="1 2 3 4 5"
VAR2="1 3 5"
for i in $VAR1
   if $i is not found in $VAR2; do
   append $i to $VAR3
   remove trailing space character
done

期望的输出:

VAR3="2 4"

答案1

看起来这些应该是字符串列表,您通过将它们存储在标量变量中以空格分隔来编码(假设字符串不包含该空格字符)。

使用列表/数组类型的变量以及支持它们的 shell 会更有意义。例如,与zsh和它的${varX:|varY}数组减法运算符

VAR1=(1 2 3 4 5)
VAR2=(1 3 5)
VAR3=(${VAR1:|VAR2})

VAR3=("${(@)VAR1:|VAR2}")保留空元素)

现在,如果您仅限于 POSIX sh,除了 之外,不支持数组$@,那么您必须更有创意。

列表交集和减法的标准命令是comm。但是列表必须以排序列表的形式提供,换行符分隔并且位于其名称作为参数传递的文件内(尽管-可以将它们用于表示标准输入)。

所以在这里,使用起来就变得很尴尬。如果您的系统支持/dev/fd/<n>特殊文件:

VAR3=$(printf '%s\n' "$VAR1" | tr ' ' '\n' | sort | {
  printf '%s\n' "$VAR2" | tr ' ' '\n' | sort |
    comm -23 /dev/fd/3 -
} 3<&0 | paste -sd ' ' -)

或者:

to_comm() { printf '%s\n' "$@" | tr ' ' '\n' | sort; }
from_comm() { paste -sd ' ' -; }
VAR3=$(to_comm "$VAR1" | { to_comm "$VAR2" | comm -23 /dev/fd/3 -;} 3<&0 |from_comm)

(还假设$VAR1至少包含一个元素(顺便说一句,您将如何与空列表不同地表达具有一个空元素的列表)并且该元素不包含换行符)。

所以你不妨手动实现它。循环第一个列表的每个元素并在第二个列表中查找它们。

在 POSIX shell 中,您可以使用 split+glob 运算符:

IFS=' ' # split on space
set -o noglob # we don't want the glob part
VAR3= sep=
for i in $VAR1; do
  case " $VAR2 " in
    (*" $i "*) ;;
    (*) VAR3=$VAR3$sep$i; sep=$IFS;;
  esac
done

VAR1=' 2 3'如果可能有空元素(例如 in或) ,则不能使用它VAR1='1 3'。为此,最好使用非空白分隔符(如下所示|),其分割规则不同:

VAR1='*|foo bar||blah' VAR2='|blah'
IFS='|' # split on |
set -o noglob # we don't want the glob part
VAR3= sep=
for i in $VAR1''; do
  # that $VAR1 split+glob invocation will split the content of $VAR1
  # into "*", "foo bar", "", "blah" while with IFS=" ", the empty
  # element wouldn't have been there as sequences of spaces would
  # have been seen as a single separator. 
  case "|$VAR2|" in
    (*"|$i|"*) ;;
    (*) VAR3=$VAR3$sep$i; sep=$IFS;;
  esac
done

in''$VAR1''为了确保foo|被分成"foo"和 而""不是仅仅"foo"在 POSIX shell 中(大多数,因为这是 POSIX 要求)被$IFS视为一个字段终结者代替分隔器

或者你可以使用awk

export VAR1 VAR2
VAR3=$(awk 'BEGIN{
  n = split(ENVIRON["VAR1"], a1, /[ ]/)
      split(ENVIRON["VAR2"], a2, /[ ]/)
  for (i in a2) in_a2[a2[i]]
  for (i = 1; i <= n; i++)
    if (! (a1[i] in in_a2)) $(++NF) = a1[i]
  print}')

答案2

第一种,简单但有限的变体

VAR3=$(printf "%d\n" $VAR1 $VAR2 | sort | uniq -u | tr '\n' ' ' | sed 's/\s$//)

$ echo "$VAR3"
2 4

主要缺陷是:它只留下VAR1变量的唯一值。也就是说,如果$VAR1一个值重复几次,则该值不会出现在 中$VAR3,因为它不是唯一的。

例子:

VAR1="1 2 2 3 4 4 4 5"
VAR2="1 3 5"
# the resulting VAR3 variable is empty
VAR3 = "" # because it is containing only unique values and `2` and `4` repeated few times in the `VAR1`, therefore, they are not unique.

# The right result should be
VAR3 = "2 2 4 4 4" 

二、更通用、更正确的变体

VAR3=$(printf "%s\n" $VAR2 | awk -v var1="$VAR1" '
{arr2[$1] = 1;}

END {
    size = split(var1, arr1); 
    for(i = 1; i <= size; i++) {
        if(!arr2[arr1[i]]) 
            printf "%s ", arr1[i];
    }
}' | sed 's/\s$//')

解释

  1. printf "%s\n" $VAR2- 将 转换$VAR2为列 - 每行一个值。
  2. awk ...-$VAR2从 中删除值$VAR1

    • {arr2[$1] = 1;}- 将所有VAR2值(它们awk通过管道传递到printf)放入数组中,其中值成为数组的索引。= 1公正的意思真的- 这个值存在。这个技巧给了我们下一个行为:第一个值出现创建数组元素,然后,如果相同的值再次出现,它会转到相同的数组索引,换句话说,当相同的值出现几次时,该项目不会改变。因此,最终我们获得了VAR2变量的所有唯一值。如果 VAR2="one three five",那么arr2将是:arr2[one] = 1, arr2[three] = 1, arr2[five] = 1
    • END { size = split(var1, arr1);- 当输入行结束(VAR2处理完成)时,我们将其拆分VAR1到数组中 - 每个值都进入单独的项目。如果VAR1="one two three four five",那么我们将得到以下数组:arr1[1] = one, arr1[2] = two, arr1[3] = three ...,依此类推。该split函数返回新创建的数组的大小。
    • if(!arr2[arr1[i]]) printf "%s ", arr1[i];- 然后,迭代arr1项目并检查是否arr2有该项目的索引。例如:i = 1; arr1[1] = "one"那么arr2[arr1[i]]就是这个- arr2[one]。该项目已存在,请勿打印。i = 2; arr1[2] = "two"。不arr2[two]存在,所以打印它。因此,我们打印 中的所有值arr1,这些值未出现在 中arr2
  3. sed 's/\s$//'- 删除尾随空格。

与第一种变体相比,这种方式的优点:

    # It can process strings
    VAR1="one two three four five"
    VAR2="one three five"
    # the resulting VAR3 variable
    VAR3 = "two four"

    # It doesn't remove multiple occurrence of one value in the VAR1
    VAR1="1 2 2 3 4 4 4 5"
    VAR2="1 3 5"
    # the resulting VAR3 variable
    VAR3 = "2 2 4 4 4"

答案3

> echo $VAR1 $VAR2 | tr ' ' '\n' | sort | uniq --unique | tr '\n' ' '
2 4

相关内容