Curso de Linux - Parte II

Após a familiarização com o uso do sistema GNU/Linux por meio da interface gráfica dada na primeira aula deste curso, buscaremos agora entender um pouco do que se trata um sistema operacional GNU/Linux, como ele é organizado, quais suas responsabilidades e como interagir com ele utilizando a linha de comando, o que possibilita utilizar um conjunto muito mais amplo de ferramentas.

Nosso roteiro completo é o seguinte.


Um pouco de história

Por ser o produto de um desenvolvimento colaborativo e livre, a história do Linux pode ser bastante confusa de se entender à primeira vista. O Linux surge a partir de uma reinterpretação do Minix, um sistema operacional (SO) baseado em Unix, por Linus Torvalds, reconhecido como o principal desenvolvedor e criador do Linux. Torvalds utilizava o SO Minix e, em 1991, começou a trabalhar em algumas ideias de um SO baseado em Unix, apesar disso, o resultado divergiu bastante do produto em qual foi inspirado.

Por sua vez, o Unix surge a partir de uma ramificação de um projeto chamado Multics, um SO que estava sendo desenvolvido por grandes empresas, como a Bell Labs, General Electrics e até mesmo o Instituto de Tecnologia de Massachusetts, mas que estava apresentando uma complexidade e tamanho exagerados. Por conta disso, alguns pesquisadores dessas empresas, lentamente, afastaram-se do desenvolvimento do Multics e começaram um novo projeto de escala menor, o Unics, que posteriormente passou a ser chamado de Unix (ninguém envolvido na época lembra como esse nome surgiu).

Com o nascimento do kernel Linux, diversos desenvolvedores foram capazes de construir seus próprios sistemas operacionais, como por exemplo a distribuição Ubuntu, que foi construída sobre a arquitetura e a infraestrutura do Debian, um outro SO que utiliza o kernel Linux. Grande parte dos SO baseados em Linux hoje em dia também seguem a licença pública geral (GNU), um conjunto de diversos softwares livres que podem ser utilizados e seguem a filosofia das quatro liberdades, visando garantir ao usuário a liberdade de usar, estudar, modificar e distribuir os softwares inclusos.

O que é um Sistema Operacional?

Embora falemos costumeiramente sobre Sistemas Operacionais (SO) — por exemplo, Ubuntu, Debian, Fedora, MacOS (e até mesmo, por mais incrível que pareça, o Windows) —, dificilmente nos perguntamos porquê ele é necessário e quais são suas responsabilidades.

Para responder o que é um SO, vamos primeiro considerar um cenário hipotético em que somente existem aplicações para o usuário final sendo executadas na máquina, por exemplo, o navegador que você está utilizando neste momento. Neste caso, os desenvolvedores do navegador precisariam saber como exatamente enviar comandos e interpretar as respostas dadas por todo o seu hardware. Por exemplo, seria necessário saber quais são as instruções implementadas no processador específico de sua máquina, sua webcam, seu disco rígido, como utilizar a CPU e memória em conjunto com outras aplicações que estão rodando simultaneamente na sua máquina e diversos outros aspectos.

Não é difícil de ver que desenvolver qualquer aplicação com o mínimo de qualidade em uma situação dessas é extremamente difícil (senão impossível). Para piorar a situação, não poderíamos garantir o mínimo de segurança, já que qualquer aplicação teria a permissão para manipular todo o sistema e o seu hardware diretamente!

Assim, conseguimos ver a necessidade de um mecanismo para resolver esses problemas. O sistema que objetiva lidar eles (e muito outros) é justamente o Sistema Operacional. Dada a tamanha complexidade dessa tarefa, os sistemas operacionais que utilizamos em nosso dia-a-dia (como o Ubuntu), podem ser divididos em diferentes níveis, cada um dos quais tem suas responsabilidades. O primeiro nível e mais importante é o kernel, que vamos discutir na sequência.

O kernel

O kernel (em português, núcleo), como seu próprio nome diz, é a parte fundamental de um SO. Ele é responsável basicamente, por lidar com a interação direta com o hardware do dispositivo, o que inclui a unidade central de processamento (CPU), memória RAM, dispositivo de armazenamento (por exemplo, disco rígido) e outros dispositivos de entrada e saída. Consequentemente, qualquer aplicação em execução (também chamada de processo) que necessite, por exemplo, de mais memória deve solicitar ao kernel, que por sua vez verificará qual região da memória física pode ser designada ao processo.

