Linux 용 C ++ 쉘
#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;
}
Fedora Linux 배포판에서 C ++로 코딩 한 셸이 있습니다. 코드를 더 좋게 만드는 방법에 대한 일반적인 개선을 환영하지만 특히 코드의 가독성에 대한 의견은 환영합니다.
답변
C ++ 표준 라이브러리 클래스 및 함수 만 사용하여이 코드에 대해 수행 할 수있는 몇 가지 개선 사항이 있습니다.
1. 사용하지 마십시오 #include <bits/stdc++.h>
이 헤더 파일이 존재한다는 보장은 없으며 컴파일러 고유의 내부 파일입니다. 이를 사용하면 코드의 이식성이 떨어집니다. C ++ 표준 라이브러리에서 사용하려는 클래스 및 함수에 대해 제공되는 헤더
만 #include있습니다.
가능한 결과 및 문제에 대한 자세한 내용은 여기에서 읽을 수 있습니다. #include
또한 #include아무것도 사용 하지 않는 헤더 파일 (예 :)도 사용하지 마십시오 #include <filesystem>.
2. 문자열 조작에 c 라이브러리 함수를 사용하지 마십시오.
예를 들어 prompt변수를 작성하는 코드 std::string는 char*다음 대신 사용하여 대폭 단순화 할 수 있습니다 .
char path[100];
getcwd(path,100);
std::string prompt = "$[" + std::string(path) + "]:";
또한 간단히 쓸 수 있습니다.
if(command == "quit"){
매개 변수의 const std::string&유형으로 사용한다고 가정합니다 command.
3. 함수 char*에 전달하기 위해 변수 배열을 할당 할 필요가 없습니다.execxy()
함수 std::vector<const char*>대신 빌드했습니다 conv().
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);
}
}
eg std::string::data()에서 얻은 원시 데이터 포인터를 사용하는 경우 기본 변수의 수명이 예를 들어 C 라이브러리 함수에서 사용되는 동안 지속되는지 확인하십시오.
엄지 손가락의 일반적으로 :
피 자신을 사용하여 메모리 관리 일을 new하고 delete명시 적으로합니다. 오히려 C ++ 표준 컨테이너 또는 적어도 스마트 포인터를 사용하십시오 .
4. bool값에 대한 명시적인 비교가 필요하지 않습니다.
변화
if(BuiltInCom(com, arglist, parsed_string.size()) == 0){
...에
if(!BuiltInCom(com, arglist, parsed_string.size())){
또한 사용 false하고 true대신 암시에서 변환의 int 0와 1리터럴.
5. const가능한 한 매개 변수에 대한 참조로 사용 및 전달
const매개 변수를 변경할 필요가없는 경우 사용하십시오 . 사소하지 않은 유형에 대해 불필요한 사본을 작성하지 않으려면
참조로 전달 ( &)을 사용하십시오 .
Execute()위 의 예에서 방법을 볼 수 있습니다 .
예를 들어도 마찬가지입니다.
std::string replaceAll(std::string data, std::map <std::string, std::string> dict);
이것은되어야한다
std::string& replaceAll(std::string& data, const std::map <std::string, std::string>& dict);
포맷.
이것은 하나의 큰 텍스트 벽입니다. 더 쉽게 읽을 수 있도록 논리적 섹션으로 분할해야합니다. 쉽게 읽을 수 있도록 섹션 사이에 약간의 수직 공간을 추가하십시오.
#include가 많이 있습니다. 그들을 주문하는 것이 좋습니다. 논리적이고 사람들이 쉽게 살펴볼 수 있도록 주문하는 방법을 선택할 수 있습니다.
나는 가장 일반적으로 가장 구체적입니다.
#include "HeaderFileForThisSource.h"
#include "HeaderFileForOtherClassesInThisProject"
...
#include <C++ Librries>
...
#include <C Librries>
...
#include <Standard C++ Header Files>
..
#include <C standard Libraries>
...
다른 사람들은 알파벳순으로 나열합니다.
무엇이 최선인지 확실하지 않지만 주문에 대한 논리가 좋을 것입니다.
읽기가 정말 어렵습니다. 텍스트의 바다에서 함수 이름을 볼 수 없습니다.
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);
신중하게 사용 using하고 정리하면 정말 사용하기 쉽게 만들 수 있습니다.
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);
암호
전역 "변수"는 나쁜 생각입니다.
std::string USERDIR = getenv("HOME");
std::string ALIASFILE = USERDIR+"/shell/.alias";
에서 설정하십시오 main(). 그런 다음이 매개 변수를 매개 변수로 전달하거나 객체에 추가 할 수 있습니다.
전역 범위에서 정적 불변 상태를 가질 수 있습니다. 이것은 상수와 같은 것을위한 것입니다.
읽기 쉽게 만드십시오.
while (1) {
이것은 다음과 같이 더 좋습니다.
while(true) {
사용자가 임의의 길이 문자열을 입력 할 수있는 고정 크기 버퍼를 사용하지 마십시오. C ++는 std::string이런 상황을 처리해야합니다.
char path[100];
getcwd(path, 100);
// Rather
std::string path = std::filesystem::current_path().string();
코드에 매직 넘버를 사용하지 마십시오.
char prompt[110] = "$[";
왜 110? 매직 넘버를 명명 된 상수에 넣으십시오.
// 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];
여기에 std :: string을 사용해야합니다.
strcat(prompt, path);
strcat(prompt,"]: ");
이전 C 문자열 함수는 안전하지 않습니다.