DIY-PACKAGE MANAGER: N-a1

Atualização: 20/01/2020
Versão: 1.0
Autor: Jefferson 'Slackjeff' Rocha

Conteúdo

  1. N-a1
  2. Tipos de gerenciadores de pacotes
  3. Iniciando
  4. Considerações finais da série a1

N-a1

Este nível a1 visa abordar a parte estrutural de um gerenciador de pacotes de baixo nível, seguindo a filosofia do UNIX, vamos criar a primeira versão bem simplória de três programas: (criar, instalar, remover) um pacote.

Não vamos se preocupar tanto com vaidade e sim como funciona todo o esquema destes três processos. Por isso é de extrema importância você não executar estes programas em seu sistema operacional diário! Você pode executar em uma virtualbox, ou até mesmo criar um ambiente enjaulado e entrar com chroot.

Este nível como dito acima não chega nem perto de um gerenciador de baixo nível seguro e utilizável em uma distribuição diária. Mas é totalmente funcional e é o pontapé inicial. Tenha uma boa diversão.

2. Tipos de gerenciadores de pacotes

Normalmente pensamos que um gerenciador de pacotes é tudo a mesma coisa, quando normalmente não é! Existem dois tipos de gerenciadores os de baixo nível e os de alto nível. Cada um realiza um tipo de função, como por exemplo, o dpkg do debian faz a parte estrutural de um pacote e o apt/apt-get faz a parte inteligente, como resolver dependências.

2.1 Baixo nível

O gerenciador de baixo nível é o coração do gerenciador. É ele que cria, instala, remove, atualiza pacotes. Alguns exemplos de gerenciadores de baixo nível são:

Um fato que precisamos saber é que normalmente gerenciadores de baixo nível não resolvem as depêndencias dos softwares. Eles normalmente empregam uma técnica em algum arquivo para o próprio empacotador do software adicionar as dependências! E este arquivo é puxado pelo gerenciador de alto nível que se encarrega de listar as dependências e resolver as mesmas.

Então quando você faz alguma função listada acima, você está utilizando o gerenciador de baixo nível!


2.2 Alto nível

O gerenciador de alto nível realiza a parte mais inteligente do gerenciamento, é ele que se encarrega de baixar pacotes disponivel em um determinado espelho, resolve dependências se assim permitir e faz todo este processo mais burocrático. Gerenciadores de alto nível:

Devo salientar que normalmente os gerenciadores de alto nível são propensos a milhares de erros! Fazer algo inteligente é complicado. Você pede pra remover o D por exemplo, se o A, B e C depender do D e o Y depender do A e for removido jundo com o D, acaba quebrando tudo. Você acaba ficando sem os softwares dito acima :)

O bom de se trabalhar com gerenciadores de alto nível é mais pela praticidade, mas precisa ser programado com muito cuidado.


3. Iniciando

3.1 Abordagem fakeroot

O primeiro passo é você entender como funciona um software compilado da fonte!
Vamos utilizar o mêtodo Fakeroot, que significa Raiz Falsa, para entendermos o conceito precisamos saber como é instalado um programa por de trás dos panos.

Para esta abordagem vamos usar um software criado com autotools, que possui ferramentas como ./configure, makefile etc...

Ao grosso modo ao instalar um programa ele possui toda a estrutura dos diretórios, como usr/{bin,share,man,doc}, etc/, /lib64, /lib ... Está estrutura se instalado com make install é solta diretamente na raiz / do sistema, logo podemos saber que a instalação será feita e você já pode executar o seu software.

Isso se tratando de softwares convencionais, os opcionais como Firefox/Telegram etc... Normalmente ficam em um diretório a parte em opt/ e normalmente já são pré-compilados.

Quando compilamos e fizemos um make install é feito o passo que falamos acima, é solto basicamente toda a estrutura do programa pré compilado na raiz /. Este processo é válido e se o desenvolvedor ter a boa vontade ele pode ter inserido a opção de desinstalação do software make desinstall.

Caso não houve boa vontade você terá que remover toda hierarquia instalado pelo software manualmente! Isto te dá um controle é claro, mas o tempo que você perde... Hmm, é complicado.

