====== UNIX: Uso avançado do Shell ====== Neste texto são apresentados os principais conceitos associados a entradas e saídas padrão, como redirecionamentos e ''pipes''. Também são vistos uma série de programas simples (os filtros), que podem ser muito úteis quando associados através de pipes. ===== Entradas e saídas padrão ===== A maioria dos comandos UNIX pode comunicar-se com o sistema através de descritores de arquivos especiais conhecidos como entradas e saídas padrão. Eles são: * Entrada padrão (''stdin'' - //standard input//): onde o comando vai ler seus dados de entrada. No Bash, esse arquivo é referenciado pelo descritor 0. * Saída padrão (''stdout'' - //standard output//): onde o comando vai escrever seus dados de saída. No Bash, esse arquivo é referenciado pelo descritor 1. * Saída de erro (''stderr'' - //standard error//): onde o comando vai enviar mensagens de erro. No Bash, esse arquivo é referenciado pelo descritor 2. {{ processo.png }} Quando um comando é lançado sem indicar seu arquivo de trabalho, ele busca seus dados da entrada padrão. Por default, o shell onde o comando foi lançado associa o processo ao seu terminal, ou seja: a entrada padrão do processo é associada ao teclado e as saídas padrão e de erros à tela da sessão corrente. {{ desv-terminal.png }} Um exemplo de uso da entrada e saída padrão é o comando ''rev'', que escreve em sua saída padrão as linhas de texto lidas em sua entrada padrão, invertendo-as: $ rev vamos fazer um teste etset mu rezaf somav * temos que achar um palindromo omordnilap mu rahca euq somet * opoetaamaateopo opoetaamaateopo * ^D $ No exemplo, as linhas marcadas com ''*'' indicam as saídas geradas pelo comando rev. O caractere ''^D'' (//Control-D//) no final indica o final da entrada padrão (ou seja, o fim de arquivo). Ao receber esse caractere, o comando ''rev'' encerra sua execução, pois chegou ao final de seu arquivo de entrada (que neste caso é o teclado). Outro exemplo de uso da entrada e saída padrão é comando ''sort'': $ sort joao maria antonio carlos manoel ^D antonio carlos joao manoel maria $ Normalmente o shell direciona a entrada padrão para o teclado e a saída padrão para a tela da sessão do usuário, mas ele pode ser instruído para redirecioná-las para arquivos ou mesmo para outros programas, como será visto na seqüência. ===== Redireção para arquivos ===== O shell pode redirecionar as entrada e saídas padrão de comandos para arquivos normais no disco, usando operadores de redireção, como mostra a figura abaixo: {{ desv-arquivos.png }} A sintaxe de redireção é específica para cada shell, isto é, não é a mesma entre o C-Shell e o Bourne Shell; aqui será apresentada a sintaxe do shell Bash. Os principais operadores de redireção para arquivos são: * Saída em arquivo: a saída padrão (//stdout//) do comando é desviada para um arquivo usando o operador ''>'': $ ls > listagem.txt * Entrada de arquivo: a entrada padrão (//stdin//) pode ser obtida a partir de um arquivo usando o operador ''<'': $ rev < listagem.txt * Uso combinado: os dois operadores podem ser usados simultaneamente. $ rev < listagem.txt > listrev.txt * Concatenação: a saída padrão pode ser concatenada a um arquivo existente usando-se o operador ''>>'', como mostra o exemplo: $ ls /etc >> listagem.txt * Saída de erros: a saída de erros (//stderr//) também pode ser redirecionada, através do operador ''2>'' (que faz referência ao descritor 2): $ ls /xpto > teste.txt ls: /xpto: No such file or directory $ ll /xpto 2> erro.txt $ cat error.txt ls: /xpto: No such file or directory * As saídas padrão e de erro podem ser redirecionadas de forma independente: $ ll /xpto /etc/passwd > acerto.txt 2> erro.txt $ cat error.txt ls: /xpto: No such file or directory $ cat acerto.txt -rw-r--r-- 1 root root 2136 Mai 14 17:02 /etc/passwd * Além disso, a saída de erro pode ser sobreposta à saída padrão: $ ll /xpto /etc/passwd > acerto.txt 2>&1 $ cat acerto.txt -rw-r--r-- 1 root root 2136 Mai 14 17:02 /etc/passwd ls: /xpto: No such file or directory * Forçar um desvio: Caso a saída seja redirecionada para um arquivo já existente, o shell recusa a operação indicando o erro (somente se a variável ''noclobber'' estiver setada através do comando ''set -C''). Essa operação pode ser forçada através do operador ''!'': $ ls > teste.txt teste.txt: File exists. $ ls >! teste.txt $ ls >> novo.txt novo.txt: No such file or directory $ ls >>! novo.txt ===== Redireção usando pipes ===== O shell permite a construção de comandos complexos através da combinação de vários comandos simples. O operador ''|'', conhecido como //pipe//, ou tubo, permite conectar a saída padrão de um comando à entrada padrão de outro. Com isso, um mesmo fluxo de dados pode ser tratado por diversos comandos consecutivamente, como mostra a figura: {{ desv-pipes.png }} É importante ressaltar que os comandos conectados são lançados simultaneamente pelo shell e executam ao mesmo tempo. O shell controla a execução de cada um para que não haja acumulo de dados entre os comandos (a cada pipe é associado um buffer de tamanho limitado). A sintaxe usada para redireção é simples. Eis alguns exemplos: $ ls -l /etc | more $ ls -l /tmp | sort | more $ ls -l /usr/bin | cut -c31-40 | sort | more O mecanismo de redireção de entrada/saída é genérico, ou seja, funciona para qualquer programa que use as entradas e saídas padrão, em qualquer linguagem de programação. ===== Filtros ===== Um filtro é basicamente um programa que lê dados da entrada padrão, realiza algum processamento e escreve os dados resultantes na saída padrão. Um exemplo simples de filtro seria: #include #include // lê caracteres em stdin e escreve em stdout, convertendo // vogais minúsculas em '*' e vogais maiúsculas em '#'. int main () { char c ; c = getchar () ; while (c != EOF) { switch (c) { case 'a': case 'e': case 'i': case 'o': case 'u': c = '*' ; break ; case 'A': case 'E': case 'I': case 'O': case 'U': c = '#' ; break ; } putchar (c) ; c = getchar () ; } return (0); } Para compilar esse filtro basta digitar: ''gcc -o filtro filtro.c''. Uma vez compilado, o arquivo executável ''filtro'' pode ser usado nas linha de comando UNIX, como qualquer outro filtro. Existe um grande número de comandos UNIX bastante simples, cujo uso direto é pouco útil, mas que podem ser de grande valia quando associados entre si através de pipes. Esses comandos são chamados filtros, porque funcionam como filtros para o fluxo de dados. Eis alguns filtros de uso corrente: * ''cat'' : concatena diversos arquivos na saída padrão * ''tac'' : idem, mas inverte a ordem das linhas * ''more'' : permite a paginação do fluxo de dados * ''tr'' : troca de caracteres entre dois conjuntos * ''head'' : seleciona as ''n'' linhas iniciais do fluxo de dados * ''tail'' : seleciona as ''n'' linhas finais do fluxo de dados * ''wc'' : conta o número de linhas, palavras e bytes do fluxo * ''sort'' : ordena as linhas segundo critérios ajustáveis * ''uniq'' : remove linhas repetidas, deixando uma só linha * ''sed'' : para operações complexas de strings (trocas, etc) * ''grep'' : seleciona linhas contendo uma determinada expressão * ''cut'' : seleciona colunas do fluxo de entrada * ''rev'' : reverte a ordem dos caracteres de cada linha do fluxo de entrada * ''tee'' : duplica o fluxo de entrada (para um arquivo e para a saída standard) * ... : qualquer programa que leia dados de stdin e escreva sua saída em stdout pode ser usado como filtro Para conhecer melhor cada um dos comandos acima, basta consultar suas respectivas páginas de manual. ===== Exercícios ===== - Usando comandos e //pipes//, determine o número de linhas da página de manual do shell Bash. - Determine quanto arquivos normais (não diretórios nem links) existem em ''/usr''. - Monte uma linha de comandos usando //pipes// para identificar todos os usuários proprietários de arquivos ou diretórios a partir de'' /tmp'', colocando o resultado no arquivo ''users-tmp.txt''. Siga os seguintes passos: * Use o comando ''find ''para listar os proprietários de todos os arquivos dentro de ''/tmp'' (dica: use a opção ''-printf'' do comando ''find''). * Ordene a listagem obtida, usando o comando ''sort'' * Remova as linhas repetidas, usando o comando ''uniq'' * Direcione a saída para o arquivo indicado ''users-tmp.txt''. - Use o comando cut na saída de um comando ls -l para mostrar apenas as permissões dos arquivos no diretório /etc. Depois use sort e uniq para mostrar quantas permissões diferentes existem naquele diretório. - Quantos arquivos invisíveis (iniciados com .) há na sua área HOME? - Quantos diretórios há na sua área HOME? - Liste todos os atributos de todos os arquivos de um diretório e utilize o cut para mostrar apenas suas permissões e seu nome. - Liste todos os arquivos e seus atributos (somente os arquivos, diretórios não devem aparecer) do diretório /etc, ordenando a saída por data do arquivo, e guarde a saída no arquivo teste.txt na sua área. - Mostre apenas o vigésimo arquivo do diretório /etc - Mostre apenas os arquivos e diretórios para os quais você tem permissão de execução na sua área HOME. - Acesse o servidor ssh.inf.ufpr.br. Utilize o comando finger para mostrar o Login de todos usuários cujo primeiro nome seja Daniel. - Execute os comandos a seguir como usuário normal. Determine o que é stdin, stdout e stderr para cada comando (o conteúdo de cada fluxo para cada comando): - ''cat nonexistentfile'' - ''file /sbin/ifconfig'' - ''grep root /etc/passwd /etc/nofiles > grepresults'' - ''/etc/init.d/sshd start > /var/tmp/output'' - ''/etc/init.d/crond start > /var/tmp/output 2>&1'' - Confira seu resultado repetindo os comandos e atribuindo //stdout// para ''$HOME/saida.txt'' e //stderr// para ''$HOME/erro.txt''. - Observe as seguintes sequências de comandos e responda às perguntas: $ mkdir vazio $ cd vazio $ cp a b cp: cannot stat 'a': No such file or directory $ cp a b >a - Por que não há mensagem de erro após o segundo comando ''cp''? Qual o conteúdo do arquivo ''a''? $ date >a $ cat a Wed Feb 8 03:01:21 EST 2012 $ cp a b $ cat b Wed Feb 8 03:01:21 EST 2012 $ cp a b >a $ cat b - Por que o arquivo ''b'' está vazio? O que há no arquivo ''a''?