목록의 항목이 예상대로 작동하지 않는지 확인하는 Bash

목록의 항목이 예상대로 작동하지 않는지 확인하는 Bash

다음을 기반으로 하는 조건이 있는 스크립트를 작성하려고 합니다.변수가 목록에 나타납니다:

#!/bin/bash

LIST=`ls`

function listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && return 0 || return 1
}

if [ $(listcontains "${LIST}" "multi.sh") ] ; then
     echo "Found!"
else
    echo "Failed :("
fi

$(listcontains "${LIST}" "multi.sh")
echo returned $?

목록에 "multi.sh"라는 파일이 있으므로 "Found!"가 예상되지만 위의 스크립트에서는 "Failed:("를 보고합니다. 후속 호출은 0을 반환합니다.

나는 노력했다

if [ 0 -eq $(listcontains "${LIST}" "multi.sh") ] ; then

그런데 오류가 발생합니다.

./script.sh: 9행: [: 0: 단항 연산자 필요

실패:(

내가 여기서 무엇을 놓치고 있는 걸까요?

답변1

if [ $(listcontains "${LIST}" "multi.sh") ] ; then

함수에서 아무 것도 인쇄하지 않으므로 명령 대체는 토큰화 후에 어떤 필드도 생성하지 않습니다. 이는 실행과 동일하며 if [ ]; then ...사이에 매개변수가 없으면 false 상태를 반환합니다.[][

따옴표를 사용하여 이 작업을 수행 하면 빈 문자열을 like if [ "$(listcontains...)" ]; then에 전달 하고 false 상태도 반환하므로 도움이 되지 않습니다. (괄호 사이에 매개변수가 있으며, 해당 매개변수가 null이 아닌지 확인합니다.)[[ "" ]

명령 대체 자체의 종료 상태는 실행할 다른 명령이 없는 경우에만 표시됩니다. $(listcontains ...)이는 나중에 한 줄에서 명령을 단독으로 실행하는 경우입니다.

명령 대체를 통해 테스트하려면 무언가를 인쇄해야 합니다. 예를 들어

listcontains() {
    if ...; then
        echo yes
    fi # else print nothing
}
if [ "$(listcontains ...)" ]; then
    echo ok
fi

(또는 와 함께 if [ "$(listcontains ...)" = yes ]; then ...)

그러나 종료 상태를 직접 볼 수 있으므로 이는 필요하지 않습니다.

listcontains() {
    if [[ ... ]]; then
        return 0
    fi
    return 1
}
if listcontains ...; then
    echo ok
fi

함수의 종료 상태는 마지막 명령의 종료 상태이므로 함수를 다음과 같이 단순화할 수 있습니다.

listcontains() {
    [[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]]
}

$2그러나 내용에 정규식 특수 문자가 있더라도 내용이 문자 그대로 처리되도록 인용을 원할 것입니다 .

예를 들어 위의 경우 단일 문자와 일치하므로 listcontains 'foo matchxsh bar' match.sh일치 항목이 검색됩니다 . .일치하지 않는 괄호와 같은 문제가 발생할 수 있습니다.

일치하기 전에 문자열의 시작과 끝 부분에 공백을 넣어서 길이를 줄일 수도 있으므로 BOL/EOL을 누르는 것에 대해 신경 쓸 필요가 없습니다.

listcontains() {
    [[ " $1 " =~ [[:space:]]"$2"[[:space:]] ]]
}

또는 더 많은 POSIXly를 사용하여 Dash에서도 사용할 수 있습니다.

listcontains() {
    case " $1 " in
        *[[:space:]]"$2"[[:space:]]*) return 0;;
        *) return 1;;
    esac
}

답변2

항목이 목록에 있는지 확인하는 가장 쉬운 방법 중 하나는 목록을 연관 배열, 즉 "해시"(항목은 키와 임의의 값임)로 변환한 다음 원하는 항목이 있는지 테스트하는 것입니다. 배열의 인덱스입니다.

나는 보통 각 키의 값으로 "0"이나 "1"을 사용합니다. 때로는 빈 문자열과 비어 있지 않은 문자열을 테스트하기도 합니다. 그것은 주로 내가 사용하는 언어와 그것이 옳고 그름을 고려하는 것에 달려 있습니다.

효과적으로 이는 연관 배열을 간단한 방법으로 사용하여 수행됩니다.놓다집합 멤버십을 테스트합니다(키에 값이 있으면 멤버이고, 그렇지 않으면 멤버가 아닙니다). 따라서 무엇을 테스트하고 어떻게 테스트할지 아는 한 값은 중요하지 않습니다.

정규식 일치가 필요하지 않으며 간단한 테스트만 하면 됩니다. 내가 찾고 있는 항목이 연관 배열의 키인가요?

컬렉션 멤버십 테스트도 빠릅니다. 일회성 테스트의 경우 성능은 중요하지 않지만 많은 수의 잠재적 컬렉션 멤버를 테스트하는 경우 성능이 중요합니다. 이는 쉘과 같이 매우 느린 언어의 경우 특히 그렇습니다.

다음은 인덱스 배열에 포함된 목록을 사용하는 예입니다.

$ items=(item1 item2 item3 item4)

$ declare -A itemhash
$ for i in "${items[@]}" ; do itemhash[$i]=1 ; done

현재 인덱스 배열과 연관 배열에 포함된 내용은 다음과 같습니다.

$ declare -p items itemhash
declare -a items=([0]="item1" [1]="item2" [2]="item3" [3]="item4")
declare -A itemhash=([item1]="1" [item2]="1" [item3]="1" [item4]="1" )

좋습니다. 해시(연관 배열)가 채워졌습니다. 이제 여기에 특정 항목이 포함되어 있는지 테스트할 수 있습니다.

$ if [ "${itemhash[item1]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

$ if [ "${itemhash[item5]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

이 방법은 항목의 소스(인덱스 배열, 파일 이름 목록, 데이터베이스 쿼리 출력 등)에 관계없이 거의 모든 목록에서 작동하며 해시가 채워지는 방식은 중요하지 않습니다. 해시의 키는 항목의 이름이어야 하며, 각 키의 값은 쉽게 테스트할 수 있는 것이어야 합니다.


큰 소음을 유발할 수 있으므로 파일 이름과 관련된 예제를 사용해서는 안 됩니다 ls. 그러나 여기에 테스트되는 현재 디렉터리의 파일 이름 목록에 "multi.sh" 파일 이름을 사용하는 또 다른 예가 있습니다.

첫째, multi.sh현재 디렉터리가 존재하지 않는 경우:

$ declare -A foo
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
not in array

그런 다음 만들고 multi.sh다시 시도하세요.

$ unset foo ; declare -A foo
$ touch multi.sh
$ while read -d '' -r f; do foo[$f]=1 ; done < <(printf '%s\0' *)
$ if [ "${foo[multi.sh]}" == 1 ] ; then echo in array ; else echo not in array ; fi
in array

printf '%s\0' *참고: 대신에 사용했습니다 ls.구문 분석된 출력 ls은 나쁜 생각입니다.. NUL로 구분된 파일 이름 목록을 읽는 것은 유효한 파일 이름, 심지어 개행 문자와 같은 성가신 문자가 포함된 파일 이름에서도 작동합니다.

그런데 최신 버전의 GNU에는 NUL로 구분된 출력 옵션이 ls있습니다 --zero. 저는 아직 그것을 사용할 의향이 없습니다. 차라리 printf ... *위의 또는 find.

관련 정보