É ai que entra o fakeroot, vamos enganar o sistema dizendo um diretório especifico ao invés de instalar na raiz /. Este diretório falso é essencial, pois podemos manipular todo o programa pre-compilado antes da instalação. Isto se tratando de software compilados, se for scripts podemos usar a mesma abordagem do fakeroot :)

Assim fizemos os nossos enfeites que vamos abordar mais adiante.

Para um exemplo, supondo que rodei toda a base ./configure && make e ao invés de por make install somente botaria em make install DESTDIR=/Um/Diretorio

Vamos usar um software como base para compilar da fonte! Assim você terá mais visão do que abordamos acima.

Vamos utilizar o editor de texto nano para iniciar a brincadeira.

Primeiramente crie um diretório em /tmp/ chamado source é dentro deste diretório aonde vamos armazenar o código fonte do nano. Dentro de /tmp/source crie o diretório nano.

$ mkdir -vp /tmp/source/nano

E adquira o fonte do editor nano e baixe com wget em /tmp/source/nano/, após feito o download descompacte o mesmo!

$ cd /tmp/source/nano
$ wget https://www.nano-editor.org/dist/v4/nano-4.7.tar.xz
$ tar xvf nano-4.7.tar.xz

Após feito este processo vamos entrar dentro do diretório que foi criado, é lá que está todo o fonte do editor nano!

$ ls
nano-4.7  nano-4.7.tar.xz
$ cd nano-4-7

Como dito, este livro está trabalhando com abordagem do autotools. Execute agora o script configure com ele podemos fazer pré-configurações como: a raiz aonde será solto o software, desabilitar/habilitar funcionalidades em geral do software etc, etc...

Nota!

Você pode usar o ./configure --help para saber todas opções disponiveis que você pode habilitar/desabilitar.

Como o objetivo deste livro não é "como compilar um software" e sim "Como escrever o seu gerenciador de pacotes", vamos usar configurações genéricas neste livro para não ser muita informação para você neste momento. Saiba que se você não sabe como funciona a compilação de programas provavelmente você não está apto a criar o seu gerenciador ainda.

$ ./configure        \
--prefix=/usr        \   
--sysconfdir=/etc    \
--mandir=/usr/man    \
--libdir=/usr/lib64  \
--enable-utf8

Entendendo as configurações usadas no configure.

Lembre-se, isto não é uma receita de bolo, cada programa tem suas próprias configurações! O básico e pontapé inicial que você pode utilizar seria: (--prefix, --sysconfdir, --mandir, --libdir).

Feita isto vamos compilar o software em si! Para isto utilizamos o make, se seu processador ter vários núcleos você pode especificar com -j Número_de_Núcleos assim a compilação será mais rápida. Exemplo aqui possuo 6 núcleos.

$ make -j 6

Tudo certo até aqui? Não houve erros? então vamos fazer a instalação na raiz / falsa! a abordagem do fakeroot inicia aqui. Para isto vamos precisar criar agora um diretório em /tmp chamado build/nome-do-programa.versão. É lá que será a "raiz" falsa do editor nano. Crie o diretório em /tmp/build/nano-versao .

$ mkdir -vp /tmp/build/nano-4.7
mkdir: created directory '/tmp/build'
mkdir: created directory '/tmp/build/nano-4.7'

Feito isto vamos realizar a instalação com make install, passando a variável DESTDIR. Assim enganamos o sistema dizendo que a raiz é em /tmp/build/nano-versao

$ make install DESTDIR='/tmp/build/nano-4.7/'

Vamos agora entrar no diretório /tmp/build/nano-versao e ver que toda estrutura de diretórios estão lá!

$ cd /tmp/build/nano-4.7

$ ls 
usr

$ tree --filelimit 10
.
└── usr
    ├── bin
    │   ├── nano
    │   └── rnano -> nano
    ├── man
    │   ├── man1
    │   │   ├── nano.1
    │   │   └── rnano.1
    │   └── man5
    │       └── nanorc.5
    └── share
        ├── doc
        │   └── nano
        │       ├── faq.html
        │       ├── nano.1.html
        │       ├── nano.html
        │       ├── nanorc.5.html
        │       └── rnano.1.html
        ├── info
        │   ├── dir
        │   └── nano.info
        ├── locale [35 entries exceeds filelimit, not opening dir]
        └── nano [44 entries exceeds filelimit, not opening dir]

