Usando C++ como Shell Script

🗞️ Parece loucura, mas além de um ótimo exercício, melhora muito o desempenho das tarefas.


Usando C++ como Shell Script


Há um tempo atrás eu havia criado um script em Bash(Shell Script que limpava algumas coisas no meu sistema, dentre elas:

  • O cache de imagens que foram visualizadas, incluindo as thumbnails;
  • O cache dos navegadores que eu tinha instalado na minha máquina: Google Chrome, Firefox e Opera;
  • O histórico do Bash;
  • Os arquivos recentes;
  • E a lixeira!

O script funcionava normalmente, bastava eu rodar o comando limpeza no terminal.

No entanto, um belo dia quando rodei esse script, notei que ele demorou muito de executar. O motivo disso foi que na lixeira havia diversos arquivos .iso, somados acho que havia uns 5GB de tamanho!

E então eu pensei perguntando pra mim mesmo:

— Por que o Bash demorou tanto para remover???

E pensei:

— Se eu fizesse esse script em C++, será que demoraria tanto?!

Então, eu decidi fazer e testar! Após terminar de escrever, deletei uns 10GB de .iso(joguei na lixeira) e rodei o código pós-compilado pra testar com o comando time e comparei o resultado do mesmo comando com o do script em Bash.

E a diferença foi GRANDEEEE! Ou seja, o mesmo script feito em C++ foi muito mais rápido!

Bom, isso já faz muito tempo, mas decidi postar aqui, pois esse “script” serve de exercício para quem está treinando seus códigos C++, é uma boa você usar: C++ como Shell Script para aprimorar suas habilidades!

Nos próximos tópicos vou descrever cada linha/trecho do código e ao final haverá todos os arquivos separados para caso você queira compilar e testar na sua máquina.


Escrevendo o código

Como vamos compilar com CMake, o ideal é criar uma pasta para o projeto:

mkdir cleanup
cd cleanup

E então crie esse primeiro arquivo: vim limpeza.hpp:

O cabeçalho local: colors.hpp, pode ser obtido da postagem: Crie sua própria biblioteca de cores para C++. Acesse, copie o código que tem lá e salve com esse mesmo nome.

Vamos adicionar:

#pragma once
#include <iostream>
#include <filesystem>
#include <memory>
#include <array>
#include <fstream>
#include "colors.hpp"

Pra não estender muito o nome do namespace

namespace fs = std::filesystem;

Nome da classe, bem intuitivo! =)

class Limpeza

Caso o usuário queira executar o código sem alterar/remover nada no seu PC, além de exibir o(a)s pastas/arquivos que serão removidos:

bool m_debug;

Armazena os caminhos dinamicamente:

std::string m_path;

Capta a pasta pessoal do usuário:

const std::string m_home = std::getenv("HOME");

Tudo aquilo que iremos limpar, que falei no início:

const fs::path m_trash  = ".local/share/Trash/files";
const fs::path m_thumbs = ".cache/thumbnails";
const fs::path m_chrome = ".cache/google-chrome";
const fs::path m_fox    = ".cache/mozilla";
const fs::path m_opera  = ".cache/opera";
const fs::path m_recent = ".local/share/recently-used.xbel";
const fs::path m_bash_h = ".bash_history";

Essas funções-membro declarei como: protected:

  • Imprime com Unicode e cores:
void print(bool, const std::string&);

Recebe um array opcional com: caminho, mensagem true e mensagem false. Essas duas funções-membro fazem quase a mesma coisa, sendo que uma é para pastas e a outra para arquivos regulares:

bool clean_dir(const std::array<std::string, 3>& = {});
bool clean_file(const std::array<std::string, 3>& = {});

E como public só declarei o construtor e run():

  • Recebe o modo debug como parâmetro:
Limpeza(bool);
void run(); // Para ser chamada na main

Após isso criei a execução do código no arquivo: vim limpeza.cpp:

#include "limpeza.hpp"

C++ não precisa de getters e setters sempre, como em Java. Você já pode(e deve) inicializar os membros com essa sintaxe no construtor. Nesse caso, inicializamos o m_debug e a m_path:

Limpeza::Limpeza(bool debug) : m_debug(debug){
  m_path = {};
}

Essa função-membro chama as outras funções-membro e se o modo debug estiver ativado ela exibe o caminho, para você ter certeza que não deletará nada que não seja os arquivos/pastas especificados!

