c ++ shell untuk linux

Aug 29 2020
#include <cstring>
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <filesystem>
#include <errno.h>
#include <bits/stdc++.h>
std::string USERDIR = getenv("HOME");
std::string ALIASFILE = USERDIR+"/shell/.alias";
std::vector<std::string> Split(std::string input, char delim);
void Execute(const char *command, char *arglist[]);
std::map<std::string, std::string> alias(std::string file);
bool BuiltInCom(const char *command, char *arglist[],int arglist_size);
char** conv(std::vector<std::string> source);
bool createAlias(std::string first, std::string sec);
std::string replaceAll(std::string data, std::map <std::string, std::string> dict);
int main() {
  while (1) {
    char path[100];
    getcwd(path, 100);
    char prompt[110] = "$[";
    strcat(prompt, path);
    strcat(prompt,"]: ");
    std::cout << prompt;
    // Takes input and splits it by space
    std::string input;
    getline(std::cin, input);
    if(input == "") continue;
    std::map<std::string, std::string> aliasDict = alias(ALIASFILE);
    input = replaceAll(input, aliasDict);
    std::vector<std::string> parsed_string = Split(input, ' ');
    // Splits parsed_string into command and arglist
    const char * com = parsed_string.front().c_str();
    char ** arglist = conv(parsed_string);
    // Checks if it is a built in command and if not, execute it
    if(BuiltInCom(com, arglist, parsed_string.size()) == 0){
        Execute(com, arglist);
    }
    delete[] arglist;
  }
}

std::vector<std::string> Split(std::string input, char delim) {
  std::vector<std::string> ret;
  std::istringstream f(input);
  std::string s;
  while (getline(f, s, delim)) {
    ret.push_back(s);
  }
  return ret;
}

void Execute(const char *command, char *arglist[]) {
  pid_t pid;
  //Creates a new proccess
  if ((pid = fork()) < 0) {
    std::cout << "Error: Cannot create new process" << std::endl;
    exit(-1);
  } else if (pid == 0) {
    //Executes the command
    if (execvp(command, arglist) < 0) {
      std::cout << "Could not execute command" << std::endl;
      exit(-1);
    } else {
      sleep(2);
    }
  }
  //Waits for command to finish
  if (waitpid(pid, NULL, 0) != pid) {
    std::cout << "Error: waitpid()";
    exit(-1);
  }
}

bool BuiltInCom(const char *command, char ** arglist, int arglist_size){
  if(strcmp(command, "quit") == 0){
    delete[] arglist;
    exit(0);
  } else if(strcmp(command, "cd") == 0){
    if(chdir(arglist[1]) < 0){
      switch(errno){
        case EACCES:
          std::cout << "Search permission denied." << std::endl;
          break;
        case EFAULT:
          std::cout << "Path points outside accesable adress space" << std::endl;
          break;
        case EIO:
          std::cout << "IO error" << std::endl;
          break;
        case ELOOP:
          std::cout << "Too many symbolic loops" << std::endl;
          break;
        case ENAMETOOLONG:
          std::cout << "Path is too long" << std::endl;
          break;
        case ENOENT:
          std::cout << "Path doesn't exist" << std::endl;
          break;
        case ENOTDIR:
          std::cout << "Path isn't a dir" << std::endl;
          break;

        default:
            std::cout << "Unknown error" << std::endl;
            break;
      }
      return 1;
    }
    return 1;
  } else if(strcmp(command, "alias") == 0){
    if(arglist_size < 2){
      std::cout << "[USAGE] Alias originalName:substituteName" << std::endl;
      return 1;
    }
    std::string strArg(arglist[1]);
    int numOfSpaces = std::count(strArg.begin(), strArg.end(), ':');
    if(numOfSpaces){
      std::vector<std::string> aliasPair = Split(strArg, ':');
      createAlias(aliasPair.at(0), aliasPair.at(1));
      return 1;
    } else {
      std::cout << "[USAGE] Alias originalName:substituteName" << std::endl;
      return 1;
    }
  }
  return 0;
}

char** conv(std::vector<std::string> source){
  char ** dest = new char*[source.size() + 1];
  for(int i = 0; i < source.size(); i++) dest[i] = (char *)source.at(i).c_str();
  dest[source.size()] = NULL;
  return dest;
}


