Pipelines e Listas de comandos no Bash

Curso de Bash

Seção do curso sobre pipelines e lista de comandos.

Conteúdo

  1. Pipelines
  2. Duplicando a saída com tee
  3. Da entrada padrão para argumento com xargs
  4. Lista de comandos

Pipelines

Pipelines (|) permitem que a saída padrão de um processo seja conectada à entrada padrão de um outro.

Com isso as Pipelines evitam a tarefa intermediária de salvar a saída de um processo em arquivo para depois enviar esse arquivo para a entrada de outro processo.

P. ex., abaixo, lista com todos os processos é salva no arquivo processos e então a quantidade de linhas 1 de processos é contada.

ps -ef > processos
wc -l < processos

Utilizando |, temos o mesmo resultado, porém sem o intermédio do arquivo.

ps -ef | wc -l
308

Aqui listamos todos os usuários, filtramos as linhas terminando em bash$, cortamos a primeira coluna e exibimos no paginador less.

getent passwd | grep "bash$" | cut -d ":" -f 1 | less
root
caio
usuario

1 tecnicamente a quantidade de caracteres novalinha.

Duplicando a saída com tee

tee lê da entrada padrão e escreve tanto na saída padrão e quanto nos arquivos enviados como argumento.

tee arq1 arq2 <<< "Uma linha qualquer"
Uma linha qualquer

Ambos os arquivos conterão o mesmo conteúdo.

head arq2 arq1
==> arq2 <==
Uma linha qualquer

==> arq1 <==
Uma linha qualquer

tee também é útil como comando intermediário em uma pipeline já que pode escrever em um arquivo os resultados até então processados e também escrever na saída padrão.

No exemplo abaixo, ls exibe lista de arquivos/diretórios ordenados pela data de modificação, primeiro os mais recentes. grep remove as linhas que não representam arquivos. tee escreve a lista de arquivos em configs e na saída padrão. head então exibe os 5 arquivos modificados mais recentemente.

ls -lt /etc | grep "^-" | tee configs | head -n 5
-rw-r--r--  1 root root      138 Dec  6 18:28 shells
-rw-r--r--  1 root root   106581 Dec  6 10:01 ld.so.cache
-rw-r--r--  1 root root    57160 Dec  6 10:01 mailcap
-rw-r--r--  1 root root     3216 Dec  3 22:12 passwd
-rw-r-----  1 root shadow   1748 Dec  3 22:09 shadow

Da entrada padrão para argumento com xargs

Alguns comandos não leem dados da entrada padrão o que os não tornaria candidatos a serem utilizados em pipelines sem xargs.

xargs recebe um comando como argumento e todas as linhas lidas na entrada padrão de xargs são concatenadas em uma só linha que então é definida como argumento para aquele comando.

O arquivo diretorios é criado abaixo.

printf "%s\n" um_dir outro_dir "dir ferente" > diretorios
head diretorios

⚠️ Note que há um caractere de espaço entre dir e ferente na última linha do arquivo , então cada um deles acaba sendo um argumento diferente para o comando no argumento de xargs.

um_dir
outro_dir
dir ferente

Sem argumento, xargs executa o comando echo.

tail diretorios | xargs
um_dir outro_dir dir ferente

Abaixo xargs executa mkdir -v com os argumentos um_dir outro_dir dir ferente.

tail diretorios | xargs mkdir -v
mkdir: created directory 'um_dir'
mkdir: created directory 'outro_dir'
mkdir: created directory 'dir'
mkdir: created directory 'ferente'

No exemplo abaixo grep escreve na saída as linhas que não iniciam com #. xargs executa realpath com a concatenção das linhas oriundas de grep como argumento. realpath escreve na saída a resolução de cada um dos caminhos especificados como argumento. tee escreve na saída e no arquivo shells.

grep "^[^#]" /etc/shells | xargs realpath | tee shells
/usr/bin/dash
/usr/bin/bash
/usr/bin/bash
/usr/bin/bash
/usr/bin/bash
/usr/bin/dash
/usr/bin/dash

A opção -I de xargs permite especificar uma sequência de caracteres que identificará onde os argumentos vindos da entrada serão inseridos no comando.

Abaixo sort -u escreve na saída as linhas que são únicas. xargs executa cp substituindo % pela concatenação das linhas lidas na entrada. cp copia cada argumento para . (diretório atual).