Perceba que toda hierarquia que seria solta na raiz / do sistema agora está aqui neste diretório!
Com isto podemos fazer o que quiser com o software, usar um compressor por exemplo como gzip e passar para um amigo o software compilado.
O seu amigo poderia extrair o software na raiz do sistema e teria o software pronto! Você poderia também a partir daqui copiar todo a estrutura para a raiz / do sistema.

Mas isto seria um problema! Pois como dito no inicio teriamos que rastrear toda a estrutura que foi solta na raiz do sistema e excluir uma por uma, arquivo por arquivo, diretório por diretório, link simbólico por link simbólico.
Em grosso modo, é isso que os gerenciadores de baixo nível fazem! Eles fazem o rastreamento do software, gerando um pacote agrupando tudo em um "arquivo" assim facilitando para usuário de ter que fazer tudo isso manual.

Entendeu a mágica do fakeroot/raiz falsa. Então a partir daqui vamos começar a criar nosso gerenciador de pacotes.


3.2 createpkg - Criando um pacote

O primeiro processo a ser feito é criar um script para empacotar o nosso software já compilado/estruturado, nesta primeira versão ele não fará nada além de empacotar e fazer pequenos ajustes!

No nivel a1 o createpkg deverá atender os seguintes requisitos:

Iniciamos nosso programa com a SHEBANG e alguns dados como autor, versão e descrição, logo após isso utilizamos o set -e para o programa parar se o status de qualquer comando for diferente de 0, ou seja erros.
Em seguida crie uma função chamada CREATE, ela será encarregada de fazer todo o processo de montagem do pacote.

#!/bin/sh
#=========================HEADER===============================|
#AUTOR:
# Jefferson Rocha 
#
#VERSÃO:
# 0.1
#
#DESCRIÇÃO:
#createpkg - cria pacotes compactador com tar e comprimido
#            com o utilitário xz.
#==============================================================|
# Erros? pare. 
set -e

#==========================| FUNÇÕES
CREATE()
{
}

O segundo passo é criar um case passando o primeiro argumento em linha, ou seja o primeiro parâmetro posicional $1. Em seguida criamos as opção para o case: -c|--create|create e rebaixamos um parâmetro com shift.
Logo após fizemos a conferência se o próximo parâmetro é nulo, se for devemos abortar pois o usuário não passou o nome do pacote, se não a FUNÇÃO CREATE deve ser chamada com o nome do pacote.

#==========================| INICIO
case $1 in
   -c|--create|create)
      shift # Rebaixando um parâmetro posicional.

      # O usuário passou o nome do pacote? exemplo dialog-teste.tar.xz
      if [ -z "$1" ]; then 
         echo "Você deve especificar o nome do pacote."
         exit 1
      fi

      # Chamando a função CREATE e passando como parâmetro o nome do pacote.
      CREATE "$1" 
   ;;
esac

Voltando a função CREATE, vamos preencher com alguns dados! O primeiro passo é ter uma variável para receber o parâmetro que enviamos para função. O nome da função será PKG, não esqueça de adicionar local na frente da variável para ficar somente local.

CREATE()
{
   # Recebendo o nome do pacote passado para a função.
   local PKG="$1"

Vamos fazer o empacotamento com o tar e comprimindo com o xz todo o diretório e gerar o "pacote" um diretório acima.

# Empacotando todo o diretório e gerando o pacote 
# um diretório acima.
tar -cvJf ../${PKG} .
echo "O pacote ${PKG} foi gerado com sucesso."
return 0
}

Feito isto já temos um empacotamento básico! Perceba que o programa não faz quase nenhum tipo de conferência e existe MUITO para adicionar para ficar funcional no dia-dia... Este processo é descrito na N-a2, aonde aprimoramos nosso software de uma maneira aonde poderemos usar no dia-dia.

O nosso programa deve ficar assim por completo:

#!/bin/sh
#=========================HEADER===============================|
#AUTOR:
# Jefferson Rocha 
#
#VERSÃO:
# 0.1
#
#DESCRIÇÃO:
#createpkg - cria pacotes compactador com tar e comprimido
#            com o utilitário xz.
#==============================================================|
# Erros? pare.
set -e

#==========================| FUNÇÕES
CREATE()
{
   # Recebendo o nome do pacote passado para a função.
   local PKG="$1"

   # Empacotando todo o diretório e gerando o pacote
   # um diretório acima.
   tar -cvJf ../${PKG} .
   echo
   echo "O pacote ${PKG} foi gerado com sucesso."
   return 0
}

#==========================| INICIO
case $1 in
   -c|--create|create)
      shift # Rebaixando um parâmetro posicional.

      # O usuário passou o nome do pacote? exemplo dialog-teste.tar.xz
      if [ -z "$1" ]; then
         echo "Você deve especificar o nome do pacote."
         exit 1
      fi

      # Chamando a função CREATE e passando como parâmetro o nome do pacote.
      CREATE1 "$1"
   ;;
esac

Entre como root, dê permissão de execução para o createpkg, e envie o programa para /usr/sbin aonde nosso programa administrativo deve ficar, assim somente o usuário root pode executar...

# ls
createpkg

# chmod -v +x createpkg 
mode of 'createpkg' retained as 0755 (rwxr-xr-x)

# cp -v createpkg /usr/sbin/
'createpkg' -> '/usr/sbin/createpkg'

Após isso entre até o diretório aonde fizemos o fakeroot do nano! Lembra que fizemos em /tmp/build/nano-4.7? É neste diretório que devemos entrar e executar o createpkg com os devidos parâmetros, no caso -c, --create ou create.

# cd /tmp/build/nano-4.7/

# ls
usr

# createpkg -c nano-4.7.tar.xz
./
./usr/
./usr/bin/
./usr/bin/rnano
./usr/bin/nano
./usr/share/
./usr/share/info/
./usr/share/info/nano.info
./usr/share/info/dir
./usr/share/locale/
./usr/share/locale/pl/
./usr/share/locale/pl/LC_MESSAGES/
./usr/share/locale/pl/LC_MESSAGES/nano.mo
./usr/share/locale/hr/
./usr/share/locale/hr/LC_MESSAGES/
./usr/share/locale/hr/LC_MESSAGES/nano.mo
./usr/share/locale/da/
./usr/share/locale/da/LC_MESSAGES/
./usr/share/locale/da/LC_MESSAGES/nano.mo
..............

O pacote nano-4.7.tar.xz foi gerado com sucesso em: ../nano-4.7.tar.xz.

Vamos retornar um diretório acima e veremos que o pacote foi gerado =)

# cd ..

# ls
nano-4.7  nano-4.7.tar.xz

3.3 pkginstall - Instalado um pacote

Temos já o nosso pacote criado mas falta alguma coisa? A instalação claro. A instalação é muito importante... Fica ao critério do instalador fazer o rastreamento/track de toda hierarquia que está sendo instalada. Sem este rastreamento ficaria inviável o gerenciador!
É como instalar com o make install, este track será importante para a remoção do pacote posteriormente.

No nivel a1 o pkginstall deverá atender os seguintes requisitos:

Vamos iniciar nosso script, criamos uma variável chamada INSTALLED_PKG e no seu contéudo deverá ter /var/log/installed.
Este diretório: /var/log/installed/ será armazenado a nossa lista de rastreamento dos softwares! Utilizados para outros processos posteriormente como remover o pacote.

#!/bin/sh
#==============================HEADER================================|
#AUTOR
# Jefferson 'Slackjeff' Rocha 
#
#VERSÃO
# 0.1
#
#DESCRIÇÃO
#pkginstall - Instala pacotes gerado com o createpkg e gera uma lista
#             de rastreamento em /var/log/installed/PKG.track
#====================================================================|

set -e

#======================== CONFIGURAÇÕES
INSTALLED_PKG="/var/log/installed"

Criamos uma área de testes! No caso conferindo se o diretório /var/log/installed existe! se não existir deve ser criado.

