Como codificar diferentes tipos de dados para STDOUT para que STDIN possa identificar qual é o quê? [fechadas]

2

Se eu tiver um script que envia tipos de dados diferentes para STDOUT para cada arquivo que ele processa. Como posso separar cada tipo de dados para que outro script que leia STDIN saiba qual é o quê?

Por exemplo. Eu tenho um script que produz duas cadeias diferentes (desconhecidas) e dois números diferentes (desconhecidos) para cada arquivo que processa. Depois, há outro script que lê a partir de STDIN para processar cada uma das cadeias de dados, números. Como posso formatar a saída do primeiro script, de modo que o segundo possa ser feito para identificar cada tipo corretamente?

Estou acostumado a serializar dados via JSON, para a web, mas gostaria de saber se existe uma solução mais leve ou integrada para STDIN / STDOUT? Talvez algum separador único ou algo que eu esteja sentindo falta?

    
por Rotareti 21.09.2017 / 20:29

3 respostas

7

Você não explica qual é o seu stdout e para onde vai! (Por exemplo, o stdout de um aplicativo CGI é direcionado para algum navegador).

A filosofia Unix e Unix pipelines quer stdin e stdout para ser texto simples, mas isso é apenas uma convenção . Em alguns casos, você pode ter outras convenções (por exemplo, lpr ou lp geralmente preferem PDF em stdin).

Se você estiver codificando algum programa, poderá torná-lo com algum modo (por exemplo, especificado por algum argumento de programa) para gerar um texto mais estruturado, como JSON ou XML ou CSV ou YAML . Observe que o jq pode processar o JSON.

Alguns programas detectam (usando isatty e / ou fstat ) quando o seu stdout (ou o seu stdin ) é um terminal e age de acordo (talvez usando ncurses , termios , ou códigos de escape ANSI .

Muitos formatos de texto (principalmente XML) têm caracteres iniciais ou cabeçalhos convencionais, portanto, em alguns casos, adivinhando-os (ou seja, descobrindo o formato de arquivo ) é possível. Veja também MIME e libmagic (e file(1) ).

I have a script that produces two different (unknown) strings and two different (unknown) numbers for each file it processes. Then there is another script that reads from STDIN to process each of the given strings, numbers. How can I format the output of the first script, so that the second can be made to identify each type correctly?

Se você sabe (e documenta) que as strings desconhecidas são boas o suficiente para não conter caracteres de controle como newlines (então cada uma é uma única linha) você pode decidir por algum formato como

FIRSTSTRING: firststring

FIRSTNUMBER: firstnumber

por exemplo

 FIRSTSTRING: foo bar is nice!
 FIRSTNUMBER: 42

escreva e use algum script simples de awk (ou talvez use sed ) para seu segundo script

Em outras palavras, decida e documente um formato ad-hoc simples; no seu caso, o pode ser mais simples de manipular do que o JSON. Você pode ter suas próprias convenções ad-hoc desde que documente-as (talvez usando algumas notações de EBNF ).

Muitas ferramentas estão prontas; por exemplo, ps , ls , df e ifconfig têm formatos e convenções de saída ad-hoc, mas bem documentados. E também para proc (5) . Por isso, muitos scripts podem analisar essas saídas.

No entanto, o JSON foi projetado para ser simples, flexível, escalável, extensível, ... E é capaz de representar strings arbitrárias (mesmo com caracteres de controle, várias linhas, etc ...) . Se isso é importante para você, use-o.

Poder-se-ia reinventar e reimplementar todos os utilitários Unix para produzir, e. JSON ou XML (mas isso é um lote de trabalho). Por exemplo, algumas pessoas reinventaram proc (5) e faz um módulo do kernel para ter um sistema de arquivos /xmlproc/ pseudo em vez do /proc/ one que gerou dados do kernel do sistema em algum formato XML. Mas isso não foi bem sucedido! As convenções sociais são muito importantes (é por isso que é tão importante documentar seu formato de saída, pelo menos em um comentário longo).

(mesmo que você use JSON ou XML, você precisa documentar como você os usa)

BTW, muitas ferramentas existentes do Unix podem adicionar sutilezas em convenções. Por exemplo, um espaço ou uma tabulação ou um caractere de retorno em um caminho de arquivo é possível (veja path_resolution () 7) ), mas poderia ser desaprovado (então eu nunca faço isso). O diretório $HOME de algum usuário poderia, em teoria, conter um caractere de retorno ou dois-pontos, mas se você fizesse isso, a maioria das ferramentas sofreria (e é provável que você quebre passwd (5) ...). Os caminhos de arquivo que começam com um traço não são amigáveis e, portanto, são caminhos muito longos (por exemplo, seu $HOME pode ter 3000 caracteres em teoria, mas isso seria realmente imprudente).

    
por 21.09.2017 / 20:42
7

