Tutorial Linux – Parte 1

Foto de um teclado de computador com retroiluminação branca

Tutorial Linux

Esse tutorial é dividido em várias partes e elas podem ser vistas na página Tutorial Linux.

Capítulo 1.UNIX

O UNIX tem uma história interessante que estaremos contando agora. É uma história de mais de 25 anos que foi iniciada nos laboratórios Bell da AT&T, nos EUA, como um passatempo de um dos jovens pesquisadores que trabalhavam lá então. Hoje o UNIX roda nos mais importantes servidores do mundo e faz parte do alicerce da estrutura sobre a qual a Internet funciona.

Nos idos de 1970 o M.I.T., a General Electric e o Bell Labs iniciaram o desenvolvimento do sistema operacional MULTICS (MULTiplexed Information and Computing Service) que trazia diversas inovações, como um sistema de arquivos hierárquico e controle de permissões por arquivo. Entretanto esse trabalho foi se mostrando um grande ‘elefante branco’ e isso fez com que o Bell Labs se retirasse do projeto obrigando um dos pesquisadores envolvidos no projeto, Ken Thompson, a procurar algo mais ‘útil’ para fazer.

Ele resolveu então iniciar o desenvolvimento de uma versão menos ambiciosa do MULTICS em linguagem assembly para um PDP-7 que estava esquecido num canto do laboratório. Quando Brian Kernighan viu o projeto em andamento resolveu batizá-lo de UNICS (UNIplexed Information and Computing Service) como uma brincadeira com o MULTICS. O nome foi posteriormente adaptado para UNIX.

O projeto começou a ficar tão bom que ganhou a participação de outro cientista, Dennis Ritchie. Nessa época eles portaram o UNIX para versões mais modernas dos PDPs. Como ele era todo desenvolvido em linguagem assembly era necessário reescrever grandes quantidades de código para cada versão nova do trabalho. Foi então que Brian Kernighan e Dennis Ritchie resolveram criar uma nova linguagem de programação baseada em B e BCPL que foi denominada C para que pudessem desenvolver o UNIX nela.

Nessa época a AT&T enfrentava um ação de monopólio na justiça que a impediu de vender software. Isso fez com que ela distribuisse livremente os códigos para as universidades americanas formando assim um ambiente de desenvolvimento de software semelhante ao que vemos hoje com o Linux. Nas universidades americanas, o UNIX ganhou muitos dos recursos que o tornaram popular, como uma pilha de rede TCP/IP e memória virtual paginada.

Essa divisão do desenvolvimento entre a AT&T e as universidades americanas gerou uma das grandes cisões do mundo UNIX. Cada sistema UNIX tem pequenas diferenças, mas eles estão agrupados em duas famílias maiores. São elas a família do System V , formada por sistemas derivados do código da AT&T, e a família BSD, formada pelos sistemas derivados do UNIX da Universidade da Califórnia em Berkeley (BSD significa Berkeley Software Distribution). Como exemplo de sistema que seguiu o estilo System V temos o HP-UX, e como exemplo de um sistema BSD temos o SunOS. Essa grande divisão foi quase fechada com o tempo, na medida em que os sistemas acabaram adquirindo os recursos uns dos outros, mas até hoje essa diferença se manifesta em coisas tão simples quanto os parâmetros que um comando aceita.

De qualquer forma, o norte do desenvolvimento do UNIX é a filosofia KISS (Keep It Simple, Stupid!). Se você for desenvolver uma ferramenta para editar textos, ela deve editar textos; deixe a função de planilha pra algum outro aplicativo. No UNIX, cada programa realiza uma tarefa, e realiza essa tarefa bem. Há maneiras bem simples de realizar a integração de diversos aplicativos pequenos e eficientes para realizar as operações que o usuário precisa. Há mais de 25 anos, com a filosofia KISS, sistemas UNIX têm servido bem a função de prover uma plataforma sólida e eficiente para os mais diversos problemas computacionais.

1.1. Estrutura

O UNIX é um sistema operacional multiusuário, multitarefa desde o seu projeto. Ele é organizado em camadas de abstração de hardware como ilustrado na figura abaixo:

Camadas dos Sistemas Unix
Camadas de um sistema Unix
O tema desse livro é o desenvolvimento em shell script, entretando é interessante analisar essa divisão para sabermos exatamente onde estaremos trabalhando.

1.1.1. Kernel

