Shell-Script

如何刪除在AR2valuesfrom在一種R2在一種l在和sFr這米VAR2 values fromVAR1 並將剩餘的值輸出到 $VAR3?

  • November 9, 2020

**作業系統:**核心 2.6.x

外殼: POSIX 兼容外殼

實用程序: BusyBox 1.25

**問題:**如何刪除中的值 $ VAR2 from $ VAR1 並將剩餘值輸出到 $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"

首先,簡單但有限的變體

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值(它們通過管道傳送到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.
  1. 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"

看起來這些是您編碼的字元串列表,通過將它們以空格分隔的方式儲存在標量變數中(假設字元串不包含該空格字元)。

使用列表/數組類型的變數以及支持它們的外殼會更有意義。例如, withzsh及其${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包含至少一個元素(你如何用一個空元素表示一個與空列表不同的列表,順便說一句),並且元素不包含換行符)。

所以你不妨手動實現它。遍歷第一個列表的每個元素並在第二個列表中查找它們。

在 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

如果可能有空元素(如 inVAR1=' 2 3'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"and""而不僅僅是"foo"例如在 POSIX shell(大多數,因為這是 POSIX 要求)中,將$IFS其視為欄位終止符而不是separator

或者您可以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}')

引用自:https://unix.stackexchange.com/questions/394204