2. Introdução ao Unix
Tutorial: Introdução ao Unix
IMPORTANTE: Este é um tutorial do tipo "faça junto" (follow along). A nossa recomendação é que você mantenha a janela em que está lendo este tutorial lado-a-lado com um terminal e vá executando os comandos conforme eles forem apresentados. Esta é a melhor forma (e a menos entediante!) de aprender a usar uma nova ferramenta.
Exercícios
Ao longo deste tutorial, você encontrará diversos exercícios. Para cada exercício, você deverá criar um arquivo com o nome {NÚMERO_DO_EXERCÍCIO}.out
(por exemplo, 2.1.out
) e colocar a resposta neste arquivo.
Ao final do tutorial, você deverá criar um arquivo zip com estes arquivos, com o nome lab1-atv2-XXXXXXX
(onde XXXXXXX
é o seu número USP) e enviá-los pelo Sharif Judge.
Sugestão para facilitar: crie uma pasta na Área de Trabalho com o nome lab1-atv2-XXXXXXX
, abra o editor de texto Atom (ícone verde com o desenho de um átomo na barra à esquerda), abra a pasta que você criou (File > Open Folder
e selecione a pasta) e vá criando os arquivos de resposta (.out) conforme os exercícios forem aparecendo.
Como criar o arquivo zip: no final do tutorial, vá até diretório lab1-atv2-XXXXXXX
com o File Explorer (ícone com o desenho de um arquivo na barra à esquerda), selecione todos os arquivos .out, clique com o botão direito, escolha Compress...
, digite o nome do arquivo, mude a extensão para .zip
e clique em Create
.
O shell
A figura abaixo mostra um programa em execução: o shell.
Talvez você já tenha ouvido falar do shell por algum outro nome, como: interpretador de comandos, terminal, linha de comando, console ou TTY. Além desses, o termo "cmd" também é comum para quem usa Windows.
A realidade é que cada um destes termos tem um significado diferente, mas não iremos entrar em todos os detalhes agora.
O mínimo que você precisa saber é o seguinte: esta janela está executando um programa genericamente chamado de shell. O shell é um programa que, ao invés de possuir uma interface gráfica (isto é, com botões, menus, janelas e que responde aos cliques do mouse), utiliza uma interface de texto.
Quando o shell é iniciado, ele lê diversos arquivos de configuração para carregar um ambiente de execução para o seu usuário e, em seguida, exibe um prompt (também chamado de prompt de comando), que é algo parecido com isso:
$
Este prompt indica que o shell está pronto para receber comandos ou instruções sobre o que fazer em seguida.
O prompt não precisa ser apenas o símbolo $
. A maioria dos shells permite que você altere o prompt padrão para exibir outras informações úteis ou para deixar o seu ambiente mais agradável visualmente. Alguns exemplos de prompt personalizados são mostrados abaixo:
# Prompt de um usuário normal
$
# Prompt de um usuário administrador (também chamado de super-usuário,
# superuser ou root)
#
# Prompt com informações adicionais: nome do usuário logado (debora) e
# diretório atual de trabalho (~/Desktop)
debora: ~/Desktop $
# Prompt parecido com o anterior, mas adicionando o horário, o nome do
# computador (hostname) e caracteres de formatação
[ 14:24:12 debora@dell: ~/Desktop ] $
Para saber mais sobre como modificar o seu prompt, consulte a seção Recursos Adicionais.
Quantos shells!
Repare que, acima, mencionamos algo sobre "a maioria dos shells". Pois é, não existe um único shell. No mundo Unix-like (sistemas operacionais do tipo Unix), os mais comuns são o sh
(Bourne shell), bash
(Bourne again shell), zsh
(Z shell), ksh
(Korn shell) e o fish
(friendly interactive shell). No Windows, os mais comuns são o cmd.exe
e o PowerShell
.
Todos os shells de Unix possuem funcionalidades similares (mas não necessariamente idênticas). No entanto, a sintaxe de cada pode ser muito diferente da dos demais. Neste curso, vamos falar mais sobre o bash
, que é o shell utilizado por padrão no Linux.
Unix: comandos básicos
Usando apenas o shell, você consegue fazer tudo que quiser no seu computador, como:
- criar arquivos e diretórios (pastas);
- manipular arquivos e diretórios (mover/apagar/renomear/editar/visualizar/criar atalhos);
- instalar/remover/configurar programas;
- criar/remover/editar usuários;
- ler/enviar emails;
- baixar arquivos da Internet;
- acessar outros computadores remotamente; e
- iniciar qualquer aplicação que possua uma interface gráfica (Chrome, Firefox, LibreOffice, leitores de PDF, players de vídeo e música, jogos, etc).
Nesta seção, você irá aprender os comandos básicos para fazer algumas destas coisas. A maioria dos comandos que serão apresentados existe em qualquer sistema operacional do tipo Unix, o que inclui, por exemplo, qualquer distribuição de Linux (como o Ubuntu, que vamos usar ao longo da disciplina), CentOS, RHEL, FreeBSD, OpenBSD e Mac OS X.
Comandos de navegação: cd
, pwd
, caminhos absolutos e caminhos relativos
pwd
(print working directory): imprime o caminho do diretório em que você se encontra.
# Imprime o diretório atual.
pwd
Exercício 2.1: execute o comando pwd
no seu terminal e responda: qual o seu diretório atual? (coloque a saída do comando no arquivo de solução, por exemplo: /home/ubuntu/foo
no arquivo 2.1.out
).
cd
(change directory): usado para ir até diretórios. Quando executado sem argumentos, este comando leva você até o diretório do seu usuário (sua home).
# Vai até o diretório do usuário.
cd
Exercício 2.2: execute o comando cd
e, em seguida, execute novamente pwd
. Qual é o seu diretório agora?
Observação: O diretório home do usuário é comumente referenciado, no shell, por $HOME
ou ~
, como mostrado abaixo.
# Equivalente a executar apenas "cd".
cd $HOME
# Equivalente a executar apenas "cd".
cd ~
Você também pode passar, como parâmetro para o cd
, um caminho absoluto. Caminhos absolutos são aqueles que começam com /
.
# Vai para o diretório /var/log
cd /var/log
# Vai para o diretório /home/ubuntu/Desktop
cd /home/ubuntu/Desktop
O diretório-raiz do sistema de arquivos no Unix, isto é, o diretório de nível mais alto, é o /
, o que significa que o comando abaixo também é valido:
# Vai para o diretório-raiz do sistema de arquivos.
cd /
Outra opção ainda é especificar um caminho relativo. Caminhos relativos são concatenados com o seu diretório atual para formar o caminho absoluto final que será considerado pelo comando.
# Vai para /home/ubuntu (caminho absoluto)
cd /home/ubuntu
# Vai para Desktop (caminho relativo). Como o diretório atual era
# /home/ubuntu (por causa do comando anterior), este comando irá levá-lo
# até o diretório /home/ubuntu/Desktop.
cd Desktop
Note que, até agora, estamos navegando sempre "para baixo" na árvore de diretórios, isto é, entrando em subdiretórios. E se quisermos navegar "para cima" nesta árvore, isto é, chegar em um diretório-pai a partir de um diretório-filho?
Uma possível resposta é: sempre usar caminhos absolutos. Por exemplo, suponha que o seu diretório atual é /var/www/html
. Para chegar em /var/www
(diretório-pai), você poderia simplesmente fazer cd /var/www
, sem problemas. Mas existe outra alternativa: o uso dos diretórios especiais.
Existem dois diretórios especiais, chamados ..
e .
, que permitem que você se refira ao diretório-pai do seu diretório atual e ao seu próprio diretório atual, respectivamente, sem precisar usar seus nomes reais. Veja os exemplos:
# Vai para o diretório-pai
cd ..
# Vai para o diretório atual (ou seja... continua onde está)
cd .
Usando estes diretórios especiais, você consegue construir caminhos para referenciar qualquer diretório ou arquivo do sistema.
# Atenção! Antes de continuar, tenha certeza de que você entende por que
# todos os comandos abaixo são equivalentes. Se tiver alguma dúvida,
# peça ajuda.
cd /home/ubuntu/Desktop
cd ~/Desktop
cd /home/ubuntu/Desktop/../Desktop
cd /home/ubuntu/Desktop/../../ubuntu/Desktop
cd ~/Desktop/../Desktop/.
cd ~/Desktop/../Desktop/./././.
cd ~ ; cd Desktop
Sidebar: Everything is a file
O Unix segue uma filosofia conhecida como "everything is a file" (tudo é um arquivo). Como você vai perceber, não existe nenhuma diferença entre um caminho que leva a um diretório (e.g., /home/ubuntu/Desktop
) ou a um arquivo (e.g., /home/ubuntu/Desktop/arquivo
). Na realidade, um diretório é um arquivo, apenas de um tipo especial.
Assim, como saber se /home/ubuntu/Desktop/foo
é um arquivo normal ou um diretório? Naturalmente, existem comandos para isso! Além disso, os programas que recebem caminhos como /home/ubuntu/Desktop/foo
sabem identificar qual é o tipo de arquivo e (na maioria das vezes) fazem a coisa certa.
Sidebar: Comandos aceitam parâmetros e opções
A maioria dos comandos do Unix aceita parâmetros, como é o caso do comando cd
visto acima. Além disso, muitos comandos aceitam opções modificadoras, também chamadas apenas de opções. Estas opções normalmente são usadas para a passagem de parâmetros adicionais (opcionais) e para alterar, de alguma forma, o comportamento do programa.
É muito comum (embora não seja uma regra) que as opções tenham um nome "longo", começando com --
, e um nome "curto" ou "abreviado", começando com -
Por exemplo, muitos comandos aceitam a opção --help
ou -h
para exibir um texto explicativo sobre o que o comando faz.
Além disso, opções também podem ter um valor associado. Por exemplo, um programa poderia receber o nome do arquivo de saída através de uma opção parecida com --output out.txt
ou --output=out.txt
. Aqui, o nome da opção é --output
e o valor (nome do arquivo de saída) é out.txt
, sendo que eles podem ser separado por um espaço ou por =
.
Comandos de manipulação de diretórios e arquivos: mkdir
, ls
, rm
, mv
, cp
mkdir
(make directory): cria diretórios.
# Cria um diretório chamado "foo". Como "foo" é um caminho relativo (pois
# não começa com "/"), o novo diretório será criado dentro do diretório
# atual.
mkdir foo
# Cria um diretório chamado "foo". Como o argumento é um caminho absoluto,
# o novo diretório será criado exatamente no local especificado,
# independente do diretório em que o comando for executado.
mkdir /home/ubuntu/foo
Exercício 2.3: crie um diretório chamado baz
dentro do diretório /tmp
usando o comando mkdir
. Agora, tente executar o comando novamente. Qual a mensagem de erro que aparece?
ls
(list): lista os arquivos de um diretório e, opcionalmente, exibe informações sobre estes arquivos.
# Lista os arquivos do diretório atual.
ls
# Com argumento: lista os arquivos de algum outro diretório (neste
# exemplo, /var/log).
ls /var/log
# A opção "-l" é usada para listar os arquivos no formato "longo",
# mostrando, para cada arquivo, informações como tamanho, data de
# modificação e usuário dono arquivo.
ls -l /
# A opção "-a" é usada para listar todos os arquivos, incluindo arquivos
# ocultos e especiais, como . e ..
ls -a
# Muitas vezes, opções também podem ser combinadas. Neste exemplo, o
# comando ls é chamado com as opções "-l" e "-a" de forma unificada.
ls -la
# A ordem das opções não importa. Assim, este comando é exatamente igual
# ao anterior.
ls -al
# O "*" pode ser usado como um wildcard (literalmente, coringa), para
# encontrar arquivos cujo nome obedece a um determinado padrão.
# Neste exemplo, o comando lista todos os arquivos do diretório atual
# que terminam com ".txt".
ls *.txt
# Lista todos os arquivos que começam com "b" no diretório /bin
ls /bin/b*
IMPORTANTE: a interpretação e expansão do wildcard ("*") é realizada pelo shell, antes de invocar o comando ls
. Por exemplo, se o diretório contiver os arquivos foo.txt
e bar.txt
, o comando ls
será invocado, pelo shell, com dois argumentos: um para cada arquivo. O ls
não tem nem ideia de que você usou um wildcard.
NOTA: No Unix, arquivos ocultos são aqueles cujo nome começa com .
, por exemplo: /var/log/.temp
.
Exercício 2.4: quais são os diretórios existentes na raiz do sistema de arquivos (/
)? Colocar, no arquivo de solução, o nome de cada diretório em uma linha.
Exercício 2.5: quais são os diretórios existentes na pasta "home" do seu usuário (~)? Colocar, no arquivo de solução, o nome de cada diretório em uma linha.
Exercício 2.6: quais são os arquivos ocultos que existem na pasta do seu usuário? Colocar, no arquivo de solução, o nome de cada arquivo oculto em uma linha.
cp
(copy): copia arquivos (lembre-se de que um diretório é um tipo de arquivo!).
# Cria uma cópia do arquivo "foo" com nome "bar".
cp foo bar
# Copia o arquivo "foo" para o diretório /tmp. Note que, como /tmp é um
# diretório, o arquivo será copiado para dentro do diretório, ao invés de
# tomar o lugar do diretório.
cp foo /tmp
# Copia o arquivo "foo" para o diretório /tmp, com o nome "bar".
cp foo /tmp/bar
# Copia todos os arquivos com extensão ".txt" para um diretório de backup.
# Note que a "/" no final do nome do diretório (chamada "trailing slash")
# é opcional.
cp *.txt /var/backup/
# Copia recursivamente. Usado para copiar diretórios ao invés de arquivos
# individuais.
cp -r diretorio_origem diretorio_destino
rm
(remove): remove arquivos.
# Remove um arquivo chamado "foo"
rm foo
# Remove recursivamente. Usado para remover diretórios.
rm -r bar
mv
(move): move arquivos. Também usado para renomear.
# Move arquivo foo do diretório dir1 para o diretório dir2.
# Novamente, a "/" no final é opcional, mas serve para deixar mais claro
# que o destino é um diretório.
mv dir1/foo dir2/
# Move arquivo foo para o mesmo diretório mas com outro nome, isto é,
# renomeia o arquivo.
mv dir1/foo dir1/baz
ln
(link): cria um link de um arquivo para outro. A forma mais comum de link é o tipo "simbólico", equivalente a um "atalho" no Windows.
# Cria um link "simbólico": foo é um atalho para baz.
ln -s baz foo
Comandos de manipulação de arquivos: touch
, cat
, head
, tail
.
touch
: atualiza a data de modificação de um arquivo. Cria o arquivo, caso ele não exista.
# Cria um arquivo com nome "foo" no diretório do usuário.
touch ~/foo
cat
(concatenate): concatena e imprime o conteúdo de arquivos.
# Imprime o conteúdo dos arquivos foo e bar, concatenados.
cat foo bar
head
: imprime as primeiras linhas de um arquivo.
# Imprime as primeiras linhas (10 por padrão) do arquivo foo
head foo
# A opção "-n" pode ser usada para indicar um número arbitrário de linhas.
head -n 20 foo
tail
: imprime as últimas linhas de um arquivo. Análogo ao comando head
.
Comandos de busca: grep
e find
find
: encontra arquivos. Aceita diversas opções para especificar os diretórios de busca, padrões de nomes, tipos de arquivos, etc.
# Encontra e imprime os nomes de todos os arquivos dentro do diretório
# atual e de seus subdiretórios, caso existam.
find .
# Encontra apenas arquivos com extensão ".txt"
find . -name "*.txt"
grep
(global regular expression print): procura conteúdos (palavras, frases, padrões) dentro de arquivos.
# Procura pelo termo "apple" dentro do arquivo fruits.txt
grep apple fruits.txt
# A opção "-i" pode ser usada para realizar uma busca no modo "case
# insensitive", isto é, considerando maiúsculas e minúsculas da mesma forma.
grep -i apple fruits.txt
Comandos administrativos: sudo
No Unix, existem usuários normais e super-usuários. Super-usuários têm permissão para executar qualquer ação no sistema, incluindo aquelas que podem causar danos irreparáveis (por exemplo, apagar arquivos essenciais).
Por motivos de segurança, as pessoas usam a linha de comando sempre como um usuário do tipo "normal", isto é, que possui permissões de acesso limitadas—por exemplo, o seu usuário não tem permissão para apagar um driver do sistema operacional, ou ler/editar arquivos que contém senhas de outros usuários. No entanto, se você quiser fazer estas coisas, é possível, desde que você utilize recursos como o comando sudo
.
Explicando de uma maneira bastante simplificada, o que acontece é: todos os diretórios e arquivos importantes para o funcionamento do sistema operacional são de propriedade do super-usuário do sistema (normalmente chamado de root).
Usuários normais (como o seu próprio usuário), no modo de operação do "dia-a-dia", não têm permissão para alterar arquivos de outros usuários normais, e muito menos do super-usuário do sistema.
No entanto, você pode solicitar permissão para executar ações como se fosse o super-usuário, o que é precisamente o que o comando sudo
faz. Qualquer linha de comando que começar com sudo
será executada como se o usuário que iniciou a ação fosse o root—portanto, você poderá alterar (quase) qualquer coisa, inclusive coisas que poderão destruir o sistema.
Por exemplo:
# Tenta criar um diretório chamado "foo" na raiz do sistema de arquivos.
# Para um usuário normal, este comando resultará em um erro ("Permission
# denied").
mkdir /foo
# Uma versão do comando que irá funcionar é adicionar "sudo" no início,
# como mostrado abaixo. Note que você precisará informar a sua senha para
# prosseguir.
sudo mkdir /foo
Exercício 2.7: todo usuário tem um home directory (por exemplo, como já vimos, o $HOME
do seu usuário é /home/ubuntu
). Qual é o home directory do usuário root
? Para descobrir, leia o arquivo /etc/passwd
e tente inferir a resposta observando a estrutura do arquivo (cada linha do arquivo se refere a um usuário do sistema). Coloque, no arquivo de solução, o caminho absoluto do diretório home do root.
Outros comandos: man
, whoami
, date
, time
, echo
.
man
(manual): exibe a documentação de um comando, com paginação, quando for o caso.
# Exibe a documentação do comando "ls".
man ls
whoami
(who am I?): imprime o nome do usuário.
# Imprime o seu nome de usuário
whoami
# Imprime o nome de usuário do super-usuário
sudo whoami
date
: imprime a data e horário atuais.
sleep
: suspende a execução pelo número de segundos especificado.
time
: cronometra o tempo de execução de um comando e imprime um resumo ao final.
# Cronometra o tempo para executar o comando que dorme por 1 segundo.
time sleep 1
echo
: imprime os argumentos recebidos. Estes podem ser constantes ou conter variáveis, e serão interpretados antes de serem impressos.
# Imprime o único argumento informado, que é uma string constante.
echo "Hello, my name is Bob."
# Imprime o único argumento informado, que é uma string com partes
# intepretadas.
echo "Hello! My username is: $(whoami)"
# Imprime os diversos argumentos. Note que, neste caso, o comando "echo"
# recebe 7 argumentos (um para cada termo separado por um espaço). Álém
# disso, o 7o. argumento não é "~", pois, assim como no caso do wildcard
# (visto acima), é de responsabilidade do shell interpretar o "~" e já
# repassar o valor final para o comando.
echo My home folder is located at: ~
Além de todos os comandos apresentados até aqui, existem muitos outros que não teremos tempo de discutir. A seção Recusos Adicionais contém alguns links que você pode usar como referência para descobrir outros comandos úteis e interessantes.
Concatenação de comandos: pipe (|
)
ATENÇÃO: esta seção apresenta uma explicação bastante simplificada a respeito da concatenação de comandos. Para uma explicação mais completa, precisaríamos falar a respeito de processos, de seus fluxos padrão (standard streams) e de redirecionamento de entrada e saída (I/O). Infelizmente, não teremos espaço suficiente para cobrir estes tópicos neste documento, mas a seção Recursos Adicionais fornece informações para quem tiver interesse em se aprofundar mais.
Como foi visto anteriormente, muitos comandos aceitam dados de entrada e produzem dados como saída. O pipe, representado pelo caractere |
, é uma funcionalidade do shell que permite conectar a saída de um comando à entrada do próximo comando e, desta forma, criar uma cadeia de comandos para alcançar um determinado resultado.
Por exemplo:
# Este comando imprime o nome de todos os arquivos e diretórios no
# diretório ~, incluindo os ocultos.
ls -a ~
# Se quisermos listar apenas os que começam com a letra "D", podemos
# conectar a saída deste comando ao `grep`, como mostrado abaixo.
# Não se preocupe com o "^" por enquanto; você o entenderá melhor
# quando falarmos sobre expressões regulares.
ls -a ~ | grep ^D
# Finalmente, se quisermos ordenar os resultados em ordem alfabética
# descrescente, podemos conectar a saída do `grep` ao comando `sort`,
# passando a opção "-r", que indica ordenação em ordem inversa:
ls -a ~ | grep ^D | sort -r
Shell scripts
O shell, além de ter a capacidade de interpretar comandos individuais, como fizemos até agora, possui uma sintaxe própria que permite que você escreva programas completos na sua linguagem, assim como você poderia escrever e executar um programa em Python, C, Java ou qualquer outra linguagem de programação.
Um programa escrito na linguagem de um shell é chamado de shell script. Um shell script é simplesmente um arquivo de texto que contém sequências de comandos, definições de variáveis, loops, if/else, etc.
Além disso, um shell script pode receber parâmetros quando é chamado e pode interagir com o usuário, lendo dados do teclado e imprimindo dados na tela.
Uma vez que cada shell (sh
, bash
, ksh
, etc) possui uma sintaxe diferente, é importante sempre identificar, na primeira linha de um shell script (conhecida como shebang), qual é o programa correto para executar o arquivo. Por exemplo:
#!/bin/bash
Esta linha indica que o restante do arquivo deverá ser interpretado pelo programa /bin/bash
. A ausência do shebang pode fazer com que um programa escrito para o bash
, por exemplo, seja interpretado pelo sh
, gerando resultados inesperados e/ou erros de execução.
Não iremos descrever toda a sintaxe do bash aqui, mas sugerimos que você consulte a seção de Recursos Adicionais como referência.