Skip to main content

103.4 Usar Streams, pipes e redirecionadores



Redirecionar fluxos de texto e conectá-los a fim de eficientemente processar os dados. As tarefas incluem redirecionamento da entrada padrão, da saída padrão e dos erros padrão, canalização (piping) da saída de um comando à entrada de outro comando, usar a saída de um comando como argumento para outro comando e enviar a saída de um comando simultaneamente para a saída padrão e um arquivo.



Redirecionamento de Entrada, Saída e erro padrão



Entrada Padrão (stdin - 0)

A entrada padrão é denominada stdin, é tudo que envia dados para máquina, por padrão o que envia esses dados é o teclado, mas você pode mudar isso, mudando a entrada padrão, por exemplo, a entrada poderia ser um arquivo, como usamos no CPIO.

Para mudar a entrada padrão você deve usar o símbolo de menor <, o uso de um único símbolo de menor informa que a entrada não é mais padrão, e que a entrada vem de um arquivo, como no CPIO, isso simplesmente é uma mudança de entrada padrão.


Crie um arquivo chamado teste.txt

Conteúdo de teste.txt

OLA

Mudando a entrada padrão

linux:~/$ tr 'A-Z' 'a-z' < teste.txt
ola

O comando tr só funciona por meio de redirecionamento de entrada, recebendo a entrada de alguma forma, por isso, mudamos a entrada padrão (A entrada padrão nesse caso é um arquivo).


Here document

Um “Documento aqui” traduzido ao pé da letra, é quando utilizamos dois símbolos de menor <<, esse é um assunto um pouco avançado, mas vou tentar exemplificar para ficar mais fácil de entender.


Uma forma que deve ser conhecida pela maioria é o uso do EOF com o comando cat, EOF significa 'end-of-file' (fim de arquivo), é usada para expressar que chegou ao fim do arquivo e a partir dali, não pode ser mais lido nenhum dado.

linux:~/$ cat << EOF
> Ola
> Tudo Bem?
EOF

Vale ressaltar que o EOF é um delimitador, podendo ser usado qualquer outro delimitador. Dessa forma, podemos criar “documentos” na entrada padrão, com espaços, quebra de linha, tab, entre outros, sem a necessidade de ter isso num arquivo. Para jogar o que for digitado num arquivo, usa-se um redirecionador de saída padrão cat << EOF > texte.txt.


Here strings

Uma “cadeia de caracteres”, é quando passamos uma cadeia de caracteres como entrada para um comando, isso é feito porque no redirecionamento de entrada, no modo simples (usando apenas um símbolo de menor) ele irá procurar um arquivo, usando Here Strings, você passa o valor diretamente, sem ter que ter esse valor num arquivo.


Exemplo, você quer converter o dado de uma variável para qualquer outro formato, no exemplo abaixo, vamos converter os dados para maiúsculo.

Convertendo dados de uma variável:*

# Convertendo os dados da variável para maiúsculo, 
# a variável 'nome' tem como valor o nome 'joao'

linux:~/$ tr 'a-z' 'A-Z' <<< "$nome"
JOAO

Convertendo dados de uma String:

# Convertendo 'user' para maiúsculo.

linux:~/$ tr 'a-z' 'A-Z' <<< "user"
USER

Perceba que esse comando converte um dado passado diretamente a ele, enquanto o modelo acima pega o dado da variável, então, pode-se usar as duas formas, isso não se limita a apenas o uso com tr, e sim, a qualquer coisa que precise ter o redirecionamento de entrada trocado.

Resumo:

<   - Redireciona a entrada padrão para um arquivo;
<< - Redireciona a entrada padrão para uma entrada de fluxo;
<<< - Redireciona a entrada padrão para Strings;


XARGS


Construir e executar linhas de comando a partir da entrada padrão.

O comando xargs pega a saída de um comando e executa outro comando, executando linha por linha, é muito usado com o comando find, mas pode ser usado com qualquer comando.


Opções:

-0, --null 
Os itens são separados por nulo, sem espaço em branco; desabilita processamento de aspas e barra invertida e de EOF lógicos.

-a, --arg-file=ARQUIVO
Lê argumentos de ARQUIVO, não da entrada padrão.

-p, --interactive
Solicita confirmação antes de executar os comandos.