#======================== TESTES
[ ! -d $INSTALLED_PKG ] && mkdir $INSTALLED_PKG

Em seguida criamos uma função chamada INSTALL e um case simples com as opções: -i|--install|install.

Neste case, na opção -i|--install|install deveremos iniciar fazendo um rebaixamento de parâmetro com o shift, após isso fizemos uma conferencia com o test verificando se o pacote que o usuário está pedindo para instalar existe! Se existir deve chamar a função INSTALL.

INSTALL()
{

}

#======================== INICIO
case $1 in
   -i|--install|install)
      shift # Rebaixando parâmetro.
      # Se o arquivo existir chem a função INSTALL com o nome do pacote.
      [ -e "$1" ] && INSTALL "$1"
   ;;

   *) echo "Opções disponiveis: -i|--install|install" ;;
esac

Voltando para a função INSTALL, vamos começar a preparar nossa função. Estamos passando o nome do pacote para função, a função deve receber o argumento/pacote em uma variável chamada pkg, outra variável que devemos iniciar é a variável pkg_only_name, está variável armazenará somente o nome do pacote cortando qualquer extensão. Não esqueça do local para deixar somente local a variável.

INSTALL()
{
   # Nome do pacote completo com extensão
   local pkg="$1"

   # Nome do pacote sem extensão .tar.xz
   local pkg_only_name="${pkg//.tar.xz/}"

Agora que já temos o nome do pacote com extensão e sem vamos imprimir alguma coisa na tela para o usuário... Outra informação é iniciar o arquivo pacote.track em /var/log/installed/pacote.track enviando como comentário o nome do pacote.

# Informação para o usuário.
echo "========> Iniciando instalação do pacote ${pkg}"

# Criando arquivo .track e enviando nome do pacote como comentário.
echo "#PACOTE: ${pkg_only_name}" > "${INSTALLED_PKG}/${pkg_only_name}.track"
echo

Seguindo, vamos fazer a extração do pacote na raiz / do sistema; Utilizando o comando tar. Mas tem uma diferença... Precisamos enviar tudo que está sendo extraido na raiz com o comando tar para o arquivo pacote.track para isto use o redirecionamento >> que envia a sáida de um comando anexando o mesmo sem sobreescrever.

# Fazendo extração do pacote e enviando a lista do que está sendo instalado
# para o arquivo .track
tar xvf ${pkg} -C / >> "${INSTALLED_PKG}/${pkg_only_name}.track"

A lista de rastreamento que será criada em /var/log/installed irá ficar com alguns caracteres extras como: ./, /, e linhas em branco! Precisamos apagar tudo isso e deixar somente o que importa que é a hierarquia do que está sendo instalado.

Vamos usar o comando sed para fazer este processo, enviando a sua saída de erros para /dev/null.

echo "Removendo caracteres desnecessários do arquivo .track"

# Removendo alguns caracteres não importantes! e que podem resultar em falhas.
sed -i 's/\.\//\//g; s/^\/$//; /^$/d' "${INSTALLED_PKG}/${pkg_only_name}.track" 2>/dev/null

Então pedimos para o sed: substitua ./ por /; substitua se existir somente / por nada; apague linhas em branco.

Em seguida enviamos uma mensagem para o usuário seguido de um return 0.

echo "Pacote: $pkg foi instalado com sucesso."
return 0

Nosso programa deve ficar assim completo:

#!/bin/sh
#==============================HEADER================================|
#AUTOR
# Jefferson 'Slackjeff' Rocha 
#
#VERSÃO
# 0.1
#
#DESCRIÇÃO
#pkginstall - Instala pacotes gerado com o createpkg e gera uma lista
#             de rastreamento em /var/log/installed/PKG.track
#====================================================================|

set -e

#======================== CONFIGURAÇÕES
INSTALLED_PKG="/var/log/installed/"

#======================== TESTES
[ ! -d $INSTALLED_PKG ] && mkdir $INSTALLED_PKG


#======================== FUNÇÕES
INSTALL()
{ 
    # Nome do pacote completo com extensão
    local pkg="$1"

    # Nome do pacote sem extensão .tar.xz
    local pkg_only_name="${pkg//.tar.xz/}"

    # Informação para o usuário.
    echo"========> Iniciando instalação do pacote ${pkg}"

    # Criando arquivo .track e enviando nome do pacote como comentário.
    echo "#PACOTE: ${pkg_only_name}" > "${INSTALLED_PKG}/${pkg_only_name}.track"
    echo

    # Fazendo extração do pacote e enviando a lista do que está sendo instalado
    # para o arquivo .track
    tar xvf ${pkg} -C / >> "${INSTALLED_PKG}/${pkg_only_name}.track"

    echo "Removendo caracteres desnecessários do arquivo .track"
    # Removendo alguns caracteres não importantes! e que podem resultar em falhas.
    sed -i 's/\.\//\//g; s/^\/$//; /^$/d' "${INSTALLED_PKG}/${pkg_only_name}.track" 2>/dev/null

    echo "Pacote: $pkg foi instalado com sucesso."
    return 0
}

#======================== INICIO
case $1 in
   -i|--install|install)
      shift # Rebaixando parâmetro.
      # Se o arquivo existir chem a função INSTALL com o nome do pacote.
      [ -e "$1" ] && INSTALL "$1"
   ;;

   *) echo "Opções disponiveis: -i|--install|install" ;;
