Bash - Redireciona a saída para uma variável ou descritor de arquivo, depois lê a partir da variável ou descritor de arquivo

1

Aqui está um exemplo de um script bash que redireciona toda a saída para um arquivo (e mostra a saída na tela também):

# writing to it
exec > >(tee --ignore-interrupts ./log)
exec 2>&1

echo "here is a message"

# reading from it again
cat ./log

Agora não quero criar o arquivo ./log . Eu quero manter todo o stdout na memória e ser capaz de ler a partir dele novamente no script. Também estou em uma situação em que pode não haver nenhum sistema de arquivos raiz montado.

Eu tentei fazer isso com a substituição do processo em vez de ./log , mas parece que não consigo entender como passar o descritor de arquivo criado pela substituição do processo para um comando subsequente para ler o que acabei de escrever.

Aqui está outro exemplo:

# can I make ./log a temporary file descriptor, an in-memory buffer?
exec 3>./log 4<./log
# writes
echo >&3 "hello"
# reads
cat <&4
    
por CMCDragonkai 10.02.2016 / 10:10

2 respostas

3

Se você quiser redirecionar globalmente tudo o que está escrito (como você faz agora), é complicado, mas pode ser hackeado em conjunto.

Eu recomendo strongmente que, se for possível, basta fazê-lo por tubulação normal. apenas embrulhe tudo que você faz em um subnível. Neste caso,

(
 echo "this is the message"
 other stuff
) | cat

ou apenas escreva tudo em uma variável com a sintaxe "$ ()".

A próxima maneira é usar o que você fez, mas escreva para um tmpfs ou /dev/shm se eles estiverem disponíveis. Isso é muito simples, mas você precisa saber quais sistemas de arquivos baseados em RAM estão instalados (e configurá-los, se possível).

Outra maneira é criar um fifo com mkfifo . Em ambos os casos, você precisa limpar a si mesmo.

EDITAR:

Eu tenho um hack muito feio, mas aposto que alguém pode melhorar isso.

#!/bin/bash
exec 3>&1
exec > >( tee >( ( tac && echo _ ) | tac | (read && cat > ./log) ) )

echo "lol"
sleep 5
echo "lol"

echo "finished writing"

exec >&-
exec >&3
exec 3>&-
echo "stdout now reopen"
sleep 1 #wait if the file is still being written asynchronously
cat ./log

Como funciona: primeiro, você tem um tee para poder ver o que está acontecendo. Isso, por sua vez, resulta em outra substituição de processo. Lá, você tem o truque tac|tac que (porque tac precisa de toda a entrada para iniciar a saída) espera que todo o fluxo termine antes de continuar. A última parte está em um subshell que realmente gera isso em um arquivo. É claro que o shell final, imediatamente após a instanciação, criaria o arquivo de saída no sistema de arquivos se essa fosse a única linha. Então, algo que também espera que a entrada finalmente chegue, precisa ser feito primeiro, para atrasar a criação do arquivo. Eu faço isso enviando uma linha fictícia primeiro com echo e, em seguida, lendo e descartando-a. O read bloqueia até você fechar o descritor de arquivo, sinalizando para tac sua hora chegou. Portanto, o fechamento do descritor de arquivo stdout no final. Eu também salvei o stdout original antes de abrir a substituição do processo, para restaurá-lo no final (para usar cat mais uma vez). Há um sleep 5 lá, então eu poderia verificar com ls se o arquivo realmente não foi criado muito cedo. O sono final é mais complicado ... O subshell é assíncrono e, se houver muita saída, você está aguardando que ambos os tac s façam suas tarefas antes que o arquivo esteja realmente lá. Então, razoavelmente, você provavelmente precisará fazer outra coisa para verificar se a coisa está realmente terminada. Por exemplo, && touch sentinel no final do último subshell e while [ ! -f sentinel ]; do sleep 1; done && rm sentinel antes de você finalmente usar o arquivo.

Ao todo, duas substituições de processo e no interior uma duas sub-caixas e dois tubos. É uma das coisas mais feias que já escrevi ... mas deve criar o arquivo apenas quando você fechar o stdout, o que significa que ele é bem controlado e pode ser feito quando os sistemas de arquivos estiverem prontos.

    
por 10.02.2016 / 10:26
1

caso simples:

output="$(echo "here is a message")"
# [...]
echo "$output"

Não é tão simples se você tiver que ler um redirecionamento e não puder usar um pipeline como

echo "$output" | while IFS= read -r line; do :; done

Você pode usar um processo FIFO e de segundo plano:

mkfifo fifo
echo "$output" >fifo &
while IFS= read -r line; do :; done <fifo

versão do tee

mkfifo fifo
( output="$(cat fifo)"; echo "$output" >fifo ) &
exec 3>&1
exec >fifo
...
exec 1>&3
cat fifo
    
por 10.02.2016 / 10:18