Assim, algumas das grandes tarefas a serem desempenhadas pelo kernel são:

  • Virtualizar a CPU. O objetivo é fazer com que os processos consigam utilizar a CPU alternadamente de uma forma justa e segura, mas sem que cada um deles saiba que isso está acontecendo.
  • Virtualizar a memória. Aqui o objetivo é parecido com o anterior, mas com a alocação de memória, de forma que o processo veja como se uma memória (menor do que a real) estivesse toda disponível ao processo. Isso possibilita mecanismos muito úteis, como o uso disco rígido como “extensão” da memória RAM (o chamado swapping).
  • Gerenciar o sistema de arquivos em disco. Diferente dos anteriores, aqui não é possível simplesmente deixar os processos verem ao que foi gerado por si mesmos, já que os dados do usuário deveriam pode ser manipulados por diferentes aplicações, por exemplo um editor de texto e um explorador de arquivos. Assim, o kernel fica responsável por gerenciar o acesso aos arquivos seguindo as permissões atribuídas a eles, além de efetivamente decidir onde os metadados e dados dos arquivos são salvos no disco.
  • Implementar drivers. Como o kernel é responsável pela manipulação do hardware, todo tipo de dispositivo que se deseja ser capaz de interagir necessita da implementação do protocolo de comunicação específico. Isso faz com que a maior parte do kernel possa ser na realidade de drivers!
  • Prover segurança. Como vimos nos itens anteriores, a segurança permeia todas as tarefas que o kernel desempenha e é uma de suas grandes responsabilidades.

Um dos kernels mais conhecidos é o Linux! Dizemos, então, que um sistema operacional é baseado em Linux quando ele utiliza o kernel Linux.

Uma forma de ver a versão do Linux que seu sistema operacional usa é por meio do comando uname --kernel-name --kernel-release, que veremos mais adiante nesta aula.

Shell: interagindo com o kernel

Até o momento, comentamos o que é o kernel e suas responsabilidades. Porém, naturalmente surge uma questão: como interagir com ele?

A forma de solicitar algo ao kernel do sistema operacional é por meio de chamadas de sistema (system calls, em inglês). Essas chamadas são instruções especiais da CPU, isto é uma instrução em linguagem de montagem (assembly, em inglês), que passam o controle para o kernel com alguma solicitação — por exemplo, abrir determinado arquivo — e depois retorna o controle para o programa solicitante quando o resultado está pronto. Esse tipo de interação, como ser notado, é bem programática.

Assim, existe um outro nível do sistema operacional é que responsável por prover uma interface mais fácil de ser utilizada para realizar solicitações ao kernel. Essa interface é chamada de shell (casca, em português). Note que a terminologia é bem intuitiva, como ilustrado pela figura a seguir.

Imagem de três círculos concêntricos, em que o interior possui o rótulo hardware, o intermediário kernel e o exterior shell

A shell pode ser visual ou por linha da comando (isto é, texto puro). Em linha de comando, como o próprio nome sugere, digitamos comandos que são interpretados pela shell e executados com ajuda do kernel.

Existem diferentes implementações de shells para sistemas Unix (como o Linux e MacOS). Algumas delas são as seguintes:

  • sh (Bourne Shell). Essa shell possui funcionalidades mais básicas e vem nos sistemas GNU/Linux por questão de retrocompatibilidade principalmente.
  • bash (Bourne Again Shell). É uma versão aprimorada da Bourne Shell. Está é a shell padrão dos sistemas GNU/Linux e a que vamos discutir em maiores detalhes parte final desta aula.
  • zsh (Z Shell). Também é uma aprimoramento da Bourne Shell, com aprimoramentos para o uso interativo.
  • fish (Fish Shell). Diferente das últimas duas, esta shell foi escrita sem basear em outras.
Aqui vemos uma das belezas de software livre: a construção de aprimoramentos com base em aplicações já existentes de forma que elas se adaptem a contextos específicos!
Muitos dos utilitários (isto é, programas com objetivos específicos) que utilizamos juntamente com a shell, assim como a própria Bourne Again Shell, são parte de uma coleção de pacotes oficiais do projeto GNU. Assim, sistemas operacionais que são baseados no projeto GNU e utilizam o kernel Linux são geralmente chamados de GNU/Linux.

Distros: complementando o kernel

Como podemos ver, mesmo com a ajuda da shell, ainda faltam componentes para um sistema operacional pareça com o que interagimos usualmente. Todos os componentes que são essenciais para o usuário final, mas não são providos kernel nem pela shell por linha de comando, são incluídos em distribuições GNU/Linux (ou também chamadas de distros, para os mais íntimos), como as discutidas no início da aula. Alguns desses componentes são os seguintes:

  • Sistema de inicialização (init system, em inglês). Esse é o primeiro processo a ser criado no sistema na fase de carregamento (boot) e é responsável por iniciar outros programas, incluindo o que disponibilizará o sistema de login do usuário. O sistema de inicialização mais utilizado atualmente nas principais distribuições GNU/Linux é o systemd.
  • Backend de áudio e vídeo. É responsável prover, consumir e processar dados de mídia (áudio e vídeo) para os processos em execução. Um exemplo é o PipeWire.
  • Servidor de Display. É responsável pela coordenação das entradas e saídas das aplicações por uma interface gráfica, o que possibilita, por exemplo, que possamos utilizar mouse para referenciar itens na tela e que tenhamos um gerenciador de janelas de aplicações. No Ubuntu 22.04, está utilizando um servidor de display baseado no Wayland. Porém o servidor legado X.org também está disponível para ser ativado.
  • Ambiente Desktop. É o que define diversos componentes visuais (como botões, menus, ícones) da interface do desktop. Por padrão, o Ubuntu e Fedora atualmente utilizam o ambiente GNOME. Outros ambientes populares são Xfce e KDE’s Plasma.
  • Aplicações do dia-a-dia. As distribuições geralmente incluem por padrão algumas aplicações úteis ao dia-a-dia, como tocadores de música e vídeo, leitor de PDF, navegador, entre diversos outros.

