Quão estáveis ​​são as “APIs stdin / stdout” do shell Unix?

20

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

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

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

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

  1. Os comandos em sistemas operacionais do tipo Unix aderem a uma padronização formal em relação à entrada e saída?
  2. Houve casos no histórico em que as atualizações de algum comando importante causavam uma quebra na funcionalidade de algum filtro criado usando uma versão mais antiga do referido comando?
  3. Os comandos do Unix amadureceram com o tempo e é absolutamente impossível mudar de tal maneira que algum filtro possa quebrar?
  4. Caso os filtros possam quebrar de tempos em tempos devido à alteração das APIs de comando, como posso, como desenvolvedor, proteger meus filtros contra esse problema?
Abdull
fonte

Respostas:

17

O padrão POSIX 2008 possui uma seção que descreve "Shell e Utilitários" . Geralmente, se você se atentar a isso, seus scripts deverão ser razoavelmente à prova de futuro, exceto possivelmente por descontinuações, mas dificilmente ocorrerão da noite para o dia, portanto, você deve ter tempo de sobra 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 -pou -Pque especifica um formato de saída garantido e previsível. Um exemplo disso é o timeutilitário , que possui implementações muito variadas. Se você precisar de um formato estável de API / saída, você usaria time -p.

Se você precisar usar um utilitário de filtro que não seja coberto pelo padrão POSIX, estará à mercê dos empacotadores de distribuição / desenvolvedores upstream, assim como à mercê dos desenvolvedores da Web remotos ao fazer a raspagem da Web.

jw013
fonte
12

Vou tentar responder da minha experiência.

  1. Os comandos realmente não aderem a uma especificação formal, mas atendem a um 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 uma saída peculiar, especialmente com relação a pse ls. Isso causou muita dor. Hoje, apenas a HP fornece comandos super peculiares. Historicamente, os utilitários de Berkeley Software Distribution (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 com o tempo. Ainda não é impossível quebrar algum script escrito para uma versão mais antiga. Pense na tendência recente em direção ao UTF-8 como uma codificação de arquivo de texto. Essa mudança exigiu a alteração de utilitários básicos, como tr. No passado, o texto simples era quase sempre 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 traceita diferentes opções de linha de comando para especificar coisas como "maiúsculas" ou "alfanuméricas".

  4. Uma das melhores maneiras de "proteger" seus filtros é não depender de um layout de texto específico. Por exemplo, não faça cut -c10-24, o que depende das posições de uma linha. Use em cut -f2vez disso, o que cortaria o segundo campo separado por tabulação. awkdivide qualquer linha de entrada em $ 1, $ 2, $ 3 ... que são espaços em branco separados por padrão. Dependa de conceitos de nível superior, como "campos", em vez de conceitos de nível inferior, como posição da coluna. Além disso, use expressões regulares: sede awkambos 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 o 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ê apenas não

Bruce Ediger
fonte
9

Primeiro, respostas muito breves para suas perguntas:

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

Quando você diz "API", está usando um termo que (para o bem ou para o mal) implica muita formalidade nas convenções de entrada / saída do filtro. Muito (e eu quero dizer "muito") em termos gerais, as principais convenções para dados que são ameaçáveis ​​à 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 de / etc / passwd. Porém, essas convenções padrão provavelmente são violadas em algum grau com mais frequência do que são seguidas à risca.

  • Existem muitos filtros (geralmente escritos em awk ou perl) que analisam os formatos de entrada de várias linhas.
  • Existem muitos padrões de entrada (por exemplo, / var / log / messages) em que não há uma 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 que você pode fazer.

  • Como @ jw013 disse , veja o que dizem os padrões posix. Obviamente, o posix não especifica todos os comandos que você deseja usar como fontes de entrada.
  • Se você deseja que seus scripts sejam portáveis, tente evitar as idiossincrasias de qualquer versão do comando que você tiver, não é necessária. Por exemplo, muitas versões GNU de comandos unix padrão possuem extensões não padrão. Isso pode ser útil, mas você deve evitá-los se desejar a portabilidade máxima.
  • Tente aprender quais subconjuntos de comandos, argumentos e formatos de saída tendem a ser estáveis ​​nas plataformas. Infelizmente, isso requer acesso a várias plataformas juntamente com o tempo, porque essas diferenças não serão anotadas em nenhum lugar, nem mesmo informalmente.

No final, você não pode se proteger totalmente dos problemas com os quais se preocupa, e não há um lugar único 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 em pequena escala, isso simplesmente não é um problema

Dale Hagglund
fonte
5

Abrangendo apenas 1) da sua pergunta.

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

Percorrendo uma lista de ferramentas básicas, há poucas que eu também atribuiria produtor , em vez de apenas filtrar:

  • wc - imprime número de bytes, palavras, linhas - formato muito simples, portanto, é absolutamente improvável que seja alterado e, além disso, não é muito provável que seja usado em um script.
  • diff - evoluímos diferentes formatos de saída, mas não ouvi nenhum problema. Também normalmente não é usado sem supervisão.
  • date - Agora, aqui realmente precisamos cuidar do que produzimos, especialmente em relação à localidade do sistema. Mas, caso contrário, o formato de saída é baseado em RFC, pois você não o especifica exatamente.
  • cal - não vamos falar sobre isso, eu sei que o formato de saída difere muito entre os sistemas.
  • ls , quem , u , ultimo - não posso ajudar se você quiser analisar ls, simplesmente não era para ser. Além disso, quem, w, por último, são mais interativos; Se você os usar em um script, terá que cuidar do que faz.
  • o tempo foi apontado em outro post. Mas sim, é o mesmo que com sl. Mais para uso local / interativo. E o bash embutido é muito diferente da versão GNU, e a versão GNU possui bugs não corrigidos por muitos anos. Só 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 hackish das coisas (na verdade, eu não as uso em scripts) e presumivelmente em formatos de E / S muito estáveis.

Há outra área com um risco muito maior de quebra, a interface da linha de comandos. A maioria das ferramentas possui recursos diferentes nos 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 existem muitas sutilezas e particularidades nas implementações de regex.
  • Simplesmente não use interruptores sofisticados. Você pode facilmente usar, man 1p findpor exemplo, para ler a página de manual de localização do POSIX, em vez da página de manual do sistema. No meu sistema, preciso do manpages-posix instalado.

E mesmo ao usar essas opções, normalmente não há erros sutilmente introduzidos e envenenam seus dados. A maioria dos programas simplesmente se recusa a trabalhar com um switch desconhecido.

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

Além disso, nos raros locais em que 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 funcionar para você, o fez 20 anos antes e ocorrerá em 20 anos). , também). Esse é um corolário da simplicidade das ferramentas.

Jo So
fonte
1

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

Quanto à compatibilidade, geralmente revertemos para verificar os números de versão dos filtros individuais. Não que eles mudem muito, mas quando você deseja usar um novo recurso e ainda deseja que o script seja executado em versões mais antigas, é necessário "defini-lo" de alguma forma. Praticamente não há mecanismo de relatório de capacidade, exceto para gravação manual de casos de teste.

lynxlynxlynx
fonte
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 possui sinalizadores de compatibilidade quando é alterado de qualquer maneira.

Os scripts escritos em um sistema tendem a continuar funcionando, mas geralmente quebram outro.

Alex Chamberlain
fonte