Ajuda para exibir progresso de comando em segundo plano (Script)

Olá senhores!

Em um script, coloco um comando para ser executado em segundo plano e pego o PID dele. Como exemplo estou usando o sleep. Mas na verdade a ideia é poder incorporar com diversos outros. Como copia de um determinado arquivo/diretório, processo de atualização, e por aí vai…

#!/bin/bash
tput civis

#Comando que será jogado em segundo plano
sleep 5 &

#Obter o PID do comando
export Com_PID=$!

#Declara conteúdo da variável de validação para que possa ser possível iniciar o loop
teste="true"

#Indicador simples da execução do progresso
echo -n "Começou"

while [ ! -z "${teste}" ]; do
	teste=""
	echo -n "."
	teste=`ps -ef | grep "${Com_PID}"`
done

echo -e "\nTerminou!"

tput cnorm
exit 0;

Da forma como está o “.” será exibido infinitamente até que o comando em segundo plano finalize.

Exemplo da saída

Outras saídas
...
...
Outras saídas

Começou...............................................................................................................................................................................................................................................................................................................
Terminou!

Queria uma forma de fazer com que quando chegasse a uma determinada quantidade de repetições, ela zerasse e recomeçasse. Não dá para usar o clear, porque a intensão é continuar exibindo as outras saídas anteriores.

Desde já agradeço a ajuda!

1 Curtida

Tem a opção de echo -e '\r' ou printf '\r'. O \r volta o cursor para o começo da linha (mas não apaga o que tem nela). Daí que usar um pouco da criatividade para fazer um indicador de progresso que nunca muda de comprimento, ou preencher o resto da linha com espaços para “apagar.”

Uma maneira relativamente simples de fazer isso, se eu entendi bem seu problema:

