Bash: 변수 할당이 "고착"되지 않는 것 같습니다.

Bash: 변수 할당이 "고착"되지 않는 것 같습니다.

while 루프 내에서 테스트가 실패할 때 "부울" 플래그 변수가 false로 유지되지 않는 이유를 추적하는 데 문제가 있습니다.

스크립트에는 몇 가지 루프가 포함되어 있지만 본질적으로 그 목적은 크론이 실행될 때 매일 아침 두 개의 무작위 앨범을 재생하는 것입니다. 한때 두 개의 앨범을 선택하는 간단한 한 줄 스크립트가 있었지만 재생하고 싶지 않은 앨범(예: 크리스마스 음악 앨범)을 블랙리스트에 추가하고 싶었습니다.

이를 위해 듣고 싶지 않은 앨범 이름이 포함된 wakeUp_blacklist.txt 파일을 읽도록 하여 일치하는 항목을 거부할 수 있도록 했습니다. 블랙리스트에 등록된 앨범을 무작위로 선택한 앨범과 한 줄씩 비교하여 일치 여부를 테스트합니다. 일치하는 항목이 발견되면 해당 일치 항목을 실패( pass = false)로 표시하고 루프백하여 다른 앨범을 선택해야 합니다. 어떤 이유에서인지 실행이 read | while루프를 종료하면 플래그가 다시 true로 전환되므로 다른 앨범은 선택되지 않습니다.

내 코드와 샘플 출력은 다음과 같습니다.

#!/bin/bash

wd="/media/External1/albums"

pass="false"
for ((i=0; i<=1; i++)); do
    while [ "$pass" == "false" ]; do
        album=`ls -d "$wd"/*/*/ | sort -R | tail -n 1`
        echo "album selected:"
        echo "$album"
        pass="true"
        echo "pass reset; pass=$pass"
        cat "$wd/wakeUp_blacklist.txt" | while read black; do
            echo "pass check 2:$pass"
            echo "black: $wd/$black"
            if [ "$album" == "$wd/$black" ] || [ "$album\n" == "$albums" ]; then
                pass="false"
                echo "test failed; pass=$pass"
            fi
            echo "pass check 1:$pass"
        done
        echo "Blacklist check complete; pass=$pass" #Why is it true again?!
    done
    pass="false"
    albums="$albums$album\n"
    echo "album assigned:"
    echo "$album"
done

echo
echo

for album in "$albums"; do
#    play "$album*"
     echo -e "$album" #List album names rather than actually play them
done

따라서 몇 번 실행하면 결국 블랙리스트에 있는 앨범을 선택하고 다음과 같은 출력을 제공합니다.

album selected:
/media/External1/albums/Adele/21/
pass reset; pass=true
pass check 2:true
black: /media/External1/albums/Vince_Guaraldi_Trio/A_Charlie_Brown_Christmas/
pass check 1:true
pass check 2:true
black: /media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
pass check 1:true
Blacklist check complete; pass=true
album assigned:
/media/External1/albums/Adele/21/
album selected:
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
pass reset; pass=true
pass check 2:true
black: /media/External1/albums/Vince_Guaraldi_Trio/A_Charlie_Brown_Christmas/
pass check 1:true
pass check 2:true
black: /media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/
test failed; pass=false
pass check 1:false
Blacklist check complete; pass=true
album assigned:
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/


/media/External1/albums/Adele/21/
/media/External1/albums/Sound_Inventions/Stille_Nacht-_A_German_Christmas/

마지막에 블랙리스트 항목이 선택한 앨범(German_Christmas)과 일치하면 테스트가 실패하고 변수가 pass있지만 false루프 외부 값의 첫 번째 출력을 확인하면 다시 true로 표시되는 것을 볼 수 있습니다!passread | while

내 스크립트가 충분히 효율적이지 않을 수 있다는 것을 알고 있지만 계속하기 전에 여기서 무슨 일이 일어나고 있는지 이해하고 싶습니다. 어떤 제안이 있으십니까?

답변1

당신은:

cat "$wd/wakeUp_blacklist.txt" | while read black; do

큰 타격을 입은 상태에서:

파이프라인의 각 명령은 자체 하위 셸에서 실행됩니다.

서브쉘은bash자체 상태가 있는 의 또 다른 인스턴스입니다 . 이것은 당신이오른쪽개념적으로 |다른 인스턴스에 "속합니다" bash- 처음의 기존 값과 선언은 복사되지만 변수는 저장 위치가 다르며 이에 대한 수정 사항은 해당 서브셸 내에서만 볼 수 있습니다(이렇게 작동한다고 생각할 수 있습니다)fork, 원한다면).

서브셸이 완료되면 변수 복사본이 더 이상 존재하지 않습니다. "외부" 셸은 수정되지 않은 자체 변수 복사본만 볼 수 있으므로 루프 외부에서 수정 사항이 손실되는 것처럼 보입니다.


이 경우 서브쉘 사용을 피할 수 있습니다간단한 리디렉션:

    while read black; do
        ...
        pass="false"
        ...
    done < "$wd/wakeUp_blacklist.txt"

이제 while루프는 기본 셸에서 실행되며 하위 셸은 생성되지 않습니다. 블랙리스트 파일의 내용은 여전히 ​​루프의 표준 입력으로 제공됩니다.< file리디렉션. 모든 변수는 동일한 환경에 존재합니다. 이 접근 방식은 또한쓸모없는 사용cat.

일부 다른 쉘(특히 zsh)에는 처음부터 이 동작이 없으며 파이프 오른쪽에 있는 변수를 자유롭게 수정할 수 있습니다.

관련 정보