esac

Vamos fazer o nosso teste, vamos dar permissão e enviar para /usr/sbin, entrar no diretório /tmp/build/ aonde está localizado nosso pacote que criamos com o createpkg e executar o pkginstall seguido do nome do pacote.

# chmod -v +x pkginstall 
mode of 'pkginstall' retained as 0755 (rwxr-xr-x)

# cp pkginstall /usr/sbin/

# cd /tmp/build/

# pkginstall -i nano-4.7.tar.xz 
========> Iniciando instalação do pacote nano-4.7.tar.xz

Removendo caracteres desnecessários do arquivo .track
Pacote: nano-4.7.tar.xz foi instalado com sucesso.

Vamos primeiramente ver se nossa lista de rastreamento foi criada em /var/log/installed/.

# cat /var/log/installed/nano-4.7.track
#PACOTE: nano-4.7
/usr/
/usr/bin/
/usr/bin/rnano
/usr/bin/nano
/usr/share/
/usr/share/info/
/usr/share/info/nano.info
/usr/share/info/dir
/usr/share/locale/
/usr/share/locale/pl/
/usr/share/locale/pl/LC_MESSAGES/
/usr/share/locale/pl/LC_MESSAGES/nano.mo
/usr/share/locale/hr/
/usr/share/locale/hr/LC_MESSAGES/
/usr/share/locale/hr/LC_MESSAGES/nano.mo
/usr/share/locale/da/
/usr/share/locale/da/LC_MESSAGES/
/usr/share/locale/da/LC_MESSAGES/nano.mo
/usr/share/locale/ko/
/usr/share/locale/ko/LC_MESSAGES/
/usr/share/locale/ko/LC_MESSAGES/nano.mo
/usr/share/locale/ga/
/usr/share/locale/ga/LC_MESSAGES/
/usr/share/locale/ga/LC_MESSAGES/nano.mo
/usr/share/locale/fr/
......................

Tudo certo até aqui, será que o editor nano 4.7 está disponivel no sistema? chame em linha de comando nano e veja.


3.4 pkgremove - Removendo um pacote

Até agora conseguimos criar um pacote e instalar, porém uma das partes mais importantes não foi criada, a parte de remover o pacote e toda sua hierarquia no sistema.

Esta é a parte mais complexa, pois deverá ser feita com mais segurança! Já penso remover o que não pode na raiz / do sistema? Precisamos criar algo com um pouco mais de segurança... Por isso preste muita atenção nos detalhes.

Iniciamos nosso script como todos os outros dois que criamos, cabeçalho, e o set -e. Devemos iniciar uma função chamada REMOVE e um case com as opções: -r|--remove|remove.

Na opção -r|--remove|remove devemos inserir um shift para rebaixar o parâmetro posicional e um teste verificando se o pacote está no sistema. Para isto usamos como base o arquivo de track localizado em /var/log/installed.

