Usando / dev / stdin e um heredoc para passar um arquivo da linha de comando

1

Estou curioso sobre a teoria de como heredocs pode ser passado como um arquivo para um utilitário de linha de comando.

Recentemente, descobri que posso passar um arquivo como heredoc.

Por exemplo:

awk '{ split($0, arr, " "); print arr[2] }' <<EOF
foo bar baz
EOF
bar

Isso é vantajoso para mim por vários motivos:

  • Heredocs melhoram a legibilidade para entradas de várias linhas.
  • Eu não preciso memorizar cada sinalizador de utilitários para passar o conteúdo do arquivo da linha de comando.
  • posso usar aspas simples e duplas nos arquivos fornecidos.
  • Eu posso controlar a expansão do shell.

Por exemplo:

ruby <<EOF
puts "'hello $HOME'"
EOF
'hello /Users/mbigras'

ruby <<'EOF'
puts "'hello $HOME'"
EOF
'hello $HOME'

Não estou claro o que está acontecendo. Parece que o shell acha que o heredoc é um arquivo com conteúdo igual ao valor do heredoc. Eu tenho essa técnica usada com gatos, mas ainda não tenho certeza do que estava acontecendo:

cat <<EOL
hello world
EOL
hello world

Eu sei que cat imprime o conteúdo de um arquivo, então presumivelmente esse heredoc é um arquivo temporário de algum tipo.

Estou confuso sobre o que exatamente está acontecendo quando "passar um heredoc para um programa de linha de comando".

Veja um exemplo usando o ansys-playbook . Eu passo ao utilitário um manual de jogo como heredoc; no entanto, ele falha, conforme mostrado usando echo $? :

ansible-playbook -i localhost, -c local <<EOF &>/dev/null
---
- hosts: all
  gather_facts: false
  tasks:
    - name: Print something
      debug:
        msg: hello world
EOF
echo $?
5

No entanto, se eu passar o utilitário o mesmo heredoc mas precedê-lo com /dev/stdin , ele terá êxito

ansible-playbook -i localhost, -c local /dev/stdin <<EOF &>/dev/null
---
- hosts: all
  gather_facts: false
  tasks:
    - name: Print something
      debug:
        msg: hello world
EOF
echo $?
0
  • O que exatamente está acontecendo quando alguém "passa um heredoc como um arquivo"?
  • Por que a primeira versão com ansible-playbook falha, mas a segunda versão é bem-sucedida?
  • Qual é o significado de passar /dev/stdin antes do heredoc?
  • Por que outros utilitários como ruby ou awk não precisam do /dev/stdin antes do heredoc?
por mbigras 19.02.2018 / 22:06

3 respostas

3

What precisely is going on when one "passes a heredoc as a file"?

Você não é. Aqui os documentos fornecem informações padrão, como um tubo. Seu exemplo

awk '{ ... }' <<EOF
foo bar baz
EOF

é exatamente equivalente a

echo foo bar baz | awk '{ ... }'

awk , cat e ruby são lidos a partir da entrada padrão, caso não recebam um nome de arquivo para ler na linha de comando. Essa é uma escolha de implementação.

Why does the first version with anisble-playbook fail but second version succeed?

ansible-playbook não lê a entrada padrão por padrão, mas requer um caminho de arquivo. Esta é uma escolha de design.

/dev/stdin é muito provável um link simbólico para /dev/fd/0 , que é uma maneira de falar sobre o processo atual descritor de arquivo # 0 (entrada padrão). Isso é algo exposto pelo seu kernel (ou biblioteca do sistema). O comando ansible-playbook abre /dev/stdin como um arquivo normal do sistema de arquivos e acaba lendo sua própria entrada padrão, que de outra forma seria ignorada.

Você provavelmente também tem /dev/stdout e /dev/stderr links para FDs 1 & 2, que você pode usar também se estiver dizendo algo onde colocar sua saída.

What is the significance of passing /dev/stdin before the heredoc?

É um argumento para o comando ansible-playbook .

Why do other utilities like ruby or awk not need the /dev/stdin before the heredoc?

Eles lêem a entrada padrão por padrão como uma opção de design, porque eles são feitos para serem usados em pipelines. Eles escrevem na saída padrão pelo mesmo motivo.

    
por 19.02.2018 / 22:21
2

Um documento aqui é um redirecionamento para a entrada padrão de um comando, assim como < . Isto significa que em qualquer lugar onde você pode usar < para redirecionar o conteúdo de um arquivo, você pode redirecionar o conteúdo de um documento aqui. O padrão POSIX lista aqui-documentos junto com os outros operadores de redirecionamento .

Em seu exemplo Ansible, ansible-playbook não lê, por padrão, seu fluxo de entrada padrão, pois espera um nome de arquivo. Dando-lhe /dev/stdin como o nome do arquivo e, em seguida, fornecendo o aqui-documento na entrada padrão, você ignora essa restrição no utilitário. O /dev/stdin "arquivo" sempre conterá o fluxo de dados de entrada padrão do processo atual.

ruby e awk , assim como muitos outros utilitários lerão da entrada padrão a menos que um nome de arquivo seja fornecido na linha de comando.

Então, você está tecnicamente errado quando diz "Parece que o shell acha que o heredoc é um arquivo com conteúdo igual ao valor do heredoc". Ele não age como um arquivo (com relação a ter um nome de arquivo e ser pesquisável), mas como um fluxo de dados na entrada padrão. Pelo menos do ponto de vista da utilidade.

A diferença é a mesma que entre

cat file

e

cat <file

Na primeira instância, cat abre o arquivo file , mas no segundo (que também é o que acontece com um aqui-documento), já que nenhum nome de arquivo foi dado como argumento para cat , cat apenas lê seu fluxo de entrada padrão (e o shell abre o arquivo ou fornece o aqui-documento, na entrada padrão para o utilitário). O utilitário não precisa saber se os dados fornecidos vêm de um arquivo, um pipe, um documento aqui ou alguma outra fonte de dados.

Como os documentos aqui são implementados pelo shell é de uma maneira sem importância, mas pode ser através do uso de um FIFO ou mesmo com um arquivo temporário.

    
por 19.02.2018 / 22:15
1

O que exatamente está acontecendo com o here-docs depende de como o shell implementa aqui -doc: ele pode ser feito com pipes internamente como no caso de dash ou com o descritor de arquivo temporário, como em bash . Então, em um caso, pode não ser possível lseek() , mas no outro - pode ser (o que para o usuário médio significa que você pode pular o conteúdo do aqui-doc). Veja a resposta relacionada .

Quanto ao caso de dois comandos ansible-playbook, também depende de como o comando é implementado (então, a menos que você leia o código fonte, você não saberá realmente). Alguns comandos simplesmente verificam se existe ou não um arquivo fornecido e não suportam stdin . Outros comandos, como awk e ruby , são projetados para esperar stdin ou um arquivo especificado na linha de comando.

O que você pode tentar fazer, no entanto, é se você estiver usando o Linux, executar strace ansible-playbook ...<other args> e ver as coisas que ele tenta abrir, o que ocorre com syscalls etc. Por exemplo, você verá que com strace -e open tail /dev/stdin <<< "Jello World" O comando tail irá tentar abrir /dev/stdin as file, enquanto trace -e open tail não.

    
por 19.02.2018 / 22:40