Você tem dois scripts, um produz duas cadeias e dois números. Cabe a você analisar isso no segundo script, como duas strings e dois números.

Como ambos os scripts estão sob seu controle, você está livre para enviar os dados de um para o outro de qualquer maneira que torne conveniente para o segundo script ler os dados.

Cabe a você determinar o formato, a ordem e a interpretação dos dados que você canaliza entre seus dois scripts. Você pode até mesmo enviar dados binários codificados, se isso fizer sentido para você.

Não há tipos de dados que possam ser aplicados aos dados, a não ser o que você decide sozinho, decidindo sobre um "contrato" ou "protocolo" particular entre seus dois scripts ou programas.

Algumas ferramentas padrão do Unix, como sort e cut , pressupõem a entrada em um formato específico, mas podem ser modificadas para alterar a interpretação dos dados de entrada por meio do uso de opções de linha de comando.

Exemplo envolvido:

#!/bin/sh

echo 'first string'
echo 'second string'

echo '1.1'
echo '3.14'

O segundo script, em seguida, lê isso:

#!/bin/sh

IFS= read -r string1
IFS= read -r string2
read number1 number2

Ou com o JSON:

#!/bin/sh

echo '{ "string1": "hello", "string2": "world", "numbers": [1.1,3.14] }'

Segundo script:

#!/bin/sh

jq -r '"A number: \(.numbers[])"'
    
por 21.09.2017 / 21:04
2

stdin e stdout são apenas os descritores de arquivo 0 e 1 de um processo e qualquer outra descrição de arquivo aberto em que apontam, por exemplo, a abertura de um arquivo com o open() chamada do sistema ou criação de um canal com pipe() ou open() em um canal nomeado ou soquete com connect() / accept() / socketpair() , etc.

No mínimo, a maioria deles seria capaz de ler e gravar qualquer sequência de bytes como um fluxo. Para aqueles que são normalmente usados para comunicação entre processos, como pipes e sockets stream , geralmente não há preservação de limites de mensagem.

Por exemplo, em uma linha de comando do shell, como:

writer | reader

onde, você terá o stdout do escritor sendo uma extremidade de um pipe (ou socketpair dependendo do shell) e stdin do leitor na outra extremidade

se writer fizer um write(1, "foo", 3); write(1, "bar", 3) , o reader não será capaz de dizer que há duas mensagens chegando a menos que isso ocorra em read() entre as duas gravações de writer .

Existem alguns tipos de arquivo, como soquetes de datagrama (pelo menos, UDP, SCTP ou domínio unix) ou SOCK_SEQPACKET em alguns sistemas que preservam os limites das mensagens, mas você precisaria de uma API diferente de read() e write() se você quiser permitir mensagens vazias, e o leitor precisaria saber antecipadamente o tamanho máximo possível dessas mensagens e alocar um buffer tão grande para recebê-las. Você ainda precisará usar alguma forma de codificação para especificar a natureza do conteúdo dessas mensagens.

Exemplo:

