c ++ shell untuk linux
#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
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);
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.