pontos='.'
maxpontos=30
while CONDICAO; do
printf "\rComeçou! %-${maxpontos}s" "$pontos"
pontos+='.'
((${#pontos}>maxpontos)) && pontos='.'
done

Tem uma maneira de verificar se um PID existe, sem chamar dois programas externos:

sleep 5 &
PID=$!

while kill -0 "$PID" 2>/dev/null; do
# Preencher a barra.
done

kill -0 apenas vê se o PID existe, nada demais.

(qualquer comando pode ficar no while na verdade, até o seu ps -ef | grep "${Com_PID}" - só suprimir a saída com >/dev/null 2>&1)

3 Curtidas

Aliado a dica do @Capezotte você pode usar echo -ne ao invés de echo -e, assim o echo não adiciona uma linha em branco, outro “hack” é mover o cursor uma linha acima com:

tput cuu1

Assim você começa sobrescrever a linha acima da linha atual

echo -ne "\r"

Assim basta preencher a linha com espaços, pode ser usando o printf:

printf ' %.0s' {1..$(tput cols)}

Esse tput cols retorna o número de caracteres numa frase


Isso aí serve pra transformar a linha acima em espaços em branco

3 Curtidas

Muito obrigado pelas dicas senhores @Natanael.755 e @Capezotte!

Acredito que vou testar ainda hoje e volto com o feedback!

Senhores… É bem isso que preciso mesmo. Agradeço a ajuda de vocês!

@Capezotte, se não fosse pedir muito, você poderia explicar detalhadamente este trecho do código? Eu meio que entendi por alto. Mas gostaria de saber exatamente o que cada detalhe dessas linhas estão fazendo.

Também achei bem interessante esta parte @Natanael.755! Mas confesso que não compreendi muito bem. Será que você poderia também fazer a gentileza de criar um exemplo de da sintaxe explicando cada detalhe?

Desculpem se eu estiver sendo ignorante! É que como iniciante, tenho procurado sempre aprender como que a solução funciona. É meio que quero que me ensinem a pescar. :sweat_smile: :sweat_smile:

Mais uma vez, agradeço pela colaboração de vocês!

printf "\rComeçou! %-${maxpontos}s" "$pontos"

‘printf’ é um comando que tem como primeiro parâmetro especificação do formato. Esse formato é uma variação do usado pela função de mesmo nome no C. Depois, esse formato é adaptado pelos parâmetros que vêm depois.
O formato, no caso, é "\rComeçou! %-${maxpontos}s".

  • \r, como eu já expliquei, manda o terminal voltar para o começo da linha.
  • %s, dentro do formato, é substituído por uma sequência de letras (string). Que string? Exato, a “$pontos”.
  • %NÚMEROs é a versão de %s que imprime a string, mas, se ela não alcançar esse tamanho, acrescentam-se espaços antes:
    Exemplo: printf %5s mãe → ’ mãe’
  • %-NÚMEROs orienta para os espaços serem colocados depois.
    Exemplo: printf %-5s mãe → 'mãe ’

É interessante colocar os espaços no nosso caso, pois o \r não apaga a linha sozinho. O NÚMERO do %NÚMEROs e %-NÚMEROs é, nesse caso, $maxpontos, que no exemplo é 30.

Tem um bocado de exemplos pela net, e o famoso manual, que pode ser lido com man printf.1 .

pontos+='.'

Acrescenta ‘.’ no final da string pontos, nada demais.

((${#pontos}>maxpontos)) && pontos='.'

(( )) faz uma operação com números e retorna sucesso se ela for verdadeira.
${#VARIÁVEL} retorna quantos caracteres tem em VARIÁVEL, e && executa o comando depois só se o que vem antes retornar sucesso (basicamente um encurtamento de if).
Ou seja, o segundo comando (redefinir pontos para apenas ‘.’) apenas roda se houver mais caracteres na variável $pontos do que a quantidade máxima ($maxpontos) especificada.

Boa… Valeu mesmo!
Agora entendi direitinho!

Fiz um teste aqui com alguns comandos e com um deles não deu certo. O sudo apt update.

Senhores… Acho que encontrei a possível solução para a situação do sudo apt update ou outro que não dê certo da forma usada no início.

bash -c "COMANDO > /dev/null 2>&1;" &

Pelo que entendi entre diversas fontes aqui:

  • O bash -c "COMANDO" - executa o comando em uma espécie de nova instancia (não sei se esta é a definição correta) do terminal. a adição do & ao final da linha (como já sabemos) joga o todo o comando anterior para o segundo plano.
  • > /dev/null 2>&1; - Envia a saída do comando para o /dev/null e para o segundo plano daquela “segunda instância” do terminal que está executando o COMANDO no bash.

Não sei se compreendi corretamente ou se exagerei nas opções adicionais. Mas tentei somente de um e de outro modo e foram ineficazes separadamente. Mas unindo as duas ideias consegui realmente fazer com que ficasse em segundo plano exibindo somente a saída programada no while e exatamente pelo mesmo intervalo de tempo que acontece a execução de forma natural com tudo em primeiro plano. Então, acho que funcionou!

Abaixo deixo o exemplo de como ficou aqui.

#!/bin/bash
tput civis
#O "sudo clear" abaixo é somente para que o sudo funcione nos comando a seguir sem interrupção na execução deles
sudo clear
#Comando que será jogado em segundo plano
bash -c "sudo apt update > /dev/null 2>&1; sudo apt upgrade -y > /dev/null 2>&1;" &
PID=$!

maxRep=15

#Indicador simples da execução do progresso
while kill -0 "$PID" 2>/dev/null; do
printf "\rComeçou%-${maxRep}s" "${repeater}"
repeater+='.'
((${#repeater}>maxRep)) && repeater=""
sleep 0.4
done

echo -e "\nTerminou!"

tput cnorm
exit 0;

Agradeço as correções naquilo que precisar melhorar!

O que você fez para resolver o problema funciona, mas ainda podemos melhorar mais um pouco.

Você pode usar o recurso “nativo” para agrupar comandos dentro de scripts ({ e }), sem chamar o bash -c por fora. (atenção que tem que ter ; antes do } :

{ sudo apt update; sudo apt upgrade -y; } &
# Você pode inclusive suprimir a saída do conjunto, tornando o código mais conciso
{ sudo apt update; sudo apt upgrade -y; } >/dev/null 2>&1 &
1 Curtida

Show de bola!

Testei aqui e funcionou de boa.

Haveria alguma forma de utilizar o echo ao invés do printf?
Pergunto porque vi que no echo -e posso aplicar formatações ao conteúdo (negrito, itálico, cores…).
Tentei e não consegui no printf.

Tecnicamente tem, mas você ia ter que calcular o número de espaços sozinhos. Mas existe uma solução para seu problema com o printf '%b', que faz o mesmo do echo -e.

printf '%b' '\nTerminou!'

Inclusive, isso dá um pouco de flexibilidade a mais. Você pode misturar partes que seriam equivalentes ao echo -e, e partes equivalentes ao echo simples:

printf '%b%s\n' '\e[35m' 'Este texto foi colorido com \e[35m!'

image

O primeiro parâmetro depois do formato recebe tratamento de echo -e (fica associado ao %b) e o segundo de echo normal (associado ao %s).

1 Curtida

Exatamente isso cara!

valeu mesmo!

Cara, por ultimo entre as possibilidades de expansão dessa ideia, queria te perguntar algo um pouco fora do contexto mas que ainda assim se encaixaria no escopo geral deste script.

Caso eu use o lolcat em uma saída posso animar o efeito por um determinado tempo:

echo "Teste" | lolcat -a -d100

O -d100 é o que determina a duração do efeito. Ha veria alguma forma de fazer com que esta duração fosse exatamente a mesma que é pega no laço while?

Valeu mesmo pela paciência e disposição!

Você quer dizer, fazer rodar o while e o lolcat juntos, e assim que while terminar, levar o lolcat junto?

Isso já exigiria rodar o lolcat no fundo também (além da tarefa principal), você teria que armazenar o PID dele também, e usar traps para garantir que ele ia ser fechado mesmo que ocorresse algum imprevisto (como você apertando Ctrl+C, ou finalizando o script no gerenciador de tarefas).

Muita complexidade, ao troco de não muita coisa. Bem mais negócio chamar lolcat, sem animação mesmo, a cada loop (e inicializar ele com algum valor que você controle, e que aumente a cada loop).

1 Curtida

Realmente… Imaginei mesmo que seria algo bom complicado!