Vamos também entrar no diretório /var/log/installed para iniciar o processo de remoção!

#!/bin/sh
#=========================HEADER===============================|
#AUTOR:
# Jefferson Rocha 
#
#VERSÃO:
# 0.1
#
#DESCRIÇÃO:
#removepkg - Remove pacote e hierarquia do sistema operacional.
#==============================================================|
# Erros? pare.
set -e

#==========================| FUNÇÕES
REMOVE()
{
}

#==========================| INICIO
case $1 in
   -r|--remove|remove)
      shift # Rebaixando um parâmetro posicional.

      # O usuário passou o nome de algum pacote?
      [ -z "$1" ] && { echo "Você deve fornecer o nome do pacote."; exit 1 ;}

      # Entrando no diretório /var lalala
      cd /var/log/installed

Vamos lançar um loop for com o glob * para verificar todos tracks que existem dentro do diretório /var/log/installed. Poderiamos usar um simples ls, porém para ganhar um pouco de velocidade e economizar pipes lançamos o loop for, fazendo uma verificação com grep se tem o padrão. O padrão no caso é o nome do pacote, se existir o padrão, lançamos em uma variável para fazer a captura e pausamos o for. Se tudo deu certo no final a função é iniciada.

# Fazendo a listagem do diretório /var/log/installed e
# conferindo se o pacote está instalado no sistema.
for pack_exist in *; do
   if ! [ "$(grep "$1" < ${pack_exist})" ]; then
      echo "O pacote $1 não está instalado no sistema."
      exit 1
   else
      # Capturando o nome do track para lançar na função REMOVE.
      pkg_for_remove="$pack_exist"
      break
   fi
done

# Chamando a função REMOVE e passando como parâmetro o nome do pacote.
REMOVE "$pkg_for_remove"
;;
esac

Agora o trabalho "pesado" vai começar, vamos começar a elaborar a função REMOVE como normal, vamos lançar a variável pkg_remove no inicio para capturar o nome do track.

REMOVE()
{
   local pkg_remove="$1"

Vamos precisar lançar três loops while para percorrer todo o arquivo track! Começando com Arquivo comum. O mesmo será alimentado com o track para ler linha a linha todo o track, com isto vamos removendo tudo que é arquivo que foi instalado.

# Deletando arquivos.
while read the_file; do
   rm $the_file 2>/dev/null && echo " Delete: $the_file"
done < "$pkg_remove"

Outro loop while precisa ser chamado, desta vez removendo SE existir links simbólicos... Eles não podem ficar soltos no sistema do usuário.

# Removendo links simbólicos se existir..
while read the_link; do
   unlink $the_link &>/dev/null && echo "Delete Links: $the_link"
done < "$pkg_remove"

E por último diretórios que ficaram vazios no sistema, por exemplo /usr/share/nano etc... Vamos fazer mais um loop while, ainda sendo alimentado pelo track.

# Removendo diretórios nulos
while read the_dir; do
   rm -d $the_dir &>/dev/null && echo "Delete empty directories: $the_dir"
done < "$pkg_remove"

Por final vamos apagar o arquivo nano.track para o gerenciador saber que ele foi removido!

echo "Deletando track ${pkg_remove}"
[ -e "${pkg_remove}" ] && rm -v ${pkg_remove}

Nosso programa pkgremove deve ficar assim no final:

#!/bin/sh
#=========================HEADER===============================|
#AUTOR:
# Jefferson Rocha 
#
#VERSÃO:
# 0.1
#
#DESCRIÇÃO:
#removepkg - Remove pacote e hierarquia do sistema operacional.
#==============================================================|
# Erros? pare.
set -e

#==========================| FUNÇÕES
REMOVE()
{
   local pkg_remove="$1"

   # Deletando arquivos.
   while read the_file; do
      rm $the_file 2>/dev/null && echo " Delete: $the_file"
   done < "$pkg_remove"

   # Removendo links simbólicos se existir..
   while read the_link; do
      unlink $the_link &>/dev/null && echo "Delete Links: $the_link"
   done < "$pkg_remove"

   # Removendo diretórios nulos
   while read the_dir; do
      rm -d $the_dir &>/dev/null && echo "Delete empty directories: $the_dir"
   done < "$pkg_remove"

   echo "Deletando track ${pkg_remove}"
   [ -e "${pkg_remove}" ] && rm -v ${pkg_remove}
}

#==========================| INICIO
case $1 in
   -r|--remove|remove)
      shift # Rebaixando um parâmetro posicional.

      # O usuário passou o nome de algum pacote?
      [ -z "$1" ] && { echo "Você deve fornecer o nome do pacote."; exit 1 ;}

      # Entrando no diretório /var lalala
      cd /var/log/installed

      # Fazendo a listagem do diretório /var/log/installed e
      # conferindo se o pacote está instalado no sistema.
      for pack_exist in *; do
         if ! [ "$(grep "$1" < ${pack_exist})" ]; then
            echo "O pacote $1 não está instalado no sistema."
            exit 1
         else
            pkg_for_remove="$pack_exist"
            break
         fi
      done

      # Chamando a função REMOVE e passando como parâmetro o nome do pacote.
      REMOVE "$pkg_for_remove"
   ;;
