Como eu mudo uma variável de uma função de fundo?

0

Eu tenho trabalhado em um script para meu pai que gerencia várias tarefas através de um script com muitas funções que se encaixam (propósitos de organização). Este script depende inteiramente de zenity para toda a interação do usuário (meu pai é realmente analfabeto em tecnologia {Age 60 +}).

O problema que estou tendo é que, uma vez que chamo a função transprog , ela não sairá do loop mesmo depois que a variável na qual o while loop depende for alterada fora da função.

O loop é chamado aqui:

copystat="1"
transprog & cp -rf "$netlocdirFIN/" "$transdir2/" # "transprog" is called here
copystat="0"
sleep 1
ret="$?"
echo "$ret"
transnew #calls next step/function

E esta é a função transprog :

transprog ()
{
i2="Im Working On It!"

(
while [ $copystat = 1 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1

# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1


# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1

done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
  --title="File Transfer In Progress" \
  --text="# Im Getting Warmed Up" \
  --percentage=0 \
  --auto-close \
  --auto-kill
}

Quando a cópia estiver concluída, o script continua: a barra de progresso do zenity está em segundo plano e realmente o confunde.

Como posso dizer à função em segundo plano para sair?

(o zenity usa os valores echo de 25,50,75 e 99 para alterar a "completude" da barra de progresso. e o echo "# (whatever)" para alterar o texto.)

tudo isso é algo para dizer a ele "Ei Duffus im bussy copiando coisas não me desligue"

Eu tentei a solução da Guss: (script separado para propósitos de teste)

#!/bin/bash

transtest ()
{

i2="Im Working On It!"

var=0
trap 'var=1' 2

(
while [ "$var" = 0 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1

# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1


done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
  --title="File Transfer In Progress" \
  --text="# Im Getting Warmed Up" \
  --percentage=0 \
  --auto-close \
  --auto-kill
}

transtest &
pid=$!
echo "$pid"
sleep 20 #code to copy? or just for this test?
echo "Killing Child: $pid"
kill -2 "$pid"
echo "wait"
wait
exit

vejo resultado de echo "wait" e ainda continua em loop.

    
por Desert_foxhole 04.02.2016 / 07:41

2 respostas

2

Você alterou o valor da variável condicional ( copystat ) fora do loop while (em execução em um subshell), que está fora do escopo do loop while . A menos que você tenha alguma condição dentro do próprio loop para acionar a condição de saída do loop ou usar um IPC , o loop continuará para sempre.

Portanto, a solução é incorporar a cópia ou qualquer outra lógica condicional dentro do próprio loop while para acionar a saída. Ou você pode pensar em mudar o design para torná-lo mais robusto em tais situações ou usar um IPC mencionado aqui .

    
por heemayl 04.02.2016 / 08:44
2

O problema que você tem é causado pelo subshell gerado quando você chama transprog para executar em segundo plano (o que o símbolo & faz). Você tem que entender que o seu script bash não é um programa, mas um shell script que se comporta de acordo com as regras do shell - um shell só pode executar um processo de cada vez e esperar que ele termine.

O " sistema de empregos " permite que você execute outro processo e não o espere, mas quando esse processo é uma função definida no bash, o bash gerará um "sub shell" que " copia " todas as variáveis e funções do original shell e acompanhe isso como um trabalho. O shell copiado não está ciente de que as alterações fizeram dele o shell pai - porque ele não está referenciando as variáveis do shell pai diretamente - apenas uma cópia delas.

O que você precisa fazer para seu loop de plano de fundo é usar um mecanismo Inter Process Communication ( IPC ) para permitir que o shell pai notifique o loop no subshell para abortar. Para uma sinalização tão simples, geralmente um "sinal POSIX" é apropriado.

O sinal mais apropriado para usar é o "sinal de interrupção", SIGINT (número de sinal 2), que é produzido quando você pressiona CTRL + C e normalmente interrompe seu script. Mas podemos capturar este sinal e atualizar uma variante local usando o comando interno trap .

Aqui está um exemplo simples:

#!/bin/bash

function test() {
  var=0
  trap 'var=1' 2
  while [ "$var" = 0 ]; do
    sleep 3
    echo 'still sleeping'
  done
  echo "done sleeping"
}

test &
pid=$!
echo $pid;
sleep 5
echo "Killing $pid"
kill -2 "$pid"
wait

Isso produzirá:

$ ./test.sh 
8359
still sleeping
Killing 8359
still sleeping
done sleeping

O que acontece aqui é que após 5 segundos o loop completou uma rodada e agora está dormindo na segunda rodada quando o pai usa o kill embutido para sinalizar a subshell. O sono do subshell é interrompido, ele recebe o sinal e atualiza a variável, depois retoma o sono Quando o loop desperta, ele envia o texto, retorna e vê que o var foi alterado e sai do loop.

Eu uso o wait embutido no pai apenas para garantir que o processo pai não saia antes do subshell. Essencialmente, "aguarda" que a sub-rede termine.

[UPDATE] Depois de olhar o OP um pouco mais, há mais alguns subshells acontecendo, e eu não quero lutar contra isso - subshells (além de funções) é uma boa maneira de gerenciar a complexidade em um script - mas isso faz com que você encontre o PID correto para enviar o sinal para um bit difícil.

Em vez disso, podemos usar outro mecanismo de IPC chamado "segmento de memória compartilhada". Isso permite que um processo aloque um lote de memória, atribua um nome a ele e permita que vários processos acessem a mesma memória (ela é abordada em detalhes no documento do IPC vinculado acima). Enquanto no Bash não podemos abordar diretamente esse recurso (ao contrário do que um programa C, por exemplo, pode fazer), podemos usar um mecanismo do Linux que expõe o IPC de memória compartilhada a qualquer programa através da categoria de dispositivo /dev/shm . Então, o script OP pode ser algo assim:

#!/bin/bash

transtest () {
  local sync="$1"
  i2="Im Working On It!"
  var=0
  (
    while [ -z "$(cat $sync)" ]; do sleep 1; done
    echo "# Your files are DONE TRANSFERING!"
    sleep 1
    echo "100"
  ) | zenity --progress --title="File Transfer In Progress" --text="# Im Getting Warmed Up" \
    --percentage=0 --pulsate --auto-close --auto-kill
}

sync=/dev/shm/syntest-$$ # allocate a shared memory segment name for this process
echo '' > $sync # init the shm
transtest $sync &
sleep 20
echo "Signaling done"
echo "done" > $sync
echo "waiting"
wait
# remove the shm segment. While not actually necessary (it will clear
# when you reboot), I like to keep a clean system
rm $sync 
exit

Basicamente, tratamos o segmento SHM como um arquivo e apenas armazenamos alguns dados nele. O loop continua lendo o "arquivo" e quando há conteúdo lá, ele se rompe.

Uma questão permanece: por que não apenas alocar um arquivo temporário (usando mktemp ) e usá-lo para sincronização? Bem - isso funcionaria quase o mesmo! A principal diferença, e porque eu prefiro SHM para esse tipo de coisa, é que não é realmente um arquivo e não está sujeito as operações de E / S do sistema de arquivos, erros de disco e os gostos, e se eu não conseguir limpá-lo, ele ir embora na próxima vez que o sistema inicializar.

    
por Guss 04.02.2016 / 08:56