$ strace -e write dd bs=2 count=3 if=/dev/zero status=none | strace -fe read cat
write(1, "
$ perl -MSocket -e '
   socketpair(my $rdr, my $wtr, AF_UNIX, SOCK_SEQPACKET, PF_UNSPEC);
   shutdown($rdr, 1); shutdown($wtr, 0);
   if (fork) {
     open STDIN, "<&", $rdr; close $wtr; close $rdr; sleep 1;
     exec qw(strace -e read cat)
   } else {
     open STDOUT, ">&", $wtr; close $rdr; close $wtr;
     exec qw(strace -e write dd count=3 bs=2 status=none if=/dev/zero)
   }'
write(1, "
writer | reader
$ strace -e write dd bs=2 count=3 if=/dev/zero status=none | strace -fe read cat
write(1, "
$ perl -MSocket -e '
   socketpair(my $rdr, my $wtr, AF_UNIX, SOCK_SEQPACKET, PF_UNSPEC);
   shutdown($rdr, 1); shutdown($wtr, 0);
   if (fork) {
     open STDIN, "<&", $rdr; close $wtr; close $rdr; sleep 1;
     exec qw(strace -e read cat)
   } else {
     open STDOUT, ">&", $wtr; close $rdr; close $wtr;
     exec qw(strace -e write dd count=3 bs=2 status=none if=/dev/zero)
   }'
write(1, "%pre%%pre%", 2)                     = 2
write(1, "%pre%%pre%", 2)                     = 2
write(1, "%pre%%pre%", 2)                     = 2
+++ exited with 0 +++
read(0, "%pre%%pre%", 131072)                 = 2
read(0, "%pre%%pre%", 131072)                 = 2
read(0, "%pre%%pre%", 131072)                 = 2
read(0, "", 131072)                     = 0
+++ exited with 0 +++
%pre%", 2) = 2 write(1, "%pre%%pre%", 2) = 2 write(1, "%pre%%pre%", 2) = 2 read(0, "%pre%%pre%%pre%%pre%%pre%%pre%", 131072) = 6 read(0, "", 131072) = 0
", 2) = 2 write(1, "%pre%%pre%", 2) = 2 write(1, "%pre%%pre%", 2) = 2 +++ exited with 0 +++ read(0, "%pre%%pre%", 131072) = 2 read(0, "%pre%%pre%", 131072) = 2 read(0, "%pre%%pre%", 131072) = 2 read(0, "", 131072) = 0 +++ exited with 0 +++
%pre%", 2) = 2 write(1, "%pre%%pre%", 2) = 2 write(1, "%pre%%pre%", 2) = 2 read(0, "%pre%%pre%%pre%%pre%%pre%%pre%", 131072) = 6 read(0, "", 131072) = 0

3 gravações de tamanho 2, 1 de tamanho 6, embora dependendo do tempo, você pode ver 3 leituras de tamanho 2, ou uma leitura de tamanho 4 e uma de tamanho 2. Isso foi com um pipe ou SOCK_STREAM socketpair.

Com um socketpair SOCK_SEQPACKET:

%pre%

São 3 leituras para essas 3 gravações, embora tenhamos atrasado as leituras em um segundo, muito depois de as gravações terem sido feitas.

Assim, no final, o melhor é codificar o tipo e o comprimento de uma forma ou de outra. Uma forma compacta e uma que permita ao leitor processar os dados assim que chegarem (mas o escritor saiba a duração da mensagem com antecedência) é usar Codificação TLV (tipo, comprimento, valor) . Você ainda precisará que o escritor e o leitor concordem com o tamanho e o tipo das palavras "type" e "length" (por exemplo, 32-bit little endian integer) e como interpretar os valores de tipo.

Ou você pode usar qualquer um dos muitos formatos de serialização que produzem texto que também é mais seguro para intercâmbio entre sistemas com diferentes endianness ou canais que não permitem bytes NUL ou fazem algum transcodificação do delimitador de linha. Como json, XML, Data::Dumper de perl, serialize() de php, a saída de typeset -p de alguns shell dependendo da linguagem que você está usando ...

    
por 22.09.2017 / 11:12

Tags