-n, --max-args=MÁX-ARGS
Usa no máximo MÁX-ARGS argumentos por linha de comando.

Exemplos:

# Procurando por arquivos de texto no /tmp, redirecionando a saída de erro
# para /dev/null e jogando os arquivos encontrados para o XARGS,
# ele vai rodar um comando 'echo "Achados: "' para cada linha que o find
# passar para ele:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null | xargs -n1 echo "Achado: "
Achado: /tmp/join.txt
Achado: /tmp/teste_arquivo1_gz.txt
Achado: /tmp/kernel-versão.txt
Achado: /tmp/copy_join.txt
Achado: /tmp/oi.txt

## A opção '-n 1' do xargs só permite 1 argumento por linha.

# Exemplo sem a opção de '-n 1' argumento por linha:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null | xargs echo "Achado: "
Achado: /tmp/join.txt /tmp/teste_arquivo1_gz.txt /tmp/kernel-versão.txt /tmp/copy_join.txt /tmp/oi.txt

## Todos os arquivos encontrados são exibidos na mesma linha (como vários
## argumentos).

A opção -p vai perguntar se você deseja executar a linha em que for exibida.

# Uso da opção -p:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null | xargs -n1 -p echo "Achado: "
echo 'Achado: ' /tmp/join.txt ?...y
Achado: /tmp/join.txt
echo 'Achado: ' /tmp/teste_arquivo1_gz.txt ?...n
echo 'Achado: ' /tmp/kernel-versão.txt ?...

# Se você digitar 'y' ele executa, se digitar 'n' ele nao irá executar.

A opção -a muda a entrada padrão para um arquivo e não vinda de um comando:

# Jogando os arquivos encontrados no arquivo 'arquis':
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null > arquis

# Fazendo o xargs lêr do arquivos 'arquis':
linux:/tmp\$ xargs -n1 -a arquis echo "Achado: "
Achado: /tmp/join.txt
Achado: /tmp/teste_arquivo1_gz.txt
Achado: /tmp/kernel-versão.txt
Achado: /tmp/copy_join.txt
Achado: /tmp/oi.txt

# Fazendo o xargs apagar todos os arquivos de texto
# encontrado pelo comando find:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null | xargs -n1 -p rm
rm /tmp/join.txt ?...y
rm /tmp/teste_arquivo1_gz.txt ?...y
rm /tmp/kernel-versão.txt ?...y
rm /tmp/copy_join.txt ?...y
rm /tmp/oi.txt ?...y

# Agora rode o comando find de novo para ver se ele encontra,
# caso não retorne nada, é porque não encontrou:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null


Usando a opção -0 ou --null:

linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null
/tmp/Kenerl 2.0 linux.txt
/tmp/download_isos.txt
/tmp/linux mint 20.txt

# Criei 3 arquivos, 2 com espaçõs no nome e 1 sem espaço no nome.
# o find mostra eles como devem ser.

# Olha o que acontece quando rodo com o xargs:
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null | xargs -n1 echo "achado: "
achado: /tmp/Kenerl
achado: 2.0
achado: linux.txt
achado: /tmp/download_isos.txt
achado: /tmp/linux
achado: mint
achado: 20.txt

## O xargs acha que cada espaço é um nome diferente.
# Para que isso não aconteça, usamos a opção -0, para que ele
# só interprete caractere nulo (^@), quem faz essa troca para
# caractere nulo é o find, usando a opção '-print0':
linux:/tmp\$ find /tmp/ -type f -name "*.txt" 2> /dev/null -print0 | xargs -0 -n1 echo "Achados: "
Achados: /tmp/join.txt
Achados: /tmp/Kenerl 2.0 linux.txt
Achados: /tmp/download_isos.txt
Achados: /tmp/linux mint 20.txt

Nem todos os comando tem a opção -print0, nesse caso vou usar um exemplo do comando fdupes, ele encontra arquivos duplicados no sistema e não possui essa opção.

Outro exemplo do xargs, usando o fdupes:

### Opções do comando FDUPES:
# -r = Recursivo
# -n = Exclui arquivo vazios da busca.
# -A = Exclui arquivos ocultos da busca.
linux:/tmp\$ fdupes -rnA /tmp/ 2>/dev/null | sed '/^$/d; s/^/"/; s/$/"/'
"/tmp/ola.txt"
"/tmp/join.txt"
"/tmp/teste arquivo gz"
"/tmp/arquivo_gz"
"/tmp/arq.gz"
"/tmp/pasta1/arq.gz"

