Quão estáveis as APIs "stdin / stdout" do shell Unix?

19

grepping, awking, sedding e piping são rotina diária de um usuário de qualquer sistema operacional Unix-like, seja na linha de comando ou dentro de um shell script (chamados coletivamente filters a partir de agora).

Em sua essência, ao trabalhar com programas CLI "padrão" e shell builtins (chamados coletivamente comandos a partir de agora), os filtros precisam de um formato preciso esperado para stdin, stdout e stderr em cada passo do filtro para funcionar corretamente. Eu chamo este formato preciso esperado de algum comando uma API deste comando no seguinte.

Como alguém com experiência em desenvolvimento web, eu comparo esse tipo de coleta de dados e processamento de dados tecnicamente com web scraping - um técnica que é muito instável sempre que houver a menor alteração na apresentação de dados.

Minha pergunta agora está relacionada à estabilidade das APIs de comando do Unix.

  1. Os comandos em sistemas operacionais semelhantes a Unix aderem a uma padronização formal em relação a suas entradas e saídas?
  2. Houve casos no histórico em que as atualizações de algum comando importante causaram a quebra da funcionalidade de algum filtro que foi criado usando uma versão mais antiga do dito comando?
  3. Os comandos do Unix amadureceram ao longo do tempo de modo que é absolutamente impossível alterar de forma que algum filtro possa quebrar?
  4. Caso os filtros possam ser interrompidos de tempos em tempos devido à mudança de APIs de comando, como posso, como desenvolvedor, proteger meus filtros contra esse problema?
por Abdull 01.07.2012 / 19:48

6 respostas

17

O padrão POSIX 2008 tem uma seção descrevendo "Shell and Utilities" . Geralmente, se você prefere que os seus scripts sejam razoavelmente preparados para o futuro, exceto possivelmente para depreciações, mas esses dificilmente acontecem durante a noite, então você deve ter bastante tempo para atualizar seus scripts.

Em alguns casos em que o formato de saída para um único utilitário varia amplamente entre plataformas e versões, o padrão POSIX pode incluir uma opção normalmente chamada -p ou -P , que especifica um formato de saída garantido e previsível. Um exemplo disso é o time utility , que tem implementações muito variadas. Se você precisa de um formato de API / saída estável, você usaria time -p .

Se você precisa usar um utilitário de filtro que não é coberto pelo padrão POSIX, então você está praticamente à mercê dos desenvolvedores de distribuição / desenvolvedores upstream, assim como você está à mercê dos desenvolvedores web remotos ao fazer raspagem da web.

    
por 01.07.2012 / 20:49
12

Vou tentar responder a partir da minha experiência.

  1. Os comandos não aderem a uma especificação formal, mas atendem ao requisito de consumir e gerar texto orientado a linhas.

  2. Sim, claro. Antes que os utilitários GNU se tornassem um padrão de fato, muitos fornecedores teriam resultados peculiares, especialmente com relação a ps e ls . Isso causou muita dor. Hoje, somente a HP oferece comandos super peculiares. Historicamente, os utilitários de Distribuição de Software de Berkeley (BSD) foram uma grande ruptura com o passado. A especificação POSIX foi uma ruptura com o passado, mas agora é amplamente aceita.

  3. Os comandos Unix realmente amadureceram ao longo do tempo. Ainda não é impossível quebrar algum script escrito para uma versão mais antiga. Pense na tendência recente para o UTF-8 como uma codificação de arquivo de texto. Essa mudança exigiu a mudança de utilitários básicos como tr . No passado, o texto simples quase sempre era ASCII (ou algo próximo), então as letras maiúsculas formavam um intervalo numérico, assim como as letras minúsculas. Isso não é mais verdade com o UTF-8, então tr aceita diferentes opções de linha de comando para especificar coisas como "maiúsculas" ou "alfanuméricas".

  4. Uma das melhores maneiras de "fortalecer" seus filtros é não depender do layout específico do texto. Por exemplo, não use cut -c10-24 , que depende das posições de uma linha. Use cut -f2 , que cortaria o segundo campo separado por tabulações. awk divide qualquer linha de entrada em $ 1, $ 2, $ 3 ... que são espaços em branco separados por padrão. Dependem de conceitos de nível superior, como "campos", em vez de conceitos de nível inferior, como a posição da coluna. Além disso, use expressões regulares: sed e awk podem fazer coisas com expressões regulares que não se importam com alguma variação na entrada. Outro truque é processar a entrada em algo cujo formato seu filtro pode ser exigente. Use tr -cs '[a-zA-z0-9]' '[\n]' para dividir o texto em uma única palavra por linha, sem pontuação. Você simplesmente não se importa com o texto de entrada nesse caso.

por 02.07.2012 / 04:15
9

Primeiro, respostas muito breves às suas perguntas:

  1. Padronização formal das convenções de entrada / saída: no
  2. Quebra no passado devido a alterações na saída: sim
  3. Absolutamente impossível interromper filtros futuros: no
  4. Como posso me proteger contra alterações: seja conservador

Quando você diz "API", você está usando um termo que (para o bem ou para o mal) implica muita formalidade em torno das convenções de entrada / saída do filtro. Muito (e eu quero dizer "muito") amplamente, as convenções primárias para dados que são suscetíveis a filtragem fácil são

  • cada linha de entrada é um registro completo
  • dentro de cada registro, os campos são separados por um caractere delimitador conhecido

Um exemplo clássico seria o formato / etc / passwd. Mas, essas convenções padrão provavelmente são violadas em algum grau com mais frequência do que seguidas ao pé da letra.

  • Existem muitos filtros (geralmente escritos em awk ou perl) que analisam formatos de entrada de múltiplas linhas.
  • Existem muitos padrões de entrada (por exemplo, / var / log / messages) onde não há estrutura de campo bem definida, e técnicas mais gerais baseadas em expressões regulares devem ser usadas.