Esquematicamente, podemos organizar esses módulos relacionados a aplicações gráficas como mostrado na figura a seguir (incluindo também o kernel e utilitários do sistema vistos anteriormente).

Essa pilha de componentes é chamada, naturalmente, de Stack do Desktop Linux.

Organização da estrutura de diretórios

Como vimos, um sistema de arquivos tem um papel importante nas responsabilidades do sistema operacional. Porém, como os dados são organizados nele?

Basicamente, duas grandes abstrações existem para organizar dados em um disco: arquivos e diretórios (também chamados de pastas). Arquivos contém literalmente os dados que estamos interessados e metadados, como o nome e as permissões. Os diretórios basicamente são representações que contém arquivos ou outros (sub)diretórios. Dessa forma, temos o que chamamos de árvore de diretórios, como ilustrada abaixo.

A raiz (vértice inicial) dessa árvore é designado por / no Linux. Para nos referir a um arquivo ou diretório, adicionamos seu nome da sequência, por exemplo /arquivo.txt. Entre nomes de diretórios e arquivos, utilizamos novamente uma barra /, como em /home/anna/test.txt. Utilizando essa idea, é possível indicar qualquer arquivo na árvore de diretórios.

No caso do Linux (e outros sistemas), é seguida uma convenção para a organização dos diretórios da raiz do sistema. Basicamente, os principais diretórios mais comuns de interagirmos no dia-a-dia são os seguintes:

  • /bin: Contém utilitários essenciais, como o bash;
  • /home: Contém os dados dos usuários;
  • /media: Ponto de montagem de dispositivos removíveis, como pen-drives;
  • /mnt: Ponto de montagem para outros sistemas de arquivo, por exemplo, de um outro disco rígido;
  • /tmp: Local para arquivos temporários. Por padrão, esse diretório é completamente apagado após reiniciar o sistema.
  • /usr: Uma hierarquia secundária;
  • /usr/bin: Contém a maior parte de programas (em binário);
  • /usr/lib: Contém as bibliotecas dos programas;

Os outros diretórios seguem a seguinte linha de raciocínio.

  • /boot: Contém arquivos estático do inicializador do sistema;
  • /dev: Contém arquivos que representam os dispositivos da máquina, como a CPU, incluindo dispositivos especiais como o zero e o null;
  • /etc: Possui configurações de sistema;
  • /lib: Inclui as bibliotecas compartilhadas e outros módulos do kernel;
  • /opt: Pacotes de aplicações adicionais ao sistema, normalmente a serem utilizadas pelo usuário final, como um navegador;
  • /proc: Contém informações dos processos e do sistema
  • /root: Contém a home do usuário root.
  • /sbin: Binários essenciais do sistema, alguns dos quais veremos mais adiante.
  • /srv: Local para armazenamento dos dados de serviços oferecidos pelo sistema. Normalmente, não é utilizado em um ambiente Desktop;
  • /usr/sbin: Binários do sistema que não são essenciais;
  • /usr/include: Cabeçalhos de programas C;
  • /usr/share: Outros dados somente de leitura independente de arquitetura da CPU, como os manuais dos programas;
  • /var: Local para as aplicações armazenarem dados variáveis;

Para uma explicação mais detalhada dessa convenção, veja o documento completo do padrão.

Utilizando a linha de comando

Programas de linha de comando, como o shell, são interpretadores interativos de uma linguagem. Basicamente, o texto inserido no console é lido (até um caractere de fim-de-linha, inserido pela tecla Enter), é interpretado como um comando e é executado na hora, gerado uma potencial saída de texto. Para aqueles familiarizados com Python, o console interativo do Python é um exemplo de programa por linha de comando.

Para ver isso funcionando, vamos abrir um terminal, que pode ser iniciado na visão das atividades (como vimos na última aula) ou pelo atalho Ctrl + Alt + t. Como dito anteriormente, estamos considerando a execução no Ubuntu, portanto, a shell que estaremos interagindo é a Bourne Again Shell, ou simplesmente bash para facilitar.

Você verá uma linha escrito algo como

