Processando um único arquivo como entrada e saída em todos os pipes [duplicado]

2

Boa noite,

Eu gostaria de filtrar o conteúdo de um arquivo com alguns comandos canalizados e, em seguida, gravar o resultado no mesmo arquivo. Eu sei, eu não posso fazer isso do jeito que eu escrevi. Espere ...

Esta é a parte do script que eu tenho.

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

Então, eu achei que poderia ter sucesso usando a substituição de processos. Eu então escrevi:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

Isso também não resolveu nada. Eu esperava que a substituição do processo "salvasse" meu conteúdo de arquivo de entrada em algum lugar, como em um arquivo temporário. Parece que eu não entendi a substituição do processo.

Eu li tópicos sobre a edição "inplace", mas esses artigos destacaram opções especiais de alguns binários como sed -i ou sort -o , mas eu preciso de uma solução geral (quero dizer, ele precisa atender a qualquer comando canalizado).

Então, primeiro, por que o 'caminho padrão dos tubos' não pode fazer isso, o que está acontecendo embaixo? :/ E como devo resolver meu problema? Alguém poderia explicar me o que é isso tudo?

Obrigado.

    
por Stphane 17.02.2016 / 01:19

3 respostas

3

Como foi mencionado, a esponja de moreutils é ótima. Eu uso esse script para emular para evitar a dependência do moreutils:

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="'mktemp'" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

Você pode usá-lo assim:

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

Você não pode fazer isso com um simples redirecionamento de saída, porque os redirecionamentos ocorrem antes dos comandos serem iniciados e um redirecionamento de saída trunca o arquivo de saída.

Em outras palavras, quando o grep (o primeiro comando simples do pipeline) for iniciado, o último redirecionamento já truncou o arquivo de entrada / saída.

Não há realmente nenhum utilitário UNIX padrão que faça a verdadeira edição no local, até onde eu saiba. sed -i apenas emula com um arquivo temporário. Eu acho que a razão é que a verdadeira filtragem inplace pode facilmente corromper o arquivo se uma etapa do pipeline falhar.

No que diz respeito ao que está acontecendo abaixo - ambos | e <() usam pipes do sistema que passam IO e um buffer de cada vez. O mecanismo não cria arquivos temporários (arquivos não-reais (sistema de arquivos)) e tenta evitar manter toda a entrada na memória de cada vez.

    
por 17.02.2016 / 17:53
1

Se você quiser entrada e saída para o mesmo arquivo, você pode tentar esponja . Como sua descrição indica:

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

Assim, você pode ter algo como sed '...' file | grep '...' | sponge [-a] file recebendo entrada do arquivo e exibindo o mesmo arquivo .

Por outro lado, usar arquivos temporários também é uma ótima maneira de trabalhar com o mesmo arquivo para entrada e saída. Você pode inicializar seus arquivos temporários da seguinte forma:

tempfile='mktemp tempFile.XXXX' # You can replace "tempFile" with any name you want

Isso cria um arquivo temporário chamado "tempFile" no diretório em que esse script é executado, com a extensão "XXXX" em que os x são substituídos por uma combinação do número do processo atual e letras aleatórias (por exemplo, tempFile.AVm7 ).

Agora você pode modificar seu pipe (ou qualquer comando canalizado) da seguinte forma:

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

Após o filtro, você pode mover seu arquivo temporário para o arquivo original da seguinte forma:

mv "$tempfile" "$filepath"

Isso elimina seu arquivo temporário e você permanece com o arquivo original filtrado. Mas, às vezes, você pode acabar criando muitos arquivos temporários que talvez não precise e não tenha destruído, portanto, é uma boa idéia limpar seu diretório excluindo todos os arquivos temporários depois que o script terminar, se você não precisar mais deles . Você pode escrever uma rotina para isso da seguinte forma:

remove_temp_files() {
    rm 'find . -name "tempFile.????"'
}

Você pode simplesmente chamar sua rotina remove_temp_files no final do seu script, eliminando todos os arquivos temporários que foram criados no formato descrito acima.

    
por 17.02.2016 / 16:44
0

Usando aqui documento e Substituição de comando é o caminho padrão a seguir neste caso:

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

Para outras perguntas, elas foram explicadas em muitas perguntas antes:

por 17.02.2016 / 17:06