Script de shell Bash executando fora de ordem quando passado sobre o stdin para o contêiner LXC

3

Estou passando o seguinte shell script simples para bash em um contêiner LXC:

apt-get update
apt-get install postgresql -y

sudo -u postgres psql -c 'create database dvdrental;'

O comando real que estou usando para executá-lo é:

cat sample.sh | lxc-attach -n test-container -- /bin/bash

A razão pela qual estou fazendo dessa maneira, em vez de carregar o script no contêiner e executar dessa forma, é que esta é apenas a prova de conceito de um aplicativo muito mais complexo que estamos construindo e que precisa assumir comandos sobre stdin e executá-los no contêiner.

Parece funcionar muito bem, exceto por uma coisa. Ele é movido para o comando psql enquanto o postgresql ainda está sendo instalado, ou seja,

[...]
Get:21 http://archive.ubuntu.com/ubuntu/ trusty/main ssl-cert all 1.0.33 [16.6 kB]
Get:22 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-common all 154ubuntu1 [103 kB]
Get:23 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql-9.3 amd64 9.3.10-0ubuntu0.14.04 [2,669 kB]
Get:24 http://archive.ubuntu.com/ubuntu/ trusty-updates/main postgresql all 9.3+154ubuntu1 [5,038 B]
Fetched 5,834 kB in 28s (207 kB/s)                                             
Preconfiguring packages ...

    sudo -u postgres psql -c 'create database dvdrental;'
Selecting previously unselected package libroken18-heimdal:amd64.
(Reading database ... 14599 files and directories currently installed.)
Preparing to unpack .../libroken18-heimdal_1.6~git20131207+dfsg-1ubuntu1.1_amd64.deb ...
Unpacking libroken18-heimdal:amd64 (1.6~git20131207+dfsg-1ubuntu1.1) ...
Selecting previously unselected package libasn1-8-heimdal:amd64.
[...]

Observe a existência da linha sudo -u postgres psql -c 'create database dvdrental;' no meio da saída. Curiosamente, ele sempre aparece logo após a conclusão da parte de download do comando apt-get ...

Alguém sabe o que pode estar causando isso?

    
por Soviero 30.11.2015 / 04:28

1 resposta

5

Ooooh, isso é divertido .

A resposta curta: acontece lá porque o apt (ou algo que se bifurca) está lendo stdin nesse ponto em sua execução, e lê as linhas restantes do script porque é isso que ainda está em stdin naquele ponto. A pequena correção: coloque </dev/null no final da linha apt-get install e continue com o seu dia.

A resposta longa (sério, isso é um biggun): não há nada de especial sobre stdin / stdout / stderr, do ponto de vista de um processo em execução. Eles são apenas descritores de arquivos, e os descritores de arquivos são compartilhados entre processos quando são bifurcados. Então, o que acontece (mais ou menos) é:

  1. A cópia do bash executando interativamente no seu terminal abre um novo pipe (2), em seguida, bifurca um novo processo, que fecha o stdout existente e, em seguida, torna o descritor do arquivo stdout (1) o final do gravador do tubo (veja dup2 (2)). Esse processo filho, em seguida, exec s cat sample.sh , que lê o arquivo e o grava no que ele considera é stdout (mas é realmente o finalizador de um pipe).

  2. A cópia do bash executando interativamente no seu terminal bifurca outro novo processo, desta vez fechando o stdin existente, e então faz com que o descritor do arquivo stdin (0) seja o final do mesmo canal discutido anteriormente (novamente, com uma chamada para dup2 ). Este processo então exec s seu processo lxc-attach .

    Se nada interferir com stdin ao longo do caminho (o que não acontece, neste caso particular) então todo processo que é bifurcado daquele que obteve o final do canal como stdin também tem exatamente o mesmo descritor de arquivo, anexado ao mesmo pipe, que tinha o conteúdo de sample.sh , como stdin. Qualquer Qualquer processo que lê a partir desse descritor de arquivo agora terá consumido os bytes lidos, e nenhum outro processo que lê a partir desse descritor de arquivo irá obter esses bytes específicos. Tome nota cuidadosa disso; você vai ver esse material novamente.

  3. Quando a festança no final de sua extravagância no encanamento de estilo armário de água italiano finalmente começa, vai ler "alguns" dos dados do tubo que é o seu stdin (porque é o que faz bash, quando chamado sem argumentos e sem um tty como stdin). Através da mágica de strace , eu apenas confirmei que bash realmente leu sua entrada um caractere de cada vez (ao invés de lê-lo em, digamos, 4k pedaços), então cada caractere individual que não faz parte de um comando que o bash tem, ou está atualmente executando, ainda estará sentado no pipe-which-bash-has-as-its-stdin.

  4. Quando o bash executa o segundo comando no seu script, apt-get install la la la, ele bifurca um novo processo. Que herda todos os descritores de arquivo do bash, incluindo (o mais importante) nosso bom amigo, o pipe-que-é-stdin . Isto também acontece para qualquer processo que apt-get forks (isto é, deixe-me assegurar-lhe bastante). Um deles, ou apt-get , está decidindo ler stdin e escrever o que lê em stdout (ou possivelmente stderr).

  5. Quando o apt-get install termina, o bash descobre o que a próxima coisa a executar é lendo o stdin mais uma vez. Porque algo else já leu tudo do canal, no entanto, não resta nada, e o bash interpreta "oh bem, acho que terminei então" e sai. Novamente, o pipe está vazio porque algo já o leu a seco, e tudo o que compartilha um único descritor de arquivo compartilha a recompensa dele.

A solução para o problema do "stdin compartilhado" é, sem surpresa, parar de passar por aí como um bong em uma festa de fraternidade. Já que você não pode parar fork (2) de automaticamente dar a todos os mesmos descritores de arquivos, você precisa dizer ao bash, em vez disso, dar apt-get (e qualquer outra coisa tomando um gole ilícito daquele doce) else para ativar, em vez disso. A coisa mais fácil de dar é /dev/null - aquela fonte sempre fiel, nunca completa, de todo o seu "Dave não está aqui, cara" delicia. Esse é o domínio do "redirecionamento de entrada", que é o que o </dev/null faz - ele diz: "hey bash, antes de você exec that apt-get , trocar stdin (descritor de arquivo 0) pelo descritor de arquivo obter da abertura /dev/null ".

Um exercício para o leitor terminar: tente colocar </dev/zero após o comando apt-get install e explique por que o que acontece acontece.

    
por 30.11.2015 / 05:43