username@hostname:~$

A primeira parte é o nome de usuário que você deu durante a instalação do sistema. Como o Linux é um sistema multiusuário, essa informação é importante para sabermos qual usuário estamos utilizando na linha de comando. Na sequência, parece o nome da máquina que você está, com o separador @ entre eles. O @ é lido em inglês como at, ou seja, “em” no português. Portanto, podemos entender literalmente como “tal usuário na máquina tal”. Após essa parte, temos mais um separador, o : (dois pontos), seguido do caminho atual na árvore do sistema de arquivos. Por padrão, o terminal é iniciado com a bash na sua pasta de usuário (~). Por fim, temos um caractere ($) que simboliza o fim dessas informações e o início do comando atual a ser executado (que está vazio no momento).

Para aquecer, vamos ver o resultado daquele comando que vimos na Seção sobre o kernel, que mostra a versão do kernel sendo utilizada em nossa máquina. Para isso, digite o comando

username@hostname:~$ uname --kernel-name --kernel-release

e pressione Enter. Uma saída parecida com

Linux 5.13.0-44-generic

aparecerá na tela logo abaixo do comando.

Vamos agora dar uma olhada mais de perto no comando executado. Vemos que ele tem um nome (uname) e é seguido por o que chamamos de argumentos (--kernel-name e --kernel-release). A ideia é muito parecida com a de funções em linguagens de programação, sendo neste caso o uso do -- um indicativo que o termo que se segue é o nome de um parâmetro do programa. Neste caso, não é passado nenhum valor explícito ao parâmetro, o que significa que ele deve ser interpretado como verdadeiro ou ativado.

Geralmente, existem alternativas mais curtas para ativar determinados parâmetros, de forma que o uso da linha de comando seja mais ágil. No caso do uname, o argumento --kernel-name pode ser substituído por -s e --kernel-release por -r, resultando no comando

username@hostname:~$ uname -s -r

Podemos reduzir ainda mais o número de comandos concatenando todos os caracteres de argumentos reduzidos (que começam com somente um -), ficando com o comando resultante

username@hostname:~$ uname -sr

Antes de continuar explorando alguns comandos mais comuns, vamos ver alguns atalhos úteis do bash e como buscar ajuda para descobrir como funcionam os comandos e seus parâmetros.

Alguns atalhos do bash

Um ponto muito importante é que o bash também provê algumas funcionalidades para facilitar ainda mais nossa vida. Por exemplo, podemos começar a digitar um determinado comando e pressionar Tab para realizar um completamento automático. Caso o comando atual seja ambíguo, podemos ainda precisar mais uma vez e receber uma lista de sugestões dos comando que atualmente batem com o escrito.

Outro ponto importante é a possibilidade de não digitar os comandos do zero toda vez. O bash armazena em disco um histórico dos comandos digitados. Para navegar nesse histórico, você pode utilizar as setas para baixo (↓) e para cima (↑) de seu teclado.

Outra opção muito interessante é a busca reversa, embora seja menos intuitiva de ser utilizada em um primeiro momento. Ela pode ser ativada por meio do Ctrl + R. Nela, você digita parte de um termo que continua no comando desejado e ela retorna a última ocorrência. Para continuar buscando acima da última ocorrência encontrada, basta continuar pressionando Ctrl + R. Uma vez encontrado resultado desejado, basta pressionar Enter para executar o comando ou seta para a direita (→) ou (←) para editar o comando atual.

Em alguns momentos, podemos querer desistir da execução de algum comando. O bash possui o atalho Ctrl + C justamente para mandar esse sinal ao programa sendo executado atualmente. Ele também serve para desistir do comando digitado até o momento, devolvendo a linha de comando limpa novamente. Por outro lado, para limpar a tela toda e voltar a ter a linha de comando no topo, podemos usar Ctrl + L.

Diversos outros atalhos existem o podem vir a ser úteis em algum momento. Para descobri-los, vamos agora aprender como buscar ajuda.

Buscando ajuda

Uma convenção utilizada para os programas a serem utilizados na linha de comando é ter um argumento chamado --help que dá um resumo da funcionalidade do programa e dos seus argumentos. Em alguns programas, o argumento pode aparecer somente na sua forma extensa, somente na sua forma reduzida (geralmente -h) ou em ambas. Por exemplo, no caso do uname, uma versão reduzida para a ajuda não está disponível.

Embora o --help (ou -h) dê algumas informações sobre os argumentos, a ideia é que ele seja uma ajuda rápida (normalmente para quando você não lembra do nome do argumento). Para ter uma explicação do comando e de seus argumentos, utilizamos o man (que é um encurtamento da palavra manual). A maioria dos programas que vamos ver nas próximas seções possuem uma entrada nos manuais. Para visualizar um manual do uname, por exemplo, basta executar o comando

username@hostname:~$ man uname