std::map<std::string, std::string> alias(std::string file){
  std::map<std::string, std::string> aliasPair;
  std::string line;
  std::ifstream aliasFile;
  aliasFile.open(file);
  if(aliasFile.is_open()){
    while(getline(aliasFile, line)){
      auto pair = Split(line, ':');
      aliasPair.insert(std::make_pair(pair.at(0), pair.at(1)));
    }
  } else {
    std::cout << "Error: Cannot open alias file\n";
  }
  return aliasPair;
}
std::string replaceAll(std::string data, std::map <std::string, std::string> dict){
  for(std::pair <std::string, std::string> entry : dict){
      size_t start_pos = data.find(entry.first);
      while(start_pos != std::string::npos){
        data.replace(start_pos, entry.first.length(),entry.second);
        start_pos = data.find(entry.first, start_pos + entry.second.size());
      }

  }
  return data;
}
bool createAlias(std::string first, std::string second){
    std::ofstream aliasFile;
    aliasFile.open(ALIASFILE, std::ios_base::app);
    if(aliasFile.is_open()){
      aliasFile << first << ":"<< second << std::endl;
      return true;
    } else return false;

}

Saya memiliki shell yang telah saya kodekan di c ++ pada distro Fedora Linux. Saya akan menyambut peningkatan umum tentang cara membuat kode lebih baik, tetapi saya secara khusus menyambut komentar tentang keterbacaan kode

Jawaban

5 πάνταῥεῖ Aug 29 2020 at 01:18

Ada beberapa peningkatan yang dapat Anda lakukan untuk kode ini hanya dengan menggunakan c ++ class dan fungsi library standar.

1. Jangan gunakan #include <bits/stdc++.h>

Tidak ada jaminan bahwa file header ini ada, dan merupakan internal khusus kompilator. Menggunakannya akan membuat kode Anda kurang portabel.
Hanya #includeheader yang disediakan untuk kelas dan fungsi yang ingin Anda gunakan dari pustaka standar c ++.
Anda dapat membaca lebih lanjut tentang kemungkinan konsekuensi dan masalah di sini: Mengapa saya tidak #include ?

Selain itu, jangan #includetajuk file di mana Anda tidak menggunakan apa pun darinya (misalnya #include <filesystem>).

2. Jangan gunakan fungsi perpustakaan c untuk manipulasi string

Misalnya, kode Anda untuk membuat promptvariabel dapat disederhanakan secara drastis hanya dengan menggunakan, std::stringbukan char*:

char path[100];
getcwd(path,100);
std::string prompt = "$[" + std::string(path) + "]:";

Anda juga bisa langsung menulis

