Quero executar um comando em paralelo em vários arquivos como parte de um fluxo de trabalho de CI do Github (em um executor Ubuntu) para acelerar o trabalho de CI. Também gostaria que o comando paralelo relatasse seu progresso.
Atualmente meu comando se parece com isso:
# ci/clang-tidy-parallel.sh
find src \
! -path "path/to/exclude/*" \
-type f \( -name "*.cpp" -o -name "*.h" \) \
| parallel --progress "clang-tidy-19 {}"
Isso funciona muito bem quando executado a partir de um shell na minha própria máquina: os trabalhos são executados em paralelo e uma única linha de saída é mostrada com quantos trabalhos estão em andamento e quantos já foram concluídos.
Entretanto, quando executado como parte do fluxo de trabalho do Github, a saída é meio desagradável:
- Ele imprime o erro
sh: 1: cannot open /dev/tty: No such device or address
várias vezes. - Ele imprime muito mais resultados de progresso do que o necessário. Algo como 1700 linhas de relatórios de progresso, enquanto há apenas cerca de 80 tarefas para executar. A maioria dessas linhas são duplicadas. Por exemplo, as primeiras linhas são:
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
local:4/0/100%/0.0s
Se eu executar o comando localmente e redirecionar o stderr para um arquivo, observo um comportamento semelhante
ci/clang-tidy-parallel.sh 2>log
Quando o comando for concluído, o arquivo de log conterá centenas de linhas de saída. (Embora não haja erros sobre /dev/tty ausente.)
Por outro lado, sem a --progress
opção, o trabalho fica parado, sem saída visível, até ser concluído, o que também não é desejável.
Existe uma maneira de configurar o GNU Parallel para que ele relate o progresso de uma forma amigável a ambientes não terminais? Em particular, eu gostaria que ele imprimisse apenas uma linha de saída quando o status de uma tarefa paralela fosse alterado (o que significaria receber uma linha por tarefa se tudo correr bem).
Agradeço ao Ole Tange por me apontar o caminho certo. Com base na solução dele e em alguma codificação assistida por IA, criei esta monstruosidade:
file_list=$(find src \
! -path "path/to/exclude/*" \
-type f \( -name "*.cpp" -o -name "*.h" \))
length=$(wc -w <<< "$file_list")
echo Running clang-tidy on $length files
echo "$file_list" |
parallel --bar "clang-tidy-19 {}" \
2> >(
perl -pe 'BEGIN{$/="\r";$|=1};s/\r/\n/g' |
grep '%' |
perl -pe 'BEGIN{$|=1}s/\e\[[0-9;]*[a-zA-Z]//g' |
perl -pe "BEGIN{\$length=$length;$|=1} s|(\d+)% (\d+):\d+=[\ds]+ (\S+).*|\$1% (\$2/\$length) -- \$3|" |
perl -ne '$s{$_}++ or print')
A saída bruta --bar
se parece com isso:
# 0 sec src/tuner/Utilities.h
3.65853658536585
[7m3% 3:7[0m9=0s src/tuner/Utilities.h [0m
(Com sequências de escape para imprimir a barra de progresso.)
Os comandos sucessivos que processam essa saída realizam as seguintes transformações:
- Transforme retornos de carro em novas linhas.
- Encontre linhas contendo porcentagem de saída.
- Retire as sequências de escape.
- Execute uma substituição de regex para extrair e formatar o número de arquivos processados, a porcentagem de conclusão e o nome do arquivo que está sendo processado. Também inclui o número total de arquivos a serem processados por meio de uma variável de shell.
- Imprima linhas exclusivas.
Todas BEGIN{$|=1}
as invocações do Perl, exceto a última, são necessárias para garantir que a saída seja liberada imediatamente.
A p
opção executará o perl em cada linha de entrada e imprimirá o resultado. A n
opção "executa em cada linha de entrada" put não imprime automaticamente. A e
opção fornece o script como um argumento CLI.
Ele gera uma saída semelhante a esta:
Running clang-tidy on 82 files
1% (1/82) -- src/tuner/LoadPositions.h
2% (2/82) -- src/tuner/Main.cpp
2% (2/82) -- src/tuner/Utilities.h
3% (3/82) -- src/tuner/Utilities.h
...
Tenho certeza de que existe uma maneira melhor de criar esses scripts Perl (e não ter quatro deles). Mas isso funciona, e meu perl-foo é muito fraco.