Ele exibirá o manual de uma forma navegável. Veja o menu de ajuda (apertando h) fornecido por ele.

Para saber mais sobre o man, podemos usar… adivinhe… man man! Além disso, é possível usar saber mais sobre o bash por meio de man bash. Resumindo: está na dúvida de algo? Tente man algo.
Podemos usar o man em outros objetos também. Por exemplo, podemos descobrir quais são as funções em uma dada biblioteca da linguagem C (por exemplo, a stdio) ou, ainda, como é a assinatura de uma determinada função.

Note que os manuais já estão disponíveis em sua máquina, de forma que não é necessário acesso à Internet para realizar uma consulta.

Nos próximas seções, caso você já conheça algum dos comandos, experimente dar uma olhada no manual dele. Provavelmente verá um argumento muito útil que você não conhecia!

Também é muito comum navegar pelo seu sistema de arquivos diretamente pelo terminal. Como já foi dito, o seu terminal deve abrir no diretório do usuário — representado pelo símbolo ~ — e caso você precise interagir com um arquivo que está em outro diretório, saber navegar pelos seus arquivos é essencial! Comentaremos todos os comandos que consideramos úteis, falando sobre sua função, alguns detalhes e dicas.

Para essa parte, utilizaremos bastante . ou .. em alguns comandos porque eles representam diretórios. . representa o diretório onde você se encontra. .. representa o diretório acima do diretório onde você se encontra — caso exista —, chamado de diretório pai.
  •  ls argumentos diretorio 

    O comando ls lista os diretórios e arquivos em um determinado diretório. Caso você não forneça nenhum diretório, ele considera o diretório em que você se encontra. Seus argumentos mais utilizados são -a, que inclui diretórios e arquivos ocultos — que possuem ‘.’ no início do seu nome Ex: “.bashrc” — -l, que lista em formato de tabela e -R, que lista recursivamente cada diretório dentro desse diretório.

    O exemplo abaixo lista todos os arquivos em formato de tabela presentes no diretório atual.

     username@hostname:~$ ls -la 
  •  cd diretorio 

    O comando cd — abreviação de “change directory” — muda de um diretório para outro. Caso você esteja no diretório ~ e queira ir pra sua área de trabalho, basta digitar cd Desktop/. O diretório pode ser estendido indefinidamente, então se quiser acessar diretamente uma pasta chamada “pasta1” em sua área de trabalho, basta digitar, do diretório ~, cd Desktop/pasta1/.

    O exemplo abaixo navega para o diretório pai do diretório atual.

     username@hostname:~$ cd .. 
  •  mkdir diretorio 

    O comando mkdir – abreviação de “make directory” — atua criando o diretório passando como parâmetro. Caso você queira criar um subdiretório junto, utilizar o argumento -p é o ideal. Usando o comando mkdir -p dir1/subdir1/, o diretório dir1/subdir1/ será criado.

    O exemplo abaixo cria um diretório chamado LinuxFTW no diretório padrão do usuário.

     username@hostname:~$ mkdir ~/LinuxFTW/ 
  •  rmdir diretorio 

    O comando rmdir – abreviação de “remove directory” — atua removendo o diretório passando como parâmetro. Porém, ele remove apenas diretórios vazios, caso você queira deletar um diretório e todo seu conteúdo, você necessita usar o comando rm que será detalhado na próxima sessão.

    O exemplo abaixo remove um diretório chamado RuindowsFTW do diretório padrão do usuário.

     username@hostname:~$ rmdir ~/RuindowsFTW/ 
    Cuidado ao utilizar os comandos rmdir e rm com o prefixo sudo, alguns códigos maliciosos utilizam disso para causar grandes danos ao seu sistema.
  •  pushd argumentos diretorio 

    Adiciona o diretório especificado numa pilha de diretórios, que pode ser acessada usando outros comandos abaixo, e navega até ele. Com o argumento -n a navegação não é realizada.

    O exemplo abaixo adiciona o diretório pai na pilha de diretórios sem navegar até ele.

     username@hostname:~$ pushd -n .. 
  •  popd argumentos diretorio 

    Remove o diretório no topo da pilha de diretórios e navega até ele.

    O exemplo abaixo acessa o diretório no topo na pilha de diretórios armazenado no exemplo acima.

     username@hostname:~$ popd 

    E produz a saída

     username@hostname:/home$ 
  •  dirs 

    Mostra a pilha de diretórios lembrados — que foram adicionados usando o comando pushd. Alguns argumentos importantes são -c que remove todos os diretórios na pilha; -p que mostra cada diretório por linha e -v que é como o -p, mas ele também enumera!

Manipulando arquivos

Uma das mais poderosas aplicações da linha de comando é o gerenciamento de arquivos. Podemos manipular a identidade do arquivo (buscando-o, movendo-o, copiando-o, deletando-o, etc) e o conteúdo do arquivo (buscando e substituindo padrões, enumerando caracteres, juntando arquivos, etc). Por conta disso, dividiremos essa sessão em Arquivo e Conteúdo.