sort -u shells | xargs -I % cp -v % .
'/usr/bin/bash' -> './bash'
'/usr/bin/dash' -> './dash'

Lista de comandos

Comandos podem ser especificados em sequência em uma mesma linha. Nas próximas subseções abordaremos os operadores necessários.

Execução condicional de comando com &&

&& apenas executa o comando à sua direita caso o comando à sua esquerda tenho finalizado com sucesso.

mkdir cria o diretóio e então ls lista o diretório.

mkdir diretorio && ls -ld diretorio
drwxrwxr-x 2 caio caio 4096 Dec  9 14:55 diretorio

Como mkdir não pôde criar o diretório, ls não foi executado.

mkdir . && ls .
mkdir: cannot create directory ‘.’: File exists

Múltiplos && podem ser especificados.

mkdir diretorio2 && cd diretorio2 && pwd
/home/caio/diretorio2

Execução condicional de comando com ||

|| executa o comando à direita apenas se o comando da esquerda terminou com erros.

ls não pôde listar seu_dir então mkdir cria o diretório seu_dir.

ls seu_dir || mkdir -v seu_dir
ls: cannot access 'seu_dir': No such file or directory
mkdir: created directory 'seu_dir'

mv renomeou o diretório de seu_dir para teu_dir e então cp não foi executado.

mv -v seu_dir teu_dir || cp -v seu_dir teu_dir
renamed 'seu_dir' -> 'teu_dir'

Múltiplos || podem ser especificados.

No exemplo abaixo, mkdir cria o diretório dir_ou_arq, então touch e ls não são executados.

mkdir -v dir_ou_arq || touch dir_ou_arq || ls -ld dir_ou_arq
mkdir: created directory 'dir_ou_arq'

Lista de comandos com ;

; permite que múltiplos comandos sejam executados independentemente na mesma linha.

whoami; pwd; date
caio
/home/caio
Wed 09 Dec 2020 16:06:36 -03

Ainda que um dos comandos resulte em falha, os outros são executados normalmente.

whoiam; pwd; date
whoiam: command not found
/home/caio
Wed 09 Dec 2020 16:22:44 -03

Redirecionamentos se limitam às entradas/saídas dos comandos especificados.

Como abaixo, em que apenas a saída de date é escrita no arquivo data_e_hora.

whoami; head <<< "ABC"; date >> data_e_hora
caio
ABC

Agrupando comandos com {}

Os comandos listados entre {} são executados como uma unidade. Funcionalidade especialmente útil para redirecionar a saída conjunta dos comandos.

Assim como com ;, os comandos são executados sequencialmente, ou seja, o segundo comando apenas é executado quando o primeiro termina.

{ deve ser seguida de espaço. Todos os comandos, incluindo o último, devem terminar com ;.

{ whoami; pwd; date; } >> conjunto
head conjunto
caio
/home/caio
Wed 09 Dec 2020 16:18:06 -03

❌ Deixar de inserir o caractere espaço depois de { é um erro.

{whoami; pwd; date;}
bash: syntax error near unexpected token `}'

❌ Outro erro é deixar de inserir ; no último comando. Na situação abaixo Bash interpreta o último comando como date}, ignora o caractere novalinha inserido por Enter/ e aguarda por } para fechar o grupo.

{ whoami; pwd; date}
>

Ao inserir }, os comandos são executados, com execeção do último (date}), que inexiste.

> }
caio
/home/caio
Command 'date}' not found, did you mean:
  command 'date' from deb coreutils (8.32-3ubuntu1)

time executa o grupo de comandos e então exibe o tempo passado entre o ínicio e o fim da execução do grupo.

time { whoami; pwd; date; }
caio
/home/caio
Wed 09 Dec 2020 16:28:32 -03

real    0m0.003s
user    0m0.003s
sys     0m0.000s

Já que é tratado como se fosse um só comando, {} pode ser utilizado entre || e &&.

No exemplo abaixo, ls gera um erro pois dir não existe, então o grupo de comandos é executado. No grupo, mkdir cria o diretório dir e rm falha ao excluir o diretório atual (.) e por falhar, ps não é executado.

ls dir || { mkdir -v dir; rm .; } && ps
ls: cannot access 'dir': No such file or directory
mkdir: created directory 'dir'
rm: cannot remove '.': Is a directory
Escrito por Caio Santesso.

Comentários

  • Conteúdo dos posts, exceto onde indicado contrário, licenciado sob a licença CC BY-SA 4.0 .