Bash: A atribuição de variáveis não parece 'aderir' [duplicado]

4

Estou com dificuldades para rastrear o motivo pelo qual minha variável de sinalizador "booleano" não permanecerá falsa quando um teste falhar dentro de um loop while.

O script envolve alguns loops, mas essencialmente pretende-se tocar dois álbuns aleatórios todas as manhãs quando é acionado pelo cron. Eu costumava ter um roteiro de uma linha simples que selecionava dois álbuns, mas eu queria colocar na blacklist todos os álbuns que eu não queria tocar (ou seja, álbuns de música natalina).

Para fazer isso, eu li um arquivo wakeUp_blacklist.txt com os nomes dos álbuns que eu não queria ouvir, então ele pode rejeitar qualquer correspondência. Isso compara os álbuns na lista negra, linha por linha, com um álbum selecionado aleatoriamente e testes para uma partida. Se uma correspondência for encontrada, ela sinaliza a partida como uma falha ( pass = false ) e DEVE voltar a loop e selecionar um álbum diferente. Por algum motivo, assim que a execução sai do loop read | while , o sinalizador volta para true e, portanto, outro álbum nunca é selecionado!

Aqui está o meu código e um exemplo de saída:

#!/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

Então, se eu executar isso algumas vezes, ele acabará escolhendo um dos álbuns na lista negra e fornecerá uma saída semelhante a:

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/

Você pode ver que, no final, quando a entrada da lista negra é uma correspondência para o álbum selecionado (German_Christmas), o teste falha, a variável pass é false , mas a primeira saída que verifica o valor pass fora do read | while loop mostra que é verdade novamente!

Eu entendo que meu script pode não ser o mais eficiente possível, mas quero entender o que está acontecendo aqui antes de prosseguir. Alguma sugestão?

    
por Justin Frahm 15.09.2014 / 01:12

1 resposta

10

Você faz:

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

No Bash :

Each command in a pipeline is executed in its own subshell

Um subshell é outra instância de bash , com seu próprio estado. Isso significa que quaisquer alterações de variáveis feitas no lado direito de um | "pertencem" a uma instância diferente de bash - os valores e declarações existentes no início são copiados, mas as variáveis têm diferentes locais de armazenamento e as modificações são visíveis apenas dentro dessa sub-camada (você pode pensar nisso como se estivesse trabalhando como fork , se você quiser).

Quando a subshell é concluída, essa cópia da variável deixa de existir. O shell "externo" só vê sua cópia da variável, não modificada, e é por isso que parece que você está perdendo suas modificações fora do loop.

Nesse caso, você pode evitar o uso de um subshell com redirecionamento simples :

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

Agora, o loop while é executado no seu shell principal e nenhuma subshell é criada. O conteúdo do arquivo da lista negra ainda é fornecido como entrada padrão para o loop usando o < file redirecionamento . Todas as variáveis existem dentro do mesmo ambiente. Essa abordagem também evita o uso inútil de cat .

Algumas outras shells (notavelmente zsh ), não têm esse comportamento para começar, e você pode modificar variáveis do lado direito de pipelines livremente.

    
por 15.09.2014 / 01:25