Pegue um comando que modifique um arquivo inline e o aceite como stdin / stdout

1

Digamos que eu tenha um comando foo que aceita um arquivo e realize alguma transformação nesse arquivo (algo semelhante a sed -i ). E digamos que este comando não aceita a opção clássica "leia de stdin, transformar, escreva para stdout".

Existe alguma maneira de "transformar" este comando para permitir o uso do fluxo de trabalho stdin / stdout?

A primeira coisa que posso pensar é escrever algum tipo de wrapper que usa arquivos temporários. Mas eu gostaria de saber se existe algum tipo de meta-ferramenta padronizada para fazer isso (ou algum truque básico).

    
por Franco Victorio 08.11.2017 / 20:00

3 respostas

2

É um pouco brega, mas:

my_temp_file=$(mktemp)
cat > "$my_temp_file"
foo "$my_temp_file"
cat "$my_temp_file"
rm -f "$my_temp_file"

Caso não seja óbvio, isso

  • lê stdin e escreve em um arquivo,
  • chama seu programa foo no arquivo temporário,
  • lê o arquivo temporário e o grava no stdout e
  • exclui o arquivo temporário.

Isso falhará se os dados a serem processados são grandes demais para caber em um arquivo em /tmp . Você pode ser capaz de atenuar isso um pouco usando a opção --tmpdir para mktemp para colocar o arquivo temporário em um sistema de arquivos que tenha mais espaço livre.

Conectar comandos com && é a última moda dos dias de hoje. Pode fazer um pouco mais de sentido dizer

my_temp_file=$(mktemp) && {
    cat > "$my_temp_file"  && {
        foo "$my_temp_file"
        cat "$my_temp_file"
    }
    rm -f "$my_temp_file"
}

porque

  • se mktemp falhar, você não tem um arquivo temporário, e não há nada que você possa fazer,
  • se cat > "$my_temp_file" falhar, você não capturou a entrada, então não há nada que você possa fazer (exceto que você ainda deseja excluir o arquivo temporário)

Se o status de saída de foo for importante para você, lidar com isso de forma adequada.

Opcionalmente, torne isso um pouco mais à prova de balas configurando um trap para fazer o rm mesmo se as coisas ficarem de lado.

    
por 08.11.2017 / 21:24
1

Assim como a resposta do G-Man, mas encapsulada em uma função:

foo_stream() { 
    local t=$(mktemp) && 
    cat - >| "$t" && 
    foo "$t" && 
    cat "$t" && 
    rm "$t"
}

Então, se foo é: foo() { sed -i 's/.*/& & &/' "$1"; } (para "triplicar" cada linha), então usamos foo_stream em um pipeline da maneira que quisermos:

$ seq 10 | foo_stream | tac
10 10 10
9 9 9
8 8 8
7 7 7
6 6 6
5 5 5
4 4 4
3 3 3
2 2 2
1 1 1

Observação: eu uso set -o noclobber para evitar a substituição acidental de arquivos existentes, e o >| redirecionamento pode substituir isso.

    
por 08.11.2017 / 21:27
0

Se você tiver controle sobre foo de tal forma que ele não edite no local , mas você pode fornecer um arquivo de entrada e um arquivo de saída:

foo() { sed 's/.*/& & &/' "$1" > "$2"; }

Então você pode usar substituições de processo para representar o "pipe de entrada" e o "pipe de saída".

$ foo <(seq 10) >(tac)
10 10 10
9 9 9
8 8 8
7 7 7
6 6 6
5 5 5
4 4 4
3 3 3
2 2 2
1 1 1
    
por 08.11.2017 / 21:35