Um dos princípios básicos de um sistema operacional moderno é o de criar uma camada de abstração do hardware, possibilitando que ao desenvolvedor de aplicações não se preocupar com o funcionamento específico de cada um dos modelos de hardware disponíveis no mercado. A aplicação irá solicitar um recurso do sistema operacional e ele é quem irá instruir o hardware corretamente através dos drivers de dispositivos.

O kernel é o núcleo dos sistemas operacionais UNIX. Ele que é reponsável por gerenciar a memória, gerenciar os processos que estão rodando, acessar os discos, passar informações para os drivers de dispositivos, entre outras coisas. A quantidade de tarefas que o kernel de um sistema executa pode variar de sistema operacional para sistema operacional.

1.1.2. Nível de usuário

Nesse nível é onde os aplicativos do sistema rodam. Nessa camada não é permitido fazer acesso direto ao hardware e nenhuma operação sem a permissão do kernel. É nessa camada que é executado o interpretador de comandos do sistema operacional UNIX, o shell.

1.1.3. Shell

O shell é o interpretador de comandos que recebe instruções do usuário e as executa. É a primeira coisa que o usuário vê quando entra no sistema. Além disso, o shell é uma linguagem de programação completa, permitindo que o usuário automatize tarefas da mesma forma que as executaria na linha de comando.

1.2. Conceitos

Vamos agora conhecer as características que são típicas de um sistema UNIX.

1.2.1. Processos

Todas as aplicações que rodam sob um sistema UNIX terão um ou mais processos. Sempre que executamos um programa na linha de comando, o shell irá enviar uma solicitação para que o kernel do sistema execute esse programa e dê a ele uma identificação de processo (PID – Process IDentification). Esse programa automaticamente irá se transformar num processo ‘filho’ do shell que o executou.

Como o UNIX é um sistema multitarefa, ele permite que vários programas sejam executados ao mesmo tempo na máquina, utilizando uma técnica de scheduling para ‘dividir’ o tempo que a CPU vai utilizar para executar cada um dos programas carregados em memória. Os UNIX possuem mecanismos que permitem que você dê mais prioridade a determinados processos fazendo com que o scheduling dedique uma fatia maior de tempo para essa aplicação. O comando que controla esse recurso é o nice. Para listar os processos rodando no sistema, seus PID s e outras informações adicionais, rode ps -ef.

1.2.2. Usuários e Grupos

O UNIX é um sistema multiusuário. Para gerenciar essa característica do sistema foi criado um mecanismo de contas de login. Cada usuário normalmente possui uma conta para usar o sistema. Quando uma conta de usuário é criada, o sistema atribui um número de identificação a esse usuário (o UID). Além disso, os sistemas UNIX possuem uma conta denominada root que é usada para administração do ambiente. O UID do root é reservado e especial, sempre igual a zero.

Essas contas também são organizadas em grupos. Cada grupo recebe um número que o identifica (o GID). Podem existir grupos vazios, que não possuem nenhum usuário, e grupos com diversos usuários. Cada usuário deve pertencer a um grupo principal e pode participar de outros grupos secundários.

Os usuários dos sistemas UNIX normalmente estão cadastrados no arquivo /etc/passwd. A estrutura desse arquivo é a mostrada abaixo:

$ cat /etc/passwd
root:*:0:0:Administrator:/:/usr/bin/ksh
fulano:*:101:101:Fulano de Tal, RH, 555-1234:/home/fulano:/usr/bin/ksh
$ _

Os campos são separados por ’:’ e organizados da seguinte maneira:

  1. Login – nome da conta do usuário. Deve possuir no máximo 8 caracteres e não deve conter símbolos.
  2. Senha – Senha criptografada do usuário. Em sistemas mais modernos a senha é armazenada criptografada em um arquivo legível apenas pelo administrador do sistema.
  3. UID – Identificação do usuário. Note que o UID do usuário root é 0.
  4. GID – Identificação do grupo principal do usuário.
  5. Descrição – Dados do usuário.
  6. Home – Diretório que será o $HOME do usuário.
  7. Shell – Qual será o shell que o usuário irá usar. Não é necessário que seja um shell, se for especificado outro programa aqui, este será executado logo que o usuário efetuar o login e retornará à tela de solicitação de login imediatamente após o término da aplicação.

Os grupos do sistema estão relacionados no arquivo /etc/group. Esse arquivo tem o seguinte aspecto:

$ cat /etc/group
users::100:fulano, cicrano, beltrano
$ _

O arquivo acima diz que os usuários ’cicrano‘, ’fulano‘ e ’beltrano‘ pertencem ao grupo ’users‘ cujo GID é 100. Note que esses usuário tem o grupo ’users’ como grupo secundário, e não primário. O grupo primário de cada um dos usuários é especificado no arquivo /etc/passwd.

1.2.3. Diretórios, Arquivos e dispositivos

O sistema de arquivos do UNIX possui diretórios, arquivos e dispositivos. Cada um deles possui atributos especiais que informam quais as permissões de acesso ao arquivo, quais os usuários que são donos desses arquivos, tamanho, data de criação e nome do arquivo. Você pode visualizar essas informações com o comando ls -l que terá uma saída como abaixo:

$ ls -l
total 0
-rw-rw-rw-  1  nobody  nobody       0  Dec 5  12:27  arquivo
brw-r--r--  1  root    root    0,   0  Dec 5  12:28  device
drwxrwxrwx  2  nobody  nobody      35  Dec 5  12:27  diretorio
-rwxrwxr-x  1  nobody  nobody       0  Dec 5  12:29  script.sh
$ _

Como você pode observar o comando ls -l mostra o conteúdo do diretório e os atributos dos arquivos organizados em colunas. Na primeira coluna podemos ver o mapa de bits com as permissões dos arquivos. A primeira letra desse mapa de bits indica qual o tipo do arquivo, os outros caracteres são agrupados de três em três indicando as permissões conforme a tabela abaixo:

Tipo Dono Grupo Todos
- - - - - - - - - -
d r w x - - - - - -
- r w - r - - r - -
l r w x r w x r w x

Tabela 1.1: Mapa de bits de permissão.

O dono do arquivo e o grupo ao qual ele pertence estão nas colunas 3 e 4 respectivamente. Cada uma das letras acima informa um determinado tipo de permissão conforme mostra a tabela abaixo:

Permissão Octal Significado
0 - - - Nenhuma operação permitida.
1 - - x Permitido apenas execução.
2 - w - Permitido apenas gravação.
3 - w x Permitido gravação e execução.
4 r - - Permitido apenas leitura.
5 r - x Permitido leitura e execução.
6 r w - Permitido leitura e gravação.
7 r w x Permitido leitura, gravação e execução.

Tabela 1.2: Permissões.

Existem outras permissões interessantes cujas implementações variam de UNIX para UNIX, a tabela abaixo mostra algumas delas. Elas serão explicadas com mais detalhes com os comandos chmod e chown.

Atributo Descrição
s SUID/SGID bit.
t Sticky bit.

Tabela 1.3: Outras permissões.

Além das permissões existe o caractere que informa qual o tipo do arquivo. A tabela abaixo mostra cada uma delas:

Atributo Tipo
- Arquivo normal.
d Diretório.
l Link simbólico.
c Dispositivo de caracter.
b Dispositivo de bloco.
p Filas (named pipes).

Tabela 1.4: Tipos de arquivos.

Além de arquivos e diretórios, que são as estruturas mais básicas de um sistema de arquivos existem outros tipos especiais de arquivos aos quais vamos dar uma atenção especial:

Links

Os links simbólicos, ou soft links, são utilizados para apontar para arquivos localizados em outros subdiretórios. Se o link é removido acidentalmente, o arquivo para o qual ele apontava permanece intacto. Se o arquivo original é removido, o link simbólico irá continuar apontando para o mesmo lugar e será inválidado (muito conhecido como link quebrado).

Além dos soft links existem também os hard links que são arquivos normais que possuem o mesmo ponto de entrada dos arquivos originais. Os arquivos com hard links são identificados pelo número da segunda coluna do comando ls -l (contador de referência) que será diferente de 1.

$ ls -l
total 1
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  arquivo_original
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
-rw-rw-r--  3  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_2
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original
$ rm hardlink_2
$ ls -l
total 1
-rw-rw-r--  2  osvaldo  osvaldo   0  Dec 5  15:39  arquivo_original
-rw-rw-r--  2  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original

Notem que o contador de referência mostra quantos hard links apontam para o mesmo arquivo. Agora vamos remover o arquivo_original.