# Nesse caso eu encapsulei a saída para delimitar os nomes dos arquivos.
# O xargs vai interpretar cada nome como deveria.

Para relembrar a explicação acima, consulte a seção quoting.

  • Explicação
    • sed Editor de fluxo para filtrar e transformar textos;
    • /^$/d Apagar as linhas em branco;
    • s/^/"/ Adiciona aspas (") no começo da linha;
    • s/$/"/ Adiciona aspas (") no fim da linha;

Apagando os arquivos duplicados:

# Agora vamos apagar os arquvios duplicados:
linux:/tmp\$ fdupes -rnA /tmp/ 2>/dev/null | sed '/^$/d; s/^/"/; s/$/"/' | xargs -n1 -p rm
rm /tmp/ola.txt ?...y
rm /tmp/join.txt ?...y
rm '/tmp/teste arquivo gz' ?...y
rm /tmp/arquivo_gz ?...y
rm /tmp/arq.gz ?...y
rm /tmp/pasta1/arq.gz ?...y


Saída Padrão (stdout - 1)


Uma saída padrão, conhecido como stdout, é tudo o que é enviado para tela (monitor), o monitor é o padrão da saída, para redirecionar a saída usamos o símbolo de maior >.

# Mudando a saída padrão do echo
linux:~/$ echo "oi" > teste.txt

Dessa forma, ao invés do echo manda para saída padrão (monitor), ele envia essa saída para um arquivo. Perceba que o uso de apenas um símbolo de maior faz a sobrescrita do arquivo, e caso ele já exista e possua dados, esses dados serão sobrescrevidos.


Append

Muitas vezes, ao mudar a saída padrão, não queremos sobrescrever o conteúdo do arquivo final, para isso acrescentamos o novo conteúdo, há um já existente. O arquivo teste.txt já possui a palavra 'oi', agora vamos adicionar dados a esse arquivo, sem ter que apagar o que já existe, para isso usamos dois símbolos de maior >>.

# Adicionando dados:
linux:~/$ echo "Tudo bem?" >> teste.txt

# listando o conteúdo de teste.txt:
linux:~/$ cat teste.txt
oi
Tudo Bem?

Perceba que o que já existia no arquivo não foi apagado.



PIPE


O pipe é considerado um redirecionador de saída, mas eu gosto de julgá-lo como um redirecionador misto, isso porque, o pipe pega a saída de um comando e redireciona como entrada de outro comando, ele não manipula apenas a saída mas a entrada também.

# Redirecionando a entrada e saída padrão:
linux:~/$ echo "oi" | tr 'a-z' 'A-Z'
OI

# Redirecionando a entrada e saída padrão:
linux:~/$ echo "oi, tudo bem com voce" | egrep "o"
oi, tudo bem com voce

No segundo exemplo, quando feito o teste no terminal, você verá que as letras o ficarão vermelhas.



TEE


Lê da entrada padrão e grava na saída padrão e no arquivo escolhido.

O comando tee vai pegar a saída de um comando, exibir essa saída na tela e então vai ao mesmo tempo gravar ela em um arquivo.


Opções:

-a, --append
Anexa os arquivos (incrementa).

Exemplos:

# Exibindo a versão do kernel e escrevendo num arquivo: 
linux:/tmp\$ echo "Kernel: $(uname -r)" | tee kernel-versão.txt
Kernel: 5.4.0-42-generic

# Exiba o conteúdo de kernel-versão.txt:
linux:/tmp\$ cat kernel-versão.txt
Kernel: 5.4.0-42-generic

Vale lembrar que dessa forma o comando tee está apagando e escrevendo de novo no arquivo, para que ele possa incrementar, usaremos a opção -a:

# Exibindo o nome da máquina e escrevendo num arquivo: 
linux:/tmp\$ echo "Hostname: $(hostname)" | tee -a kernel-versão.txt
Hostname: chiredean

# Exiba o conteúdo de kernel-versão.txt:
linux:/tmp\$ cat kernel-versão.txt
Kernel: 5.4.0-42-generic
Hostname: chiredean
# Perceba que o conteúdo sofreu um incremento (não foi substituido).


Erro Padrão (stderr - 2)