void Limpeza::run(){
  this->clean_dir({m_trash, "Lixeira esvaziada", "esvaziar lixeira"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_file({m_recent, "Arquivos recentes limpados", "limpar arquivos recentes"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_file({m_bash_h, "Histórico do Bash removido", "limpar histórico do Bash"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_thumbs, "Thumbnails removidas", "remover cache das Thumbnails"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_chrome, "Cache do Chrome limpado", "remover cache do Chrome"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_fox, "Cache do Firefox limpado", "remover cache do Firefox"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_opera, "Cache do Opera limpado", "remover cache do Opera"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}
}

Essa print, organiza a saída para não ficar repetindo sempre:

void Limpeza::print(bool action, const std::string& msg){
  (action) ? 
    std::cout << hey::green + "\u2705 " + msg + " com sucesso!" + hey::off << '\n' :
    std::cerr << hey::red + "\u274C Falha ao " + msg + "." + hey::off << '\n';
}

Remove os diretórios:

  • se existir;
  • se der pra remover;
  • se não estiver vazio;
  • se o debug estiver desabilitado. Tudo de forma segura e com todas as verificações possíveis! E cria um novo(quando for necessário) para agilizar o processo:
bool Limpeza::clean_dir(const std::array<std::string, 3>& arr){
  m_path = m_home + '/' + arr[0];
  if(m_debug){
    this->print(true, arr[1]);
    return true;
  }

  try {
    if (fs::exists(m_path) && fs::is_directory(m_path)) {
      if (fs::is_empty(m_path)) {
        return false;
      }else{
        if(fs::remove_all(m_path)){
          this->print(true, arr[1]);
          fs::create_directory(m_path);
        }else{
          this->print(false, arr[2]);
          return false;
        }
      }
    }else{
      return false;
    }
  }catch (const fs::filesystem_error& e){
    std::cerr << "EXECUTAR ESSA AÇÃO: " << e.what() << '\n';
    return false;
  }
  return true;
}

As mesmas ações da função-membro acima, mas para arquivos regulares e usa a std::ofstream em vez da std::filesystem para recriar os arquivos e não pastas:

bool Limpeza::clean_file(const std::array<std::string, 3>& arr){
  m_path = m_home + '/' + arr[0];
  if(m_debug){
    this->print(true, arr[1]);
    return true;
  }

  try {
    if (fs::exists(m_path) && fs::is_regular_file(m_path)) {
      std::size_t size = std::filesystem::file_size(m_path);
      if(size == 0){
        return false;
      }
      if(fs::remove_all(m_path)){
        this->print(true, arr[1]);
        std::ofstream out(m_path);
        out.close();
      }else{
        this->print(false, arr[2]);
        return false;
      }
    }else{
      return false;
    }
  }catch (const fs::filesystem_error& e){
    std::cerr << "EXECUTAR ESSA AÇÃO: " << e.what() << '\n';
    return false;
  }
  return true;
}

E por final, nosso arquivo: vim main.cpp:

Note que passamos o --debug como parâmetro de forma opcional!

#include "limpeza.hpp"

int main(int argc, char** argv){
  bool debug {false};
  std::string param {};
  if(argc > 1){
    param = argv[1];
    if(param == "--debug"){
      debug = true;
    }else{
      std::cerr << "Use: " << argv[0] << " [--debug]\n";
    }
  }
  auto lp = std::make_unique<Limpeza>(debug);
  lp->run();
  return 0;
}


Todos os arquivos

limpeza.hpp

#pragma once
#include <iostream>
#include <filesystem>
#include <memory>
#include <array>
#include <fstream>
#include "colors.hpp"

namespace fs = std::filesystem;

class Limpeza {

  bool m_debug;
  std::string m_path;

  const std::string m_home = std::getenv("HOME");

  const fs::path m_trash  = ".local/share/Trash/files";
  const fs::path m_thumbs = ".cache/thumbnails";
  const fs::path m_chrome = ".cache/google-chrome";
  const fs::path m_fox    = ".cache/mozilla";
  const fs::path m_opera  = ".cache/opera";
  const fs::path m_recent = ".local/share/recently-used.xbel";
  const fs::path m_bash_h = ".bash_history";

  protected:
    void print(bool, const std::string&);
    bool clean_dir(const std::array<std::string, 3>& = {});
    bool clean_file(const std::array<std::string, 3>& = {});

  public:
    Limpeza(bool);
    void run();
};

limpeza.cpp

#include "limpeza.hpp"

Limpeza::Limpeza(bool debug) : m_debug(debug){
  m_path = {};
}

void Limpeza::run(){
  this->clean_dir({m_trash, "Lixeira esvaziada", "esvaziar lixeira"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_file({m_recent, "Arquivos recentes limpados", "limpar arquivos recentes"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_file({m_bash_h, "Histórico do Bash removido", "limpar histórico do Bash"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_thumbs, "Thumbnails removidas", "remover cache das Thumbnails"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_chrome, "Cache do Chrome limpado", "remover cache do Chrome"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_fox, "Cache do Firefox limpado", "remover cache do Firefox"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}

  this->clean_dir({m_opera, "Cache do Opera limpado", "remover cache do Opera"});
  if(m_debug){ std::cout << hey::yellow << m_path << hey::off << "\n\n";}
}

void Limpeza::print(bool action, const std::string& msg){
  (action) ? 
    std::cout << hey::green + "\u2705 " + msg + " com sucesso!" + hey::off << '\n' :
    std::cerr << hey::red + "\u274C Falha ao " + msg + "." + hey::off << '\n';
}

bool Limpeza::clean_dir(const std::array<std::string, 3>& arr){
  m_path = m_home + '/' + arr[0];
  if(m_debug){
    this->print(true, arr[1]);
    return true;
  }

  try {
    if (fs::exists(m_path) && fs::is_directory(m_path)) {
      if (fs::is_empty(m_path)) {
        return false;
      }else{
        if(fs::remove_all(m_path)){
          this->print(true, arr[1]);
          fs::create_directory(m_path);
        }else{
          this->print(false, arr[2]);
          return false;
        }
      }
    }else{
      return false;
    }
  }catch (const fs::filesystem_error& e){
    std::cerr << "EXECUTAR ESSA AÇÃO: " << e.what() << '\n';
    return false;
  }
  return true;
}

bool Limpeza::clean_file(const std::array<std::string, 3>& arr){
  m_path = m_home + '/' + arr[0];
  if(m_debug){
    this->print(true, arr[1]);
    return true;
  }

  try {
    if (fs::exists(m_path) && fs::is_regular_file(m_path)) {
      std::size_t size = std::filesystem::file_size(m_path);
      if(size == 0){
        return false;
      }
      if(fs::remove_all(m_path)){
        this->print(true, arr[1]);
        std::ofstream out(m_path);
        out.close();
      }else{
        this->print(false, arr[2]);
        return false;
      }
    }else{
      return false;
    }
  }catch (const fs::filesystem_error& e){
    std::cerr << "EXECUTAR ESSA AÇÃO: " << e.what() << '\n';
    return false;
  }
  return true;
}

main.cpp

#include "limpeza.hpp"

int main(int argc, char** argv){
  bool debug {false};
  std::string param {};
  if(argc > 1){
    param = argv[1];
    if(param == "--debug"){
      debug = true;
    }else{
      std::cerr << "Use: " << argv[0] << " [--debug]\n";
    }
  }
  auto lp = std::make_unique<Limpeza>(debug);
  lp->run();
  return 0;
}

Além do arquivo colors.hpp que é da postagem que falei.


Compilando com CMake, rodando e instalando

Para compilar vamos usar esse CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)

project(Limpeza
  LANGUAGES CXX
  VERSION 0.0.1
)

add_compile_options(-g -Wall -Wextra -Wpedantic -pedantic)
if(CHECK_MEM)
  message("Compilando com libasan. Saiba mais: <https://github.com/google/sanitizers/>")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
endif()

set (CMAKE_CXX_STANDARD 23)
add_executable(limpeza main.cpp limpeza.cpp)

Note no CMakeLists.txt que podemos passar o parâmetro opcional: -DCHECK_MEM=ON para o usar a libasan que é a biblioteca: sanitizers do Google, para mais informações veja o vídeo: 10 Dicas de Flags e Parâmetros para GNU GCC.

Se quiser fazer o download de todos aquivos clique no botão abaixo para fazer o download do cleanup.zip

Baixar cleanup.zip

Logo você pode usar o CMake assim(com libasan):

cmake -B build . -DCHECK_MEM=ON

Ou somente: cmake -B build .

Em seguida, compilar e testar com modo debug:

cmake --build build
build/limpeza --debug

Se quiser instalar e usar para fazer suas limpezas, recomendo usar localmente(só para seu usuário):

mkdir -p ~/.local/bin
echo 'export PATH="${PATH}:${HOME}/.local/bin" >> ~/.bashrc'
exec $SHELL
install -v ./build/limpeza ~/.local/bin

Teste de novo para ver tudo que será ou não removido e depois rode definitivamente:

Lembrando que sem ser no modo debug, somente as ações que ele executar haverá saída. Po exemplo, você não tem o Opera instalado, ou já rodou o comando antes, ele não fará nada e nem exibirá!

limpeza

Saída:

Saída comando limpeza feito com C++

Se quiser que o histórico do terminal fique limpo após rodar tudo, adicione o isso ao final do seu ~/.bashrc assim:

limpeza(){
  ${HOME}/.local/bin/limpeza $@
  history -c
}

E releia o ~/.bashrc:

exec $SHELL
# Ou
source ~/.bashrc

Faça assim, pois rodar processos como esse com std::system além de não funcionar não é recomendado!


Futuramente pretendo mostrar outros scripts que eu fiz com C++ e depois organizar todos e pôr em um único repositório no GitHub.

Espero que tenham gostado dessa mini-aventura! 😎


cpp bash shellscript comandos cppdaily


Compartilhe


Nosso canal no Youtube

Inscreva-se


Marcos Oliveira

Marcos Oliveira

Desenvolvedor de software
https://github.com/terroo


Crie Aplicativos Gráficos para Linux e Windows com C++

Aprenda C++ Moderno e crie Games, Programas CLI, GUI e TUI de forma fácil.

Saiba Mais

Receba as novidades no seu e-mail!

Após cadastro e confirmação do e-mail, enviaremos semanalmente resumos e também sempre que houver novidades por aqui para que você mantenha-se atualizado!