$ rm arquivo_original
$ ls -l
total 1
-rw-rw-r--  1  osvaldo  osvaldo   0  Dec 5  15:39  hardlink_1
lrwxrwxrwx  1  osvaldo  osvaldo  16  Dec 5  15:39  softlink_1 -> arquivo_original
$ _

Vejam que o hard link continua existindo e com o mesmo conteúdo do arquivo_original, entretanto o soft link continua apontando para o arquivo que foi removido, caracterizando um link quebrado.

Dispositivos

Os arquivos do tipo dispositivo são utilizados para fazer a comunicação com os drivers dos dispositivos físicos da máquina. Quando gravamos ou lemos informações desses arquivos estamos, na verdade, enviando e recebendo informações do driver do dispositivo associado a esse arquivo.

Normalmente, todos os dispositivos ficam no diretório /dev e só são usados diretamente pelo usuário root. Alguns dispositivos, porém, são usados rotineiramente por todos os usuários. O /dev/null, por exemplo, é um dispositivo de caracteres que simplesmente descarta qualquer coisa que seja enviada pra ele. Esse dispositivo é usado para, por exemplo, eliminar saída indesejada de algum comando.

Filas

As filas (conhecidas como named pipes) são arquivos que podem ser abertos para operações de escrita por um processo e para leitura por um ou vários processos. Tudo o que for escrito nesses arquivos pelo processo que o abriu para escrita estará disponível para leitura pelos outros processos.

As filas, como o nome em inglês diz, são funcionalmente equivalentes aos pipes, mas têm uma manifestação no sistema de arquivos que pode ser usada em outros programas (por isso, pipes nomeados).

1.2.4. Sinais

Sinais são uma forma simples de comunicação entre processos, com algumas convenções pré-estabelecidas.

Um sistema UNIX normalmente executa muitos processos ao mesmo tempo. Porém, essa execução não acontece sem interrupções; há eventos no sistema que influenciam o funcionamento deles, e deve haver uma forma de notificar esses processos. Por exemplo, quando o sistema vai ser desligado para manutenção, deve existir uma forma de avisar os processos que eles devem escrever qualquer informação que estejam manipulando para o disco, arrumar seu ambiente e parar de rodar para que a máquina possa ser desligada. É para esse tipo de tarefa que os sinais são usados.

Os sinais mais comuns são o HUP, o TERM, o INT e o KILL. O HUP é enviado para processos para indicar que o usuário que o estava executando saiu do sistema. Normalmente, o procedimento padrão nesse caso é gravar dados no disco e sair, mas a ação depende do que o programa achar apropriado. Se for um processo que deve continuar rodando, esse sinal será ignorado, ou alguma outra ação apropriada será performada. O TERM é um pedido de interrupção do processo, e a forma padrão de agir é o programa se desligar graciosamente. O INT é como o TERM, mas é enviado quando o usuário aperta CTRL+C para interromper a execução do processo. O KILL é uma ordem para matar o processo. Ao contrário do TERM, que é um pedido, o KILL elimina imediatamente o processo, não dando tempo sequer parar que ele grave informações essenciais em disco ou para qualquer outra tarefa.

Quase todos os sinais podem ser interceptados pelo processo, exceto os sinais KILL e o sinal STOP, que são essenciais para garantir a estabilidade do sistema em caso de mau uso por algum usuário. Alguns sinais são normalmente enviados pelo kernel, e não pelo usuário, como o FPE em caso de exceção de ponto flutuante (caso o processo execute uma divisão por zero ou outra operação matemática ilegal), o SEGV (em caso de falha de segmentação – o programa tentou ler uma área de memória que não pertence a ele). Outros sinais são enviados por processos de monitoramento. Por exemplo, o processo que monitora a quantidade de energia do no-break da máquina pode enviar o sinal PWR para todos os processos ao detectar que uma falta de energia está prestes a ocorrer. Há, ainda, dois sinais cujo funcionamento não é especificado nem por convenção: USR1 e USR2, que realizam funções não determinadas e que variam de programa para programa.

Uma lista completa dos sinais disponíveis pode ser encontrado na man page signal(5) (acessível com o comando man 5 signal). Lembre-se que, por motivos óbvios de segurança, os usuários só podem enviar sinais para seus próprios processos. Apenas o usuário root pode enviar sinais para processos de todos os usuários.

1.2.5. Pipes