Sua quarta pergunta, como se proteger contra variações na estrutura de saída, é realmente a única sobre a qual você pode fazer alguma coisa.

  • Como @ jw013 disse , observe o que os padrões posix dizem. Claro, posix não especifica todos os comandos que você vai querer usar como fontes de entrada.
  • Se você quiser que seus scripts sejam portáveis, tente evitar as idiossincrasias da versão do comando que você tenha instalado. Por exemplo, muitas versões GNU de comandos unix padrão possuem extensões não padrão. Estes podem ser úteis, mas você deve evitá-los se quiser máxima portabilidade.
  • Tente saber quais subconjuntos de argumentos de comandos e formatos de saída tendem a ser estáveis em todas as plataformas. Infelizmente, isso requer acesso a várias plataformas ao longo do tempo, porque essas diferenças não serão registradas em nenhum lugar, nem mesmo informalmente.

No final, você não pode se proteger totalmente dos problemas com os quais está preocupado, e não há um único lugar para procurar uma declaração "definitiva" do que um determinado comando deve fazer. Para muitos scripts de shell, especialmente aqueles escritos para uso pessoal ou de pequena escala, isso simplesmente não é um problema

    
por 02.07.2012 / 00:06
5

Cobrindo apenas 1) da sua pergunta.

Naturalmente, as APIs sempre podem mudar à vontade de seus criadores e, assim, quebrar software dependente em qualquer idioma. Dito isso, a grande ideia das APIs " I / O das ferramentas Unix" é que praticamente não há nenhuma (talvez 0x0a como fim da linha). Um bom script filtra os dados com as ferramentas do Unix, em vez de criá-los. Isso significa que seu script pode quebrar porque a especificação de entrada ou saída mudou, mas não porque o formato de E / S (novamente, não há realmente um) das ferramentas individuais usadas no script foi alterado (porque algo que realmente não existe) não pode realmente mudar).

Passando por uma lista de ferramentas básicas, há poucas que eu também atribuiria produtor , em oposição a apenas filtro:

  • wc - imprime o número de bytes, palavras, linhas - muito formato simples, assim, é absolutamente improvável que mude e, além disso, não é muito provável que seja usado em um script.
  • diff - evoluíram diferentes formatos de saída, mas não ouvi falar de nenhum problema. Também não é normalmente usado sem supervisão.
  • date - Agora, aqui realmente temos que cuidar do que produzimos, especialmente em relação à localidade do sistema. Caso contrário, o formato de saída é RFC, dado que você não especifica exatamente você mesmo.
  • cal - não vamos falar sobre isso, eu sei que o formato de saída difere muito entre os sistemas.
  • ls , quem , w , último - não posso ajudar se você quiser analisar ls, apenas não era para ser. Além disso, quem, por último, são mais interativos; Se você usá-los em um script, você deve cuidar do que faz.
  • time foi apontado em outro post. Mas sim, é o mesmo que com ls. Mais para uso interativo / local. E o bash builtin é muito diferente da versão GNU, e a versão GNU tem erros não corrigidos há muitos anos. Apenas não confie nisso.

Aqui estão as ferramentas que esperam um formato de entrada específico mais específico do que ser um fluxo de bytes:

  • bc , dc - calculadoras. Já no lado mais agressivo das coisas (na verdade, não as uso em scripts) e presumivelmente em formatos de E / S muito estáveis.

Existe outra área com um risco muito maior de quebra, a saber, a interface de linha de comando. A maioria das ferramentas possui recursos diferentes nos vários sistemas e na linha do tempo. Exemplos são

  • Todas as ferramentas que usam regex - regex podem alterar o significado com base na localidade do sistema (por exemplo, LC_COLLATE) e há muitas sutilezas e peculiaridades nas implementações de regex.
  • Simplesmente não use switches sofisticados. Você pode facilmente usar man 1p find , por exemplo, para ler a página de localização do POSIX em vez da página de manual do sistema. No meu sistema, preciso de manpages-posix instalado.

E mesmo quando você usa essas opções, normalmente não haverá erros introduzidos e envenenarão seus dados. A maioria dos programas simplesmente se recusará a trabalhar com um switch desconhecido.

Para concluir, eu diria que o shell tem realmente o potencial de ser uma das linguagens mais portáteis (é portátil quando você faz um script portável). Compare com as suas linguagens de script favoritas, onde ocorrem erros sutis, ou o seu programa compilado favorito, que irá ceder a compilação.

Além disso, nos raros lugares onde a quebra pode ocorrer devido a incompatibilidades, provavelmente não seria por causa do tempo induzido, mas por causa da diversidade entre os diferentes sistemas (ou seja, se funciona para você, o fez 20 anos antes e em 20 anos também). Isso é um corolário da simplicidade das ferramentas.

    
por 02.07.2012 / 00:49
1

Existem apenas padrões IO de fato - espaço em branco e saída separada nula.

Quanto à compatibilidade, costumamos reverter para verificar números de versão de filtros individuais. Não que eles mudem muito, mas quando você quiser usar um novo recurso e ainda quiser que o script seja executado em versões mais antigas, você terá que "sair" de alguma forma. Não há praticamente nenhum mecanismo de relatório de capacidade, exceto pela escrita manual de casos de teste.

    
por 01.07.2012 / 20:43
0

Os scripts quebram, alguns com mais frequência do que outros. O software antigo e famoso tende a permanecer relativamente o mesmo e geralmente tem sinalizadores de compatibilidade quando muda de qualquer maneira.

Scripts escritos em um sistema tendem a continuar trabalhando, mas geralmente quebram outro.

    
por 01.07.2012 / 19:58