Arquivo

Considere um cenário em que você precisa documentar um projeto que será adicionado em um repositório de código do seu novo projeto de software livre. Um tipo de arquivo comumente utilizado nesse cenário é o README.md. Precisamos então criar este arquivo.

Uma forma de criar o arquivo é escrever um conteúdo diretamente em um editor de texto de linha de comando e salvá-lo com o nome de um arquivo ainda não existente. Essa opção fará mais sentido quando passarmos para a segunda parte desta seção.

Vamos criar um arquivo inicialmente sem conteúdo algum. Neste caso, podemos utilizar o comando touch (tocar, em português), como mostrado a seguir.

touch README.md

Liste os arquivos do diretório atual após o comando para certificar que ele foi realmente criado. Outra operação importante é mover ele. Consideramos que, por algum motivo, queremos colocá-lo na pasta docs dentro da pasta atual, podemos usar o comando mv para realizar a tarefa da seguinte maneira

mv README.md docs

Outra forma é passar também um nome para o arquivo de destino. Assim, fazemos duas operações em uma: mover e renomear. Por exemplo, poderíamos fazer algo como

mv README.md docs/README.txt

Assim, o mv também serve para simplesmente renomear arquivos. Basta movê-los para o mesmo diretório, mas com outros nomes.

Com uma sintaxe bem parecida, podemos copiar um arquivo com o comando cp. Experimente copiar o arquivo docs/README.md de volta para pasta atual. O cp também serve para copiar pasta por completo também, bastando dizer para ser executado recursivamente com o argumento --recursive (-r).

Também de forma muito parecida podemos excluir um arquivo com o comando rm. Quando executado sem o modo recursivo, rm só apaga arquivos. Utilizando o modo recursivo (-r), porém tudo tentará ser apagado diretamente.

Cuidado ao utilizar o comando rm -r, já que não haverá pedido de confirmação. Utilizar o argumento --force (-f) pode tornar as coisas ainda mais catastróficas, já que tudo que for possível será apagado.

Conteúdo

Para a edição do conteúdo de algum arquivo, há bastante limitações, já que cada tipo de arquivo tem sua melhor forma de ser editado, por exemplo, como editariamos um arquivo .jpg utilizando um editor de texto? Portanto, vamos focar apenas em arquivos de texto.

Para editar arquivos de texto utilizamos, isso mesmo, um editor de textos! O que vem por padrão na maioria das distribuições é o nano. Para editar um arquivo com ele, basta digitar nano nome_do_arquivo, e seu terminal se transformará num editor de texto! Os comandos para salvar, sair, entre outros estão na parte de baixo do editor, e o caractere ^ significa a tecla Control do teclado.

Um ótimo editor de texto, com alta produtividade, customização, suporte e pontos de estilo é o Vim! A maioria das máquinas possuem o Vi instalado, mas não sua versão melhorada, o Vim — Vi improved — entretanto, os dois são bastante similares.

Mudando permissões de arquivos

Como mencionamos brevemente nas responsabilidades do kernel, uma preocupação importantíssima é a segurança do sistema. No sistema de arquivos, isso é refletido principalmente pelo esquema de permissões de arquivos (e diretórios). Para visualizar algumas permissões, vamos continuar utilizando o arquivo README.md criado anteriormente. Para isso, entre no diretório que ele está e use o argumento -l do comando ls. Como saída, será gerado algo como

-rw-rw-r-- 1 username usergroup 0 jun  3 10:30 README.md

Os primeiros caracteres indicam justamente a permissão do arquivo. Ignorando o primeiro caractere, que indica o tipo do registro no sistema de arquivo, temos três partes de três caracteres, isto é rw-, rw- e r--. Ele são chamado coletivamente de modos do arquivo. A primeira parte indica as permissões do seu usuário. O r indica que seu usuário tem a permissão de ler (read, em inglês) o arquivo, escrever (write) e não tem a permissão de executar (simbolizado por x quando permitido) o arquivo. A segunda parte indica as permissões do grupo do seu usuário (neste caso, o grupo é seu user name e só contém seu próprio usuário). Por fim, a última parte indica as permissões de qualquer usuário, que neste caso não possui permissão de escrita.

Podemos mudar essas permissões por meio do comando chmod (abreviação de change mode ou mudar modo). Neste caso, passamos qual é o novo modo de cada parte. Para uma delas, vamos utilizar um caractere para indicar o nível de permissão, dentre usuário (u), grupo (g) e outro (o), seguido de um = e as permissões que queremos configurar utilizando os caracteres rwx. Para juntar cada nível, separamos por vírgula. Por exemplo, para mudar as permissões para rwx, r-- e ---, usamos

