Bem-vindo ao Bash Challenge # 8 de Yes I Know IT e It's FOSS. Neste desafio semanal mostraremos uma tela do terminal e contaremos com você para nos ajudar a obter o resultado que desejamos. Pode haver muitas soluções, e ser criativo é a parte mais divertida do desafio.
Se você ainda não fez isso, dê uma olhada nos desafios anteriores:
Você também pode comprar esses desafios (com desafios não publicados) na forma de livro e nos apoiar:
Pronto para jogar? Então aqui está o desafio desta semana.
Como adicionar um cabeçalho?
Esta semana trabalho com vários arquivos de dados e um arquivo de cabeçalho. Eu só quero inserir o conteúdo do arquivo de cabeçalho no topo de cada arquivo de dados:
Para fins de demonstração, exibi apenas um arquivo. Mas você pode imaginar que tenho muitos deles - muitos para considerar a edição manual.
De qualquer forma, por algum motivo, minha solução não funcionou: não só perdi os dados, mas meu cabeçalho apareceu duas vezes.
cat HEADER DATA01 | tee DATA01
# Month, Year, Est.Value
# Month, Year, Est.Value
Como você pode ver, eu realmente preciso de sua ajuda aqui - tanto para me explicar o que estava acontecendo quanto para me ajudar a resolver esse problema. Estou realmente ansioso para ler suas soluções na seção de comentários abaixo!
Alguns detalhes
Para criar este desafio, usei:
- GNU Bash, versão 4.4.5 (x86_64-pc-linux-gnu)
- Debian 4.8.7-1 (amd64)
- Todos os comandos são fornecidos com uma distribuição Debian padrão
- Nenhum comando recebeu alias
Solução
Como reproduzir
Aqui está o código bruto que usamos para produzir este desafio. Se você executá-lo em um terminal, poderá reproduzir exatamente o mesmo resultado exibido na ilustração do desafio (presumindo que você esteja usando a mesma versão de software que eu):
rm -rf ItsFOSS
mkdir -p ItsFOSS
cd ItsFOSS
cat > HEADER << EOT
# Month, Year, Est.Value
EOT
cat > DATA01 << EOT
Dec, 2015, 15000
Jan, 2016, 12540
Feb, 2016, 11970
EOT
clear
head HEADER DATA01
cat HEADER DATA01 | tee DATA01
Qual era o problema?
Em um pipeline, todos os comandos são iniciados em paralelo. Isso significa que o comando cat
lendo o arquivo DATA01 e o comando tee
que substitui esse mesmo arquivo são iniciados simultaneamente.
Esta é realmente uma condição de corrida. No meu sistema, tee
teve tempo de sobrescrever o arquivo de destino antes que cat
tivesse a oportunidade de lê-lo. Para ilustrar isso, podemos atrasar os comandos e ver que a saída depende claramente do tempo:
cat HEADER DATA01 | ( sleep 1; tee DATA01 )
# Month, Year, Est.Value
Dec, 2015, 15000
Jan, 2016, 12540
Feb, 2016, 11970
sleep 1 ; cat HEADER DATA01 ) | tee DATA01
# Month, Year, Est.Value
Eu teria um problema semelhante (embora seja determinístico desta vez) usando o mais simples:
cat HEADER DATA01 > DATA01
Nesse caso, o shell sempre sobrescreve o arquivo de destino antes de lançar o comando cat
. Portanto, o conteúdo do arquivo é perdido muito antes de cat
ter a oportunidade de lê-lo.
Como consertar isso?
Obviamente, ninguém jamais usaria o hack sleep
em uma situação real. Mas isso não é um problema: como parte das ferramentas POSIX padrão, temos vários comandos à nossa disposição para inserir o cabeçalho no topo de um arquivo. Antes disso, vamos dar uma olhada na solução mais básica.
A solução KISS
cat HEADER DATA01 > DATA01.NEW
mv -f DATA01.NEW DATA01
Eu realmente preciso comentar isso? Bem, embora seja rudimentar, esta solução tem um bom recurso: como rm
usará a chamada do sistema rename
, que por si só é atômica nesse sentido que referencia o arquivo DATA01
, outros processos verão o conteúdo antigo ou o novo - mas nenhum um conteúdo escrito pela metade.
Uma solução um tanto semelhante, mas evitando criar um arquivo temporário visível no sistema de arquivos, obteria primeiro um descritor de arquivo para ler do arquivo original antes de substituí-lo:
exec 3<DATA01 # (1)
rm -f DATA01 # (2)
cat HEADER - <&3 >DATA01 # (3)
exec 3<&- # (4)
- Abra o arquivo DATA1 para leitura usando o descritor de arquivo 3;
- Desvincular o arquivo original (ou seja: remover a entrada do diretório, mas não os dados, pois o arquivo ainda está aberto);
- Use cat para ler o cabeçalho primeiro, seguido por uma leitura stdin do descritor de arquivo 3 e gravar em um novo arquivo DATA01;
- Feche o descritor de arquivo 3 Isso excluirá efetivamente o conteúdo DATA01 antigo.
Observe que esta solução não é mais atômica no sentido usado acima. De qualquer forma, parabéns a Adithya Kiran Gangu por ter proposto essa solução!
Usando sed
Ao encontrar problemas semelhantes pela primeira vez, minha ideia era usar sed
. É muito fácil inserir um cabeçalho após a primeira linha usando sed
. Mas é mais difícil inserir algo antes da primeira linha . Na verdade, para isso, precisaremos de um pouco de magia:
sed -i \'1{
r HEADER
N
}\' DATA01
Para entender completamente, você precisa saber que o comando (r) ead insere o conteúdo de um arquivo no fluxo de destino, mas apenas quando o processamento da linha atual terminar. É por isso que usei o comando (N) ext: ele encerrará o processamento da linha 1 mais cedo (ou seja: antes da saída de linha normal). Assim, ao encontrar esse comando, sed
termina o processamento da linha 1. O que dispara a saída do conteúdo do arquivo HEADER. Mas a própria linha 1 não é enviada para a saída. Ele é mantido no buffer sed
.
Em seguida, sed
lê a próxima linha de entrada, anexa-a ao buffer, e como não temos nenhuma regra para a linha 2, processe-a como de costume enviando seu buffer para a saída (lembre-se, nesse estágio, o buffer contém ambos linha 1 e linha 2).
Essa solução tem uma grande desvantagem: ela assume que há uma linha 2. Se o arquivo de dados contiver apenas uma linha, isso falhará terrivelmente.
Usando ed
ou ex
Temos muito poucas ocasiões em que usamos ed
ou seu primo ex
. Ambos são editores orientados por linha. Seu comportamento é muito semelhante ao vi
, nesse sentido, você carrega o arquivo na memória e envia comandos ao editor para modificar esse arquivo. A única diferença aqui é que faremos o script dos comandos em vez de enviá-los interativamente.
ed DATA01 << .
0r HEADER
wq
.
ex -s DATA01 << .
0r HEADER
wq
.
Isso funciona muito bem, mas como temos que carregar o arquivo inteiro na memória, o que pode ser um problema para arquivos muito grandes.
Como sempre, essas são provavelmente apenas um subconjunto de todas as soluções possíveis. Portanto, não hesite em usar a seção de comentários para compartilhar suas próprias ideias.
E fique ligado para mais diversão!
Via itsfoss.com. Você pode conferir o post original em inglês:
[Bash Challenge 8] Can You Solve This Bash Script Puzzle?Última atualização deste artigo: 23 de july de 2017