if(command == "quit"){

seharusnya Anda gunakan const std::string&sebagai tipe untuk commandparameter.

3. Anda tidak perlu mengalokasikan array char*variabel untuk meneruskannya ke execxy()fungsi

Baru saja membuat std::vector<const char*>alih - alih conv()fungsi Anda :

void Execute(const std::string& command, const std::vector<std::string>& args) {
  std::vector<const char*> cargs;
  pid_t pid;

  for(auto sarg : args) {
      cargs.append(sarg.data());
  }
  cargs.append(nullptr);

  //Creates a new proccess
  if ((pid = fork()) < 0) {
    std::cout << "Error: Cannot create new process" << std::endl;
    exit(-1);
  } else if (pid == 0) {
    //Executes the command
    if (execvp(command.data(), cargs.data()) < 0) {
      std::cout << "Could not execute command" << std::endl;
      exit(-1);
    } else {
      sleep(2);
    }
  }
  //Waits for command to finish
  if (waitpid(pid, NULL, 0) != pid) {
    std::cout << "Error: waitpid()";
    exit(-1);
  }
}

Dalam kasus seperti itu di mana Anda menggunakan pointer data mentah yang diperoleh misalnya std::string::data(), pastikan bahwa masa pakai variabel yang mendasari bertahan selama penggunaannya di misalnya fungsi pustaka C.

Sebagai pedoman umum:
Hindari melakukan manajemen memori sendiri menggunakan newdan deletesecara eksplisit. Lebih baik gunakan wadah standar c ++ atau setidaknya penunjuk cerdas .

4. Anda tidak perlu perbandingan eksplisit untuk boolnilai - nilai

Perubahan

if(BuiltInCom(com, arglist, parsed_string.size()) == 0){

untuk

if(!BuiltInCom(com, arglist, parsed_string.size())){

Juga gunakan falsedan truesebagai ganti konversi implisit dari int 0dan 1literal.

5. Gunakan constdan berikan referensi untuk parameter bila memungkinkan

Gunakan constjika Anda tidak perlu mengubah parameter.
Gunakan pass by reference ( &) jika Anda ingin menghindari salinan yang tidak perlu dibuat untuk jenis non-sepele.

Anda dapat melihat caranya dalam contoh yang Execute()saya berikan di atas.

Sama halnya misalnya

std::string replaceAll(std::string data, std::map <std::string, std::string> dict);

ini seharusnya

std::string& replaceAll(std::string& data, const std::map <std::string, std::string>& dict);
4 MartinYork Aug 29 2020 at 01:48

Pemformatan.

Ini adalah satu dinding teks yang besar. Anda perlu membagi hal-hal menjadi beberapa bagian logis agar lebih mudah dibaca. Tambahkan beberapa spasi vertikal di antara bagian agar lebih mudah dibaca.


Anda memiliki banyak #include. Sangat menyenangkan untuk memesannya. Anda bisa memilih cara apapun untuk memesannya asalkan logis dan memudahkan orang untuk melihatnya.

Saya melakukan yang paling spesifik untuk yang paling umum.

 #include "HeaderFileForThisSource.h"
 #include "HeaderFileForOtherClassesInThisProject"
 ...
 #include <C++ Librries>
 ...
 #include <C Librries>
 ...
 #include <Standard C++ Header Files>
 ..
 #include <C standard Libraries>
 ...

Yang lain mendaftarnya menurut abjad.

Tidak yakin apa yang terbaik tetapi beberapa logika untuk pemesanan akan menyenangkan.


Ini sangat sulit untuk dibaca. Saya tidak bisa melihat nama fungsi di lautan teks.

std::vector<std::string> Split(std::string input, char delim);
void Execute(const char *command, char *arglist[]);
std::map<std::string, std::string> alias(std::string file);
bool BuiltInCom(const char *command, char *arglist[],int arglist_size);
char** conv(std::vector<std::string> source);
bool createAlias(std::string first, std::string sec);
std::string replaceAll(std::string data, std::map <std::string, std::string> dict);

Dengan beberapa penggunaan yang bijaksana dan beberapa perapihan usingAnda dapat membuatnya sangat mudah digunakan.

using  Store = std::vector<std::string>;
using  Map   = std::map<std::string, std::string>;
using  CPPtr = char**;

Store       Split(std::string input, char delim);
void        Execute(const char *command, char *arglist[]);
Map         alias(std::string file);
bool        BuiltInCom(const char *command, char *arglist[],int arglist_size);
CPPtr       conv(std::vector<std::string> source);
bool        createAlias(std::string first, std::string sec);
std::string replaceAll(std::string data, std::map <std::string, std::string> dict);

Kode

"Variabel" global adalah ide yang buruk.

std::string USERDIR = getenv("HOME");
std::string ALIASFILE = USERDIR+"/shell/.alias";

Siapkan ini main(). Anda kemudian dapat meneruskan ini sebagai parameter atau menambahkannya ke objek.

Anda dapat memiliki status statis yang tidak dapat diubah dalam cakupan global. Ini untuk hal-hal seperti konstanta.


Buat agar mudah dibaca.

  while (1) {

Ini akan lebih baik karena:

  while(true) {

Jangan gunakan buffer ukuran tetap di mana pengguna dapat memasukkan string dengan panjang arbitrer. C ++ memiliki kemampuan std::stringuntuk menangani situasi seperti ini.

    char path[100];
    getcwd(path, 100);

    // Rather
    std::string  path = std::filesystem::current_path().string();

Jangan gunakan angka ajaib dalam kode Anda:

    char prompt[110] = "$[";

Mengapa 110? Masukkan angka ajaib ke dalam konstanta bernama

    // Near the top of the programe with all other constants.
    // Then you can tune your program without having to search for the constants.
    static std::size_t constepxr bufferSize = 110;

    .....
    char buffer[bufferSize];

Harus menggunakan std :: string di sini

    strcat(prompt, path);
    strcat(prompt,"]: ");

Fungsi string C lama tidak aman.