Quero obter o número de bytes e o sha1sum da saída de um comando.
Em princípio, sempre se pode fazer algo como:
BYTES="$( somecommand | wc -c )"
DIGEST="$( somecommand | sha1sum | sed 's/ .*//' )"
... mas, para o caso de uso no qual estou interessado, somecommand
consome bastante tempo e produz uma grande quantidade de resultados, então prefiro chamá-lo apenas uma vez.
Uma maneira que vem à mente seria algo como
evil() {
{
somecommand | \
tee >( wc -c | sed 's/^/BYTES=/' ) | \
sha1sum | \
sed 's/ .*//; s/^/DIGEST=/'
} 2>&1
}
eval "$( evil )"
...o que parece funcionar, mas me faz morrer um pouco por dentro.
Gostaria de saber se existe uma maneira melhor (mais robusta, mais geral) de capturar a saída de diferentes segmentos de um pipeline em variáveis separadas.
EDIT: O problema no qual estou trabalhando no momento é em bash
, então estou mais interessado em soluções para esse shell, mas também faço muita zsh
programação, então também tenho algum interesse nessas soluções.
EDIT2: Tentei portar a solução de Stéphane Chazelas para bash
, mas não funcionou:
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
cmd | tee >(sha1sum > $1) | wc -c | read bytes || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
Quando executo o script acima, a saída que recebo é
bytes=
checksum=96d89030c1473585f16ec7a52050b410e44dd332
O valor de checksum
está correto. Não consigo entender por que o valor de bytes
não está definido.
EDIT3: OK, graças à dica do @muru, resolvi o problema:
#!/bin/bash
cmd() {
printf -- '%1000s'
}
bytes_and_checksum() {
local IFS
read bytes < <( cmd | tee >(sha1sum > $1) | wc -c ) || return
read checksum rest_ignored < $1 || return
}
set -o pipefail
unset bytes checksum
bytes_and_checksum "$(mktemp)"
printf -- 'bytes=%s\n' $bytes
printf -- 'checksum=%s\n' $checksum
Agora:
bytes=1000
checksum=96d89030c1473585f16ec7a52050b410e44dd332
INFELIZMENTE ...
... minha bytes_and_checksum
função para (impasse?) quando cmd
produz muito mais saída do que no meu exemplo de brinquedo acima.
De volta à prancheta...
Seria mais fácil usar arquivos temporários. Em
zsh
:Cuidado, muitas
wc
implementações incluem espaços em branco ao redor do número gerado.read
com o valor padrão de$IFS
removê-los.Observe que o status de saída
sha1sum
está perdido.Cria
=()
o arquivo temporário sem saída de nada. Esse arquivo temporário é removido automaticamente quando o comando ao qual é fornecido (aqui uma função anônima) retorna.Em
cmd > file | other-cmd
,cmd
a saída de étee
d internamente,zsh
pois é redirecionada duas vezes, então aqui tanto parasha1sum
quanto parawc
. Envolvemos para garantir que o zsh aguarde a conclusão dos redirecionamentos do processocmd
.{...}
Aqui, como a saída de ambos
sha1sum
ewc
é garantido que não será maior do que alguns bytes, eles também podem ser enviados para pipes, e você não precisaria ler esses pipes simultaneamente (o que o zsh pode fazer, pois tem uma interface paraselect()
/poll()
mas não bash). Isso poderia ser feito sequencialmente sem causar conflitos, por isso é uma versão fácil de tee em diferentes variáveis .Em sistemas baseados em Linux (onde
/dev/fd/x
quandox
um fd para um pipe se comporta como um pipe nomeado):(até funcionaria no bash).
Para obter detalhes sobre os impasses que você encontraria com saídas maiores, consulte também tee + cat: use uma saída várias vezes e depois concatene results .
Estou usando um script bash de backup que possui as seguintes funções auxiliares "intermediárias" que usam um "suposto nome de arquivo" como argumento (veja o exemplo tar.gz abaixo):
Exemplo:
Estou usando-o em um script que consome muito tempo, CPU e IO, algo assim:
Assim, verifiquei meu backup em relação a inversões aleatórias de bits de memória/disco. Ele cria o arquivo "mybackup.tar.sha1" como se "mybackup.tar" tivesse sido realmente criado e verificado, embora na verdade neste exemplo os dados descompactados nunca sejam gravados no disco.
Advertência: o
pipechecksum
script não termina o script em caso de erro, mesmo comset -euo pipefail
. A alternativapipechecksum
que retorna diferente de zero na incompatibilidade de soma de verificação:Parece bom, mas cheguei com isso hoje e não posso considerá-lo comprovado.