103.4 Lição 2
Certificação: |
LPIC-1 |
---|---|
Versão: |
5.0 |
Tópico: |
103 Comandos GNU e Unix |
Objetivo: |
103.4 Usando fluxos, pipes e redirecionamentos |
Lição: |
2 de 2 |
Introdução
Um aspecto da filosofia Unix afirma que cada programa precisa ter um propósito específico e não deve tentar incorporar recursos fora de seu escopo. Mas manter as coisas simples não significa que os resultados serão menos elaborados, já que diferentes programas podem ser encadeados para produzir uma saída combinada. O caractere de barra vertical |
, também conhecido como símbolo pipe, pode ser usado para criar uma canalização (pipeline) conectando a saída de um programa diretamente à entrada de outro programa, ao passo que a substituição de comandos permite armazenar a saída de um programa em uma variável ou usá-lo diretamente como argumento para outro comando.
Pipes
Ao contrário dos redirecionamentos, com os pipes os dados fluem da esquerda para a direita na linha de comando e o destino é outro processo, não um caminho do sistema de arquivos, descritor de arquivo ou Here document. O caractere de barra vertical |
manda o shell iniciar todos os comandos distintos ao mesmo tempo e conectar a saída do comando anterior à entrada do comando seguinte, da esquerda para a direita. Por exemplo, em vez de usar redirecionamentos, o conteúdo do arquivo /proc/cpuinfo
enviado para a saída padrão por cat
pode ser canalizado para o stdin de wc
com o seguinte comando:
$ cat /proc/cpuinfo | wc 208 1184 6096
Na ausência do caminho para um arquivo, wc
conta o número de linhas, palavras e caracteres que recebe em seu stdin, como é o caso no exemplo. Muitos pipes podem estar presentes em um comando composto. No exemplo a seguir, dois pipes são usados:
$ cat /proc/cpuinfo | grep 'model name' | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
O conteúdo do arquivo /proc/cpuinfo
produzido por cat /proc/cpuinfo
foi canalizado para o comando grep 'model name'
, que em seguida seleciona apenas as linhas contendo o termo model name
. A máquina que executa o exemplo tem muitas CPUs, portanto existem linhas repetidas com model name
. O último pipe conecta grep 'model name'
ao uniq
, que é responsável por pular qualquer linha idêntica à anterior.
Os pipes podem ser combinados com redirecionamentos na mesma linha de comando. O exemplo anterior pode ser reescrito em uma forma mais simples:
$ grep 'model name' </proc/cpuinfo | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
O redirecionamento de entrada para grep
não é estritamente necessário, pois grep
aceita um caminho de arquivo como argumento, mas o exemplo demonstra como construir comandos combinados.
Pipes e redirecionamentos são exclusivos, ou seja, uma origem pode ser mapeada para apenas um destino. Ainda assim, é possível redirecionar uma saída para um arquivo e ainda vê-la na tela com o programa tee
. Para isso, o primeiro programa envia sua saída para o stdin de tee
e um nome de arquivo é fornecido a este último para armazenar os dados:
$ grep 'model name' </proc/cpuinfo | uniq | tee cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz $ cat cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
A saída do último programa na cadeia, gerada por uniq
, é exibida e armazenada no arquivo cpu_model.txt
. Para não sobrescrever o conteúdo do arquivo fornecido, e sim anexar dados a ele, a opção -a
deve ser fornecida para tee
.
Apenas a saída padrão de um processo é capturada por um pipe. Digamos que você precise passar por um longo processo de compilação na tela e, ao mesmo tempo, salvar tanto a saída padrão quanto o erro padrão em um arquivo para inspeção posterior. Supondo que seu diretório atual não tenha um Makefile, o seguinte comando gerará um erro:
$ make | tee log.txt make: *** No targets specified and no makefile found. Stop.
Embora exibida na tela, a mensagem de erro gerada por make
não foi capturada por tee
e o arquivo log.txt foi criado vazio. É preciso fazer um redirecionamento antes que um pipe possa capturar o stderr:
$ make 2>&1 | tee log.txt make: *** No targets specified and no makefile found. Stop. $ cat log.txt make: *** No targets specified and no makefile found. Stop.
Neste exemplo, o stderr de make
foi redirecionado para o stdout, de forma que o tee
foi capaz de capturá-lo com um pipe, exibi-lo na tela e salvá-lo no arquivo log.txt
. Em casos como esse, pode ser útil salvar as mensagens de erro para inspeção posterior.
Substituição de comando
Outro método para capturar a saída de um comando é a substituição de comando. Ao colocar um comando entre crases, o Bash o substitui por sua saída padrão. O exemplo a seguir mostra como usar o stdout de um programa como argumento para outro programa:
$ mkdir `date +%Y-%m-%d` $ ls 2019-09-05
A saída do programa date
, a data atual formatada como ano-mês-dia, foi usada como um argumento para criar um diretório com o mkdir
. Um resultado idêntico é obtido usando $()
em vez de crases:
$ rmdir 2019-09-05 $ mkdir $(date +%Y-%m-%d) $ ls 2019-09-05
O mesmo método pode ser usado para armazenar a saída de um comando como uma variável:
$ OS=`uname -o` $ echo $OS GNU/Linux
O comando uname -o
retorna o nome genérico do sistema operacional atual, que foi armazenado na variável de sessão OS
. Atribuir a saída de um comando a uma variável é muito útil em scripts, possibilitando armazenar e avaliar os dados de várias maneiras distintas.
Dependendo da saída gerada pelo comando substituído, a substituição do comando interno do shell pode não ser apropriada. Um método mais sofisticado para usar a saída de um programa como argumento de outro programa emprega um intermediário chamado xargs
. O programa xargs
usa o conteúdo que recebe via stdin para executar um determinado comando com o conteúdo como argumento. O exemplo a seguir mostra o xargs
executando o programa identify
com argumentos fornecidos pelo programa find
:
$ find /usr/share/icons -name 'debian*' | xargs identify -format "%f: %wx%h\n" debian-swirl.svg: 48x48 debian-swirl.png: 22x22 debian-swirl.png: 32x32 debian-swirl.png: 256x256 debian-swirl.png: 48x48 debian-swirl.png: 16x16 debian-swirl.png: 24x24 debian-swirl.svg: 48x48
O programa identify
é parte do ImageMagick, um conjunto de ferramentas de linha de comando para inspecionar, converter e editar a maioria dos tipos de arquivo de imagem. No exemplo, o xargs
pegou todos os caminhos listados por find
e os colocou como argumentos para identify
, que então exibe as informações para cada arquivo formatado conforme exigido pela opção -format
. Os arquivos encontrados pelo find
no exemplo são imagens contendo o logotipo da distribuição em um sistema de arquivos Debian. -format
é um parâmetro para identify
, não para xargs
.
A opção -n 1
exige que o xargs
execute o comando fornecido com apenas um argumento por vez. No caso do exemplo, em vez de passar todos os caminhos encontrados por find
como uma lista de argumentos para identify
, o uso de xargs -n 1
executaria o comando identify
para cada caminho separadamente. Usar -n 2
executaria o identify
com dois caminhos como argumentos, -n 3
com três caminhos como argumentos e assim por diante. Da mesma forma, quando o xargs
processa conteúdos com várias linhas — como é o caso com a entrada fornecida por find
— a opção -L
pode ser usada para limitar quantas linhas serão usadas como argumentos por execução do comando.
Note
|
Pode ser desnecessário usar o |
Se os caminhos contiverem caracteres de espaço, é importante executar o find
com a opção -print0
. Esta opção instrui o find
a usar um caractere nulo entre cada entrada para que a lista possa ser analisada corretamente por xargs
(a saída foi suprimida):
$ find . -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 du | sort -n
A opção -0
diz ao xargs
que o caractere nulo deve ser usado como separador. Dessa forma, os caminhos de arquivo fornecidos pelo find
são analisados corretamente, mesmo se contiverem espaços em branco ou outros caracteres especiais. O exemplo anterior mostra como usar o comando du
para descobrir o uso de espaço em disco por cada arquivo encontrado e em seguida classificar os resultados por tamanho. A saída foi suprimida para fins de concisão. Observe que para cada critério de pesquisa é necessário incluir a opção -print0
para find
.
Por padrão, o xargs
coloca por último os argumentos do comando executado. Para mudar esse comportamento, usamos a opção -I
:
$ find . -mindepth 2 -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 -I PATH mv PATH ./
No último exemplo, todo arquivo encontrado por find
é movido para o diretório atual. Como o(s) caminho(s) de origem devem ser informados para o mv
antes do caminho de destino, um termo de substituição é dado à opção -I
do xargs
, que é então apropriadamente colocado junto a mv
. Ao usar o caractere nulo como separador, não é necessário colocar o termo de substituição entre aspas.
Exercícios Guiados
-
É conveniente salvar a data de execução das ações realizadas por scripts automáticos. O comando
date +%Y-%m-%d
mostra a data atual no formato ano-mês-dia. Como a saída desse comando pode ser armazenada em uma variável do shell chamadaTODAY
usando a substituição de comando? -
Usando o comando
echo
, como o conteúdo da variávelTODAY
pode ser enviado para a saída padrão do comandosed s/-/./g
? -
Como a saída do comando
date +%Y-%m-%d
pode ser usada como uma Here string para o comandosed s/-/./g
? -
O comando
convert image.jpeg -resize 25% small/image.jpeg
cria uma versão menor deimage.jpeg
e coloca a imagem resultante em um arquivo com o mesmo nome dentro do subdiretóriosmall
. Usando oxargs
, como é possível executar o mesmo comando para todas as imagens listadas no arquivofilelist.txt
?
Exercícios Exploratórios
-
Uma rotina de backup simples cria periodicamente uma imagem da partição
/dev/sda1
comdd < /dev/sda1 > sda1.img
. Para realizar futuras verificações de integridade de dados, a rotina também gera um hash SHA1 do arquivo comsha1sum < sda1.img > sda1.sha1
. Adicionando pipes e o comandotee
, como esses dois comandos poderiam ser combinados em um só? -
O comando
tar
é usado para empacotar muitos arquivos em um só, preservando a estrutura de diretórios. A opção-T
permite especificar um arquivo contendo os caminhos a arquivar. Por exemplo,find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
cria o arquivo tar compactadoetc.tar.xz
a partir da lista fornecida pelo comandofind
(a opção-T -
indica a entrada padrão como a lista de caminhos). Para evitar possíveis erros de análise devido a caminhos que contêm espaços, quais opções deveriam estar presentes para os comandosfind
etar
? -
Em vez de abrir uma nova sessão remota do shell, o comando
ssh
pode simplesmente executar um comando indicado como argumento:ssh user@storage "remote command"
. Dado quessh
também permite redirecionar a saída padrão de um programa local para a entrada padrão do programa remoto, como o comandocat
canalizaria um arquivo local chamadoetc.tar.gz
para/srv/backup/etc.tar.gz
emuser@storage
através dessh
?
Resumo
Esta lição cobre as técnicas tradicionais de comunicação entre processos empregadas pelo Linux. A canalização de comandos cria um canal de comunicação unilateral entre dois processos e a substituição de comandos permite armazenar a saída de um processo em uma variável shell. A lição passa pelas seguintes etapas:
-
Como pipes podem ser usados para transmitir a saída de um processo para a entrada de outro processo.
-
A finalidade dos comandos
tee
exargs
. -
Como capturar a saída de um processo com a substituição de comando, armazenando-a em uma variável ou usando-a diretamente como parâmetro para outro comando.
Os comandos e procedimentos abordados foram:
-
Canalização de comandos com
|
. -
Substituição de comandos com crases e
$()
. -
Os comandos
tee
,xargs
efind
.
Respostas aos Exercícios Guiados
-
É conveniente salvar a data de execução das ações realizadas por scripts automáticos. O comando
date +%Y-%m-%d
mostra a data atual no formato ano-mês-dia. Como a saída desse comando pode ser armazenada em uma variável do shell chamadaTODAY
usando a substituição de comando?$ TODAY=`date +%Y-%m-%d`
ou
$ TODAY=$(date +%Y-%m-%d)
-
Usando o comando
echo
, como o conteúdo da variávelTODAY
pode ser enviado para a saída padrão do comandosed s/-/./g
?$ echo $TODAY | sed s/-/./g
-
Como a saída do comando
date +%Y-%m-%d
pode ser usada como uma Here string para o comandosed s/-/./g
?$ sed s/-/./g <<< `date +%Y-%m-%d`
ou
$ sed s/-/./g <<< $(date +%Y-%m-%d)
-
O comando
convert image.jpeg -resize 25% small/image.jpeg
cria uma versão menor deimage.jpeg
e coloca a imagem resultante em um arquivo com o mesmo nome dentro do subdiretóriosmall
. Usando oxargs
, como é possível executar o mesmo comando para todas as imagens listadas no arquivofilelist.txt
?$ xargs -I IMG convert IMG -resize 25% small/IMG < filelist.txt
ou
$ cat filelist.txt | xargs -I IMG convert IMG -resize 25% small/IMG
Respostas aos Exercícios Exploratórios
-
Uma rotina de backup simples cria periodicamente uma imagem da partição
/dev/sda1
comdd < /dev/sda1 > sda1.img
. Para realizar futuras verificações de integridade de dados, a rotina também gera um hash SHA1 do arquivo comsha1sum < sda1.img > sda1.sha1
. Adicionando pipes e o comandotee
, como esses dois comandos poderiam ser combinados em um só?# dd < /dev/sda1 | tee sda1.img | sha1sum > sda1.sha1
-
O comando
tar
é usado para empacotar muitos arquivos em um só, preservando a estrutura de diretórios. A opção-T
permite especificar um arquivo contendo os caminhos a arquivar. Por exemplo,find /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
cria o arquivo tar compactadoetc.tar.xz
a partir da lista fornecida pelo comandofind
(a opção-T -
indica a entrada padrão como a lista de caminhos). Para evitar possíveis erros de análise devido a caminhos que contêm espaços, quais opções deveriam estar presentes para os comandosfind
etar
?Options
-print0
and--null
:$ find /etc -type f -print0 | tar -cJ -f /srv/backup/etc.tar.xz --null -T -
-
Em vez de abrir uma nova sessão remota do shell, o comando
ssh
pode simplesmente executar um comando indicado como argumento:ssh user@storage "remote command"
. Dado quessh
também permite redirecionar a saída padrão de um programa local para a entrada padrão do programa remoto, como o comandocat
canalizaria um arquivo local chamadoetc.tar.gz
para/srv/backup/etc.tar.gz
emuser@storage
através dessh
?$ cat etc.tar.gz | ssh user@storage "cat > /srv/backup/etc.tar.gz"
or
$ ssh user@storage "cat > /srv/backup/etc.tar.gz" < etc.tar.gz