esac

Dê permissão de execução e envie para /usr/sbin, em seguida execute pkgremove -r nano para fazer o teste.

# chmod -v +x pkgremove 
mode of 'pkgremove' retained as 0755 (rwxr-xr-x)

# cp -v pkgremove /usr/sbin/
'pkgremove' -> '/usr/sbin/pkgremove'

# pkgremove -r nano
Delete: /usr/bin/rnano
Delete: /usr/bin/nano
Delete: /usr/share/info/nano.info
Delete: /usr/share/info/dir
Delete: /usr/share/locale/pl/LC_MESSAGES/nano.mo
Delete: /usr/share/nano/texinfo.nanorc
Delete: /usr/share/nano/nanorc.nanorc
Delete: /usr/man/man1/rnano.1
Delete: /usr/man/man5/nanorc.5
Delete empty directories: /usr/share/info/
Delete empty directories: /usr/share/doc/nano/
Delete empty directories: /usr/share/nano/
.........................

Deletando track nano-4.7.track
removed 'nano-4.7.track'

Perfeito! Funcionou tudo corretamente... Para não ficar a lista muito longa o resultado foi cortado! Não temos mais o nano no sistema vamos ter que reinstalar ele, com o nosso gerenciador de pacotes claro :)

# which nano
which: no nano in (/usr/local/sbin:/usr/local/bin:/sbin:/usr/sbin:/bin:/usr/bin)

# cd /tmp/build/

# ls
nano-4.7  nano-4.7.tar.xz

# pkginstall -i nano-4.7.tar.xz 
========> Iniciando instalação do pacote nano-4.7.tar.xz

Removendo caracteres desnecessários do arquivo .track
Pacote: nano-4.7.tar.xz foi instalado com sucesso.

# which nano
/usr/bin/nano

3.5 Todos os scripts prontos

Se você quiser dar uma olhada em todos os programas que criamos createpkg, pkginstall e pkgremove. Eles estão disponiveis no github

4. Considerações finais do nível a1

Vimos que nosso gerenciador de pacotes está funcional, criando pacotes, instando e removendo! Mas não com perfeição... Há muito a ser feito ainda e muitas funcionalidades precisam ser inseridas.

Por exemplo, nosso gerenciador não instala vários pacotes de uma só vez, não instala pacotes de um diretório diferente, somente quando você está no mesmo que o pacote. Também temos a questão de instalar em outro lugar "raiz" utilizado para fazer um jailroot. Outra questão é de por "comandos" em um script de pré e pós instalação do pacote! Tudo é muito estático. No nível a2 vamos fazer estas e mais algumas melhorias. Assim você pode utilizar no seu dia a dia.

Outro detalhe que o código está muito simples e foi feito proposicionalmente! Poderiamos ter feito código mais complexos para agilizar muita coisa, porém a abordagem é direcionado para aprendizagem, o programador menos experiente precisa entender bloco a bloco o que está acontecendo e com comentários para complementar :)
Abordagem continuará assim nesta pegada em outros niveis.


Obrigado pela leitura e bom divertimento