$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

これは、スペースで区切られたスカラー変数に格納してエンコードする文字列のリストでなければなりません(文字列に対応するスペース文字が含まれていないと仮定)。

リスト/配列型変数とそれをサポートするシェルを使用する方が合理的です。たとえば、withzshと its${varX:|varY}配列減算演算子:

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

VAR3=("${(@)VAR1:|VAR2}")空の要素を保持)

shこれで、配列をサポートしていないPOSIXに制限されている場合は、$@より創造的でなければなりません。

リストの交差と減算のための標準コマンドはです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少なくとも1つの要素があり(空のリストとは異なり、1つの空の要素を持つリストをどのように表現しますか)、要素に改行文字が含まれていないとします。

したがって、手動で実装することをお勧めします。最初のリストの各要素を繰り返して、2番目のリストで見つけます。

POSIX シェルでは、次のように 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はPOSIXシェルでフィールドとして扱われず、合計に分割されるようにすること''です(ほとんどPOSIX要件なので)。$VAR1''foo|"foo""""foo"$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" 

2. より一般的で正しい変形

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- 列に変換 - 1行あたり1つの値。
  2. awk ...$VAR2- それから値を削除してください$VAR1

    • {arr2[$1] = 1;}- パイプで接続されたすべてのVAR2値を配列に入れます。ここで値は配列のインデックスになります。定義は意味するawkprintf= 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

関連情報