username@hostname:~$ chmod u=rwx,g=r,o= README.md

Verifique o resultado novamente com o ls -l.

Também é possível realizar a mesma tarefa passando valores decimais correspondentes aos bits da permissão desejada. Confuso? Calma, realmente é. Leia o manual com cuidado que fica mais claro.
Outro comando relacionado com chmod é o chown (abreviação de_change owner_), que muda o dono do arquivo.

Comandando seu sistema

Também podemos comandar nosso sistema usando a linha de comando do terminal! Nessa sessão iremos estudar os comandos que alteram o status de atividade de nossa máquina.

  •  shutdown argumentos tempo mensagem 

    O comando shutdown atua desligando a máquina em um determinado tempo. Seus argumentos mais utilizados são -r que, ao invés de desligar, reinicia seu sistema e -c que cancela algum desligamento agendado. Para agendar um desligamento, basta passar o tempo que você deseja desligar. O argumento +m agenda um desligamento para daqui m minutos. A palavra now referencia o tempo 0, portanto sudo shutdown now inicia o desligamento do seu computador imediatamente.

    O exemplo abaixo agenda um desligamento para meia noite.

     username@hostname:~$ sudo shutdown 00:00 
    O comando shutdown precisa do prefixo sudo para ser executado.
  •  exit 

    O comando exit atua encerrando a shell que você se encontra.

    O exemplo abaixo encerra uma janela de terminal aberta.

     username@hostname:~$ exit 

Gerenciando processos

Uma outra grande utilidade do uso da linha de comando é para manipular os processos (isto é, programas em execução) da sua máquina. Isso possibilita, por exemplo, que você aniquile um programa travado de uma forma simples, como veremos.

Na primeira parte desta seção, veremos manipulação de processos em execução no sistema no geral e na segunda parte processos especificamente sendo executados na instância atual da bash (isto é, seus processos filhos).

Processos do sistema

Primeiramente, uma funcionalidade esperada é ter uma forma de saber quais são os processos que estão sendo e informações associadas. Um desses comandos é top, que mostra de forma dinâmica os processos em execução, além de algumas informações de consumo de recursos (CPU e memória).

O ps, por outro lado, captura no instante de sua execução a lista de processos. Alguns argumentos úteis são o -e (lista todos os processos) e -f (formata com mais informações).

Podemos então combiná-lo com o grep para achar um processo em específico. Por exemplo, para achar o navegador (supondo que seja o Firefox), podemos executar

username@hostname:~$ ps -e | grep firefox

o que gerará um output parecido com

   2169 ?        00:18:52 firefox

O primeiro valor mostrado na linha da saída (2169, neste caso) é o chamado identificador do processo (process identifier, em inglês), que é abreviado por PID. Por meio dele, podemos dizer qual processo gostaríamos de parar, por exemplo.

Para ilustrar justamente isso, abra o explorador de arquivos pela interface gráfica. No GNOME, o explorador de arquivos possui o nome de nautilus. Assim, para descobrir o número do processo devemos executar o seguinte comando.

username@hostname:~$ ps -e | grep nautilus

Supondo que o PID seja 22752, podemos então encerrar forçadamente o explorador de arquivos por meio do comando

username@hostname:~$ kill -9 22752

O argumento -9 indica que será enviado o sinal SIGKILL ao(s) processo(s) listados na sequência.

Cuidado ao utilizar kill em um processo, uma vez que não é solicitada a confirmação. Você pode perder algum progresso que não esteja salvo na aplicação em questão.
Existem outros programas para encerrar processos, incluindo pkill e killall, alguns dos quais aceitam parâmetros com o nome do executável ao invés do PID.

Processos filhos da shell

Até o momento, estivemos executando somente um processo por vez na shell. Porém, é possível ter sob controle diferentes processos em uma mesma instância. Esses processos são chamados de jobs (tarefas, em português).

Para ilustrar como manipular esses jobs, vamos considerar um cenário em que um editor de arquivos (pelo terminal) está sendo usado. Por exemplo, poderíamos estar editando (ou criando) um arquivo README.md no diretório atual por meio do nano.

username@hostname:~$ nano README.md

Durante a edição, pode ser que necessitemos verificar algo nos diretórios ou arquivos. Uma possibilidade é abrir outro terminal e verificar, porém isso pode ser inconveniente em alguns cenários, como em uma conexão SSH. Assim, podemos dizer à bash atual que pause o processo atual (isto é, ele para a execução na instrução atual).

Para fazer isso, basta pressionar as teclas Ctrl + z. Com isso, é gerada a seguinte saída

Use "fg" to return to nano.

[1]+  Stopped                 nano

e obtemos novamente a possibilidade de executar comandos na bash. Neste caso, o processo do nano ganhou (+) o identificador 1 de job. Neste momento, portanto, poderíamos executar qualquer outro comando no terminal que desejássemos.