O erro padrão, ou saída padrão de erros é a saída padrão, ou seja, é o monitor, a menos que isso seja alterado.

Diferente do stdin e stdout que possuem redirecionados sendo so símbolos de menor < e maior >, stderr usa o mesmo símbolo do redirecionamento de saída (stdout >), seguido do número que você deseja (0 = sdtin, 1 = sdtout e 2 = sdterr).


OBS.: Como sdtin e stdout possuem símbolos próprios (menor < e maior >), podemos omitir o número 0 e 1, pois cada símbolo já tem uma definição.

# Jogando a saída de erro para o arquivo erros_sdterr:
linux:/tmp\$ ls arq.gz 2> erros_stderr

## Como o arquivo arq.gz não existe, o comando ls vai retornar
## um erro na saída padrão:
linux:/tmp\$ ls arq.gz
ls: não foi possível acessar 'arq.gz': Arquivo ou diretório inexistente
## O '2>' informa que vamos redirecionar a saída de erro padrão
## para o arquivo erros_stderr.

# Podemos usar o modo append, usando o símbolo de maior maior '>>',
# assim vamos ter um incremento no arquivo de erros e ele não será
# substituido:
linux:/tmp\$ ls arq1.gz 2>> erros_stderr

# Exibindo o conteúdo de erros_stderr:
linux:/tmp\$ cat erros_stderr
ls: não foi possível acessar 'arq.gz': Arquivo ou diretório inexistente
ls: não foi possível acessar 'arq1.gz': Arquivo ou diretório inexistente


Meios de redirecionar


Como já foi explicado boa parte de como funciona o sistema de redirecionamento, vou passar alguns exemplos que podem ser úteis:


Exemplos:

## Redirecionando a entrada padrão para um arquivo:
linux:/tmp\$ tr "a-z" "A-Z" < arquis
/TMP/JOIN.TXT
/TMP/TESTE_ARQUIVO1_GZ.TXT
/TMP/KERNEL-VERSãO.TXT
/TMP/COPY_JOIN.TXT
/TMP/OI.TXT

## Redirecionando a entrada padrão para uma string:
linux:/tmp\$ tr "a-z" "A-Z" <<< "kernel"
KERNEL

## Redirecionando a entrada padrão para uma variável:
linux:/tmp\$ tr "a-z" "A-Z" <<< "$USER"
LINUX

## Redirecionando a entrada padrão para um fluxo de dados:
linux:/tmp\$ cat << "FIM"
> $USER
> $SHELL
> FIM
$USER
$SHELL

## Redirecionando a saída padrão para um arquivo:
linux:/tmp\$ echo "kernel" > arq_sdtout

## Redirecionando a saída padrão para um arquivo com append:
linux:/tmp\$ echo "kernel 2" >> arq_sdtout

## Redirecionando a saída de erro padrão para um arquivo:
linux:/tmp\$ ls kernel 2> arq_stderr

## Redirecionando a saída de erro padrão para um arquivo com append:
linux:/tmp\$ ls kernel2 2>> arq_stderr

## Conecta a saída de erro com a saída padrão.
# Trancar stderr ao sdtout:
linux:/tmp\$ ls -lh arq_stderr arq2_stderr > arq_saida 2>&1

# Exibindo o conteúdo de arq_saida:
linux:/tmp\$ cat arq_saida
ls: não foi possível acessar 'arq2_stderr': Arquivo ou diretório inexistente
-rw-rw-r-- 1 linux linux 76 jul 23 14:05 arq_stderr

# O '2>&1' informa que a saída de erro será a mesma de stdout,
# no caso, vai para o mesmo arquivo.

# O '>&2' conecta a saída padrão na saída de erro:
linux:/tmp\$ ls -lh arq_stderr arq2_stderr 2> arq_saida2 >&2

# Exibindo o conteúdo de arq_saida2:
linux:/tmp\$ cat arq_saida2
ls: não foi possível acessar 'arq2_stderr': Arquivo ou diretório inexistente
-rw-rw-r-- 1 linux linux 76 jul 23 14:05 arq_stderr

# O símbolo '2>&-' fecha a saída de erro (não existe saída de erro).
linux:/tmp\$ ls -lh arq2_stderr 2>&-
## O erro não será exibido, é o mesmo que '2> /dev/null'.