$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

이는 공백으로 구분된 스칼라 변수에 저장하여 인코딩하는 문자열 목록이어야 합니다(문자열에 해당 공백 문자가 포함되어 있지 않다고 가정).

목록/배열 유형 변수와 이를 지원하는 셸을 사용하는 것이 더 합리적입니다. 예를 들어, with zsh및 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최소한 하나의 요소가 있고(빈 목록과 다르게 하나의 빈 요소가 있는 목록을 어떻게 표현하겠습니까?) 요소에 개행 문자가 포함되어 있지 않다고 가정합니다.

따라서 수동으로 구현하는 것이 좋습니다. 첫 번째 목록의 각 요소를 반복하여 두 번째 목록에서 찾습니다.

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- 열로 변환 - 행당 ​​하나의 값.
  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

관련 정보