Caso desejemos listar os jobs atuais, usamos o comando de mesmo nome, isto é

username@hostname:~$ jobs

que retorna uma saída parecida com o gerada na primeira fez que o projeto é pausado.

Para retomar a execução de um processo temos agora duas opções, basicamente. A primeira delas é voltar para o estado que estávamos antes de pressionar Ctrl +z. Neste caso, queremos que o processo volte a ficar “na frente” (foreground em inglês). O comando para realizar isso é o fg. Em um cenário em que vários jobs estão pausados, precisamos passar também o identificador na hora de executar o comando. Em nosso cenário do editor de texto, podemos então executar

username@hostname:~$ fg 1

em que o 1 é o identificador do nano.

A outra opção é deixar o comando executando por baixo dos panos, isto é, no background. Para isso usamos o comando bg. No caso de um editor de texto, não faz sentido a execução em background, já que ele é um comando interativo. Em outros cenários, porém, pode ser muito útil. Quando o processo é finalizado no background, seu status muda para concluído na próxima vez que executar jobs.

Gerenciando pacotes

Uma feature incrível do Linux é que podemos instalar pacotes pela linha de comando de nossa shell! O gerenciador de pacote costuma variar para cada distribuição e podem também variar como funcionam. Para isso, utilizamos o comando apt — abreviação de “advanced package tool” — e utilizamos seus argumentos para especificar se queremos instalar, atualizar ou remover um pacote.

Os argumentos mais utilizados são:

  • update: é utilizado para baixar informações novas relacionadas a um pacote.
  • upgrade: é utilizado para instalar todos os upgrades disponíveis de todos os pacotes instalados no sistema.
  • full-upgrade: ele faz a mesma coisa que o upgrade, mas remove os pacotes que atrapalharem a atualização do sistema.
  • install: esse é o mais utilizado, ele instala o pacote.
  • remove: e esse remove.

Caso você queira instalar a linguagem de programação Python, por exemplo, basta você abrir o seu terminal, digitar sudo apt install python, assim como na shell abaixo.

username@hostname:~$ sudo apt install python

Nunca foi tão fácil, né? :)

Os comandos de gerenciamento de pacote precisam do prefixo sudo! E tome cuidado tentando supor os nomes dos pacotes que você quer instalar! Talvez instale um pacote indesejado.

Abrindo aplicações gráficas

Algumas aplicações podem ser abertas diretamente pela shell, nesse caso, a shell inteira será utilizada para continuar rodando esse aplicativo, com base nisso, a seção Gerenciando Processos pode ajudar bastante a lidar com esse cenário! Algumas dessas aplicações são:

  • firefox: o navegador de internet.
  • code: o editor de código Visual Code.
  • nautilus: o gerenciador de arquivos do Ubuntu (e de outras distros).

Com isso, basta você digitar o nome do aplicativo e ele começa a rodar, abrindo uma interface gráfica normalmente, como se você tivesse aberto do seu menu de aplicações. Por exemplo, caso você queira abrir o Visual Code para programar um arquivo em Python mas quer fazer isso dentro de um novo diretório chamado src, você pode digitar o comando

username@hostname:~$ code src/script.py

Outro comando bastante útil nesse caso é o xdg-open que abre qualquer arquivo passado no parâmetro na aplicação de preferência do usuário. Por exemplo:

Caso você queira abrir um arquivo .html, uma página da web, no seu navegador pelo terminal, você pode digitar o comando

username@hostname:~$ xdg-open index.html

Utilizando TeleTypewriter (TTY)

Durante toda a aula, utilizamos o terminal para a execução dos comandos. O terminal é um software de interface gráfica do ambiente de Desktop utilizado. Porém, existe também a possibilidade de interagir com a linha de comando por outro mecanismo, que não depende do ambiente do Desktop: o TeleTypewriter (TTY).

Antes de testá-lo em sua máquina, tenha certeza que entendeu como voltar para interface gráfica novamente (ou abra este post em outro dispositivo).

Quando o sistema é iniciado, vários TTY são criados e são identificados por números. Para acessar o TTY 3, basta pressionar conjuntamente as teclas Ctrl + Alt + F3. Para retornar à interface gráfica, pressione Ctrl + Alt + F2. Os outros TTY podem ser acessados com o mesmo atalho, mas variando a numeração da tecla F utilizada.

Uma vez em um TTY, é possível realizar login e ter acesso a um bash. Nele, você pode executar tudo que aprendeu nesta aula sem depender da interface gráfica do sistema.

Isso se torna muito útil em uma situação que algum processo está deixando a máquina muito lenta, de forma que é inviável abrir um terminal para aniquilá-lo.

Como esta aula tem muitos conceitos e novidades, não hesite em revisitá-la daqui um tempo para absorver um pouco mais do que foi exposto.

Isso conclui a segunda parte do Curso de Linux. Até semana que vem! 🐧