Pipes são uma forma mais elaborada de comunicação entre processos. Cada processo, ao ser rodado, ganha automaticamente três file descriptors. Eles são stdin, stdout e stderr. Pipes fazem a ligação entre descritores de arquivo de diferentes programas.

Por exemplo, é possível especificar que a saída de um programa vai ser a entrada de outro. Essa operação é muito comum. O seguinte comando vai ler o arquivo dados e enviar seu conteúdo para o o comando grep:

$ cat dados | grep texto

Com o auxílio do shell, foi criado um pipe que une a saída do comando cat (stdout) com a entrada do comando grep (stdin). O descritor stderr é usado para mensagens de erro. Normalmente, a saída de erro vai para o terminal, assim como a saída padrão. Quando chegarmos no capítulo sobre shell, porém, vamos aprender como usar redireção para enviar os dados dessas duas saídas para lugares diferentes.

1.2.6. Códigos de erros

Todo processo, ao ser terminado, retorna um código de erro numérico entre 0 e 255. O código 0 indica, por convenção, execução com sucesso, e qualquer coisa diferente disso indica algum erro ou alguma situação não normal. O código de retorno dos comandos é descrito na seção Return Codes de suas respectivas man pages (que podem se acessadas com o comando man comando), e varia de acordo com a função dos programas. O código de retorno 1, por exemplo, normalmente indica uso incorreto do programa. O código 2 pode indicar que algum arquivo está faltando, etc. Também é razoavelmente comum o uso do código 255 para indicar situações catastróficas de erro.

Alguns sistemas mais antigos têm programas que retornam a quantidade de erros ocorridos. Isso é uma convenção antiga que não deve mais ser usada – o correto é retornar um valor fixo de erro e enviar dados sobre quais erros ocorreram para stderr. Essa convenção foi abolida porque, caso o programa tenha mais de 255 erros, o código poderá voltar para os valores iniciais. Um programa com 256 erros retornaria 0, o padrão de execução com sucesso.

1.2.7. Parâmetros

É comum, quando executamos determinados programas, que tenhamos que passar parâmetros para eles. Por convenção, os parâmetros no UNIX são precedidos do caractere ‘-‘. Programas GNU (que seguem as regras da Free Software Foundation) suportam também uma forma de passagem de parâmetros mais extensos, que são precedidos por ’–‘ como na mensagem apresentada abaixo:

-v, --verbose - Exibe informações detalhadas.
-x, --exchange - Troca os parâmetros.
-h, --help - Exibe esta tela de ajuda.

Existe também uma forma de forçar o UNIX a não mais interpretar os parâmetros desse tipo, para isso basta inserir o ’–‘ sem nenhuma opção a mais. Essa opção é útil quando temos que informar um nome de arquivo começado com ’-‘ como parâmetro de um comando:

$ ls -- -arquivo-
-arquivo-
$ _

Outra coisa importante que devemos explicar aqui é a utilização de globs para a passagem de nomes de arquivos como parâmetros. Na tabela abaixo você poderá ver os globs mais comuns:

Glob Descrição
? Qualquer caracter.
* Quaisquer caracteres.
[] Qualquer caractere da lista.
[!] Qualquer caractere exceto o da lista.
{} Qualquer ocorrência da lista (separada por vírgula).

Tabela 1.5: Globs.

Esses globs podem ser utilizados da seguinte maneira:

$ ls
abbb abcde bbbb cbbb hipermercado mercado supermercado
$ ls [bc]bbb
bbbb cbbb
$ ls {hiper,super,}mercado
hipermercado mercado supermercado
$ ls a???
abbb
$ ls a*
abbb abcde
$ ls [!a]*
bbbb cbbb hipermercado mercado supermercado
$ _

Uma coisa que precisa ser explicada é que o shell é que fica com a responsabilidade de ’traduzir‘ os globs e passá-los como parâmetro para aplicação. Deixando a aplicação livre do trabalho de interpretar os globs e substituí-los pelos nomes dos arquivos.

1.3. Comentários finais

Nesse capítulo aprendemos um pouco sobre a história dos sistemas UNIX, seus fundamentos e sua filosofia, com uma breve introdução ao uso da linha de comando. Esse conhecimento é fundamental para usar efetivamente o sistema. Coisas que podem parecer estranhas, como decorar os números octais equivalentes às permissões, facilitam o trabalho do usuário de sistema UNIX, e esse tipo de conhecimento pode ser assimilado facilmente com a prática e a disciplina de uso do sistema.

Continua…