UVa12298 3KP-BASH Project
UVa12298 3KP-BASH Project
- 题目链接
- 题意
- 输入格式
- 输出格式
- 分析
- AC 代码
题目链接
UVa12298 3KP-BASH Project
题意
摘自 《算法竞赛入门经典:训练指南》刘汝佳,陈锋著。有删改。
你的任务是为一个假想的 3KP 操作系统编写一个简单的 Bash 模拟器。由于操作系统本身还没有写完,你暂时需要在模拟器的内存中虚拟出一个文件系统,而不是和真实的文件系统交互。下面介绍 3KP 操作系统中的文件系统。
文件(file)是数据存储的最小单位。文件名由英文字母(大小写敏感)、数字和点(.)组成,但不能包含两个连续的点。用户创建的文件名不能是单个的点字符(它代表当前目录)。文件名长度不能超过 255,文件大小不能超过 2 63 2^{63} 263。一个文件可能具有 directory 属性和 hidden 属性。
目录(directory)是大小为 0 、具有 directory 属性的文件,里面可以保存任意数量的目录和文件。空文件系统只有一个文件,叫做“根目录”。在任意时刻,有一个称为“当前目录”的目录。 Bash 启动时,当前目录就是根目录。
指令中引用文件的时候,可以使用绝对路径或者相对路径。绝对路径以字符/开头,如 /home/acm/uva ;相对路径不以字符 / 开头,当前目录和上层目录分别用 . 和 … 表示,如当前目录为 /home/acm/uva,那么相对路径 …/…/…/home/… 就是根目录。 你的 Bash 模拟器需要支持 8 个命令,具体如下表所示。
用法 | 描述 |
---|---|
cd path | 改变当前目录。path 可以是相对路径,也可以是绝对路径。如果目录不存在或名称不合法,输出 path not found 。 |
touch filename [-size] [-h] | 修改文件。文件名之前的部分应当是文件系统中存在的目录,否则输出 path not found 。filename 的最后一部分应该是合法的文件名,否则输出 bad usage 。在正常情况下,同名文件(如果有的话)应当先被删除,然后新建文件。文件的大小由 -size 参数给出(默认为0),参数-h表示创建隐藏文件。如果存在同名的目录,输出 a dretory with the same name exists 。用户不会指定多个 -size 参数。 |
mkdir path [-h] | 创建目录。文件名之前的部分应当是文件系统中存在的目录,否则输出 path not found。path 的最后一部分应该是合法的文件名,否则输出 bad usage。参数 -h 表示创建隐藏目录。如果存在一个同名目录或文件,输出 file or directory with the same name exists 。 |
find filename [-r] [-h] | 查找目录或文件。filename的前部应当是文件系统中存在的目录,否则输出 path not found。filename 的最后一部分应该是合法的文件名(否则按找不到处理),表示查找的目录或者文件的名称。-r 参数表示当前目录下的所有子目录也要查找。默认情况下,find 命令在显示结果时会忽略掉隐藏文件(但会在隐藏目录中进行查找), 如果加了 -h 参数,则会连隐藏文件一起显示。如果没有一个需要输出的文件,输出 file not found 。否则对于找到的每个文件,输出单独的一行,依次包含带绝对路径的文件名、文件大小,以及 hidden (如果有隐藏属性)、 dir (如果是目录)。这些文件应当按照带绝对路径的文件名的字典序从小到大输出。 |
ls [path] [-h] [-r] [-s] [-S] [-f] [-d] | 没有参数的情况下,Is 命令列出当前目录的所有非隐藏文件(格式同find)。如果没有一个需要输出的内容,输出 [empty]。如果path是文件系统中存在的目录,则列出该目录(而非当前目录)中的文件。如果指定了 path 參数但 path 不是一个合法的路径,则输出 path not found 。 -h 表示要列出隐藏文件(默认不显示),-r 表示递归列出所有子目录的文件(即使未指定 -h 也需要进入隐藏目录递归),-s 表示按照文件大小的非降序排列(文件大小相同的情况仍按照带绝对路径的文件名的字典序排列),-S 表示按照文件大小的非升序排列(如文件大小相同则参见 -s),-d 表示只列出目录,-f 表示只列出非目录。用户不会同时指定 -s 和 -S 。 |
pwd | 输出当前目录的绝对路径 |
exit | 退出 Bash |
grep “string” | 本题中,grep 只能通过管道的形式调用,即 command1 | grep"string",表示先执行 command1,然后在命令输出结果中搜索字符串“string”,按照原顺序输出所有包含这个字符串的行。比如,前一个命令的输出是 path not found,那么 grep"ot fou"就应该也输出 path not found。用户输入的 string 参数保证在一对引号内,且内部不含引号,也没有转义符 |
命令行包含一条或多条命令,用管道符号 | 隔开(管道符号的前后不一定有空格)。第一条命令必须是除了 grep 之外的上述命令之一,而剩下的命令必须是 grep。如果违反上述规定,应输出 bad usage,并且不执行任何命令。输入不会在除了 grep 之外的其他命令中使用引号(原题未写,但书中是这么说的)。
命令和参数、参数和参数之间用一个或多个空格隔开。每个命令的必选参数(如果有的话)不一定是第一个参数,可选参数可能在必选参数之前,且以任意顺序出现。如果用户忽略了必选参数,或传入了过多必选参数,应输出 bad usage 。但忽略传入的无效可选参数(如 mkdir a -h -h -h -h -abababab 等价于 mkdir a -h)。
输入格式
输入包含多组数据,每组数据对应一次独立的 BASH 会话。数据的每行为一行命令(长度不超过 2048 个字符)。命令的参数之间可以有任意数量的空白字符。每次会话的最后一条命令保证为exit。输入结束标志为文件结束符(EOF)。假定每次会话开始时,文件系统为空,当前目录为根目录。
输出格式
依次输出每行命令的输出(如果有的话)。
分析
纯模拟题,很难 AC,洛谷题解上 ExplodingKonjac 说他耗时 346 天通过此题。贴上 ExplodingKonjac 的补充说明:
文件名中只能包含大小写英文字母、数字、点号,且不可以是单个点号,也不能包含两个连续点号,长度不能超过 255。这句在题面中有,但是容易被忽视。执行 touch 和 mkdir 时文件或目录名违反上述规定要输出 bad usage(但如果文件文件或目录名之前的路径如果不存在要输出 path not found)。
文件大小不能超过 2 63 2^{63} 263,违反该规定应当输出 bad usage。实测数据没有超过 2 63 2^{63} 263的,不过读取-size参数应该注意像 -100ch 这种要当数字提取成100,而 -ch100这种不能当数字。
文件路径中两个斜杠之间可能是空的,这种东西表示当前路径。比如路径 .///././// 和路径 . 表示同一种东西。
一个命令行中有由管道符号隔开的多个命令时,应该首先判断第一个命令不是 grep(是 grep 则直接输出 bad usage)才执行第一个命令。之后的命令必须都是 grep,否则直接输出 bad usage 而不执行第二个命令及以后的所有命令。 比如 mkdir a | tt | grep "cc"会先执行 mkdir a,然后输出 bad usage。
管道符号和空格可能被引号所包含,你不能根据其分隔参数或命令。比如说,grep “ot fou” 不能被分隔成 grep、“ot、fou”。
ls 命令中可能同时包含 -d 和 -f 参数,此时应该输出 [empty]。但是数据中不会同时包含 -s 和 -S。
AC 代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;#define ULL unsigned long longconst string no_such_command = "no such command", bad_usage = "bad usage", path_not_found = "path not found",file_not_found = "file not found", dir_exists = "a directory with the same name exists",f_dir_exists = "file or directory with the same name exists", empty_l = "[empty]";
struct params {ULL size = 0; bool r = false, h = false, s = false, S = false, f = false, d = false;};
struct file {string name, full_path; int parent; ULL size; bool dir, hidden; vector<int> sub;file(const string& name, const string& full_path, int parent, ULL size = 0, bool dir = false, bool hidden = false):name(name), full_path(full_path), parent(parent), size(size), dir(dir), hidden(hidden) {};
};
vector<file> fs; int curr_dir;void trim(string& s) {int l, r, t = s.size();for(l=0; l<t; ++l) if(!isspace(s[l])) break;for(r=t-1; r>l; --r) if(!isspace(s[r])) break;s = s.substr(l, r-l+1);
}bool valid_path(const string& s, int& dir, string& name, bool pre = true) {vector<string> p; dir = s[0] == '/' ? 0 : curr_dir;if (!s.empty() && s.back() == '/') pre = false;for (int i=0, h=0, t=s.size(); i<t; ++i) {if (s[i] == '/') {if (i > 0 && s[i-1] != '/') p.push_back(s.substr(h, i-h));h = i+1;} else if (i+1 == t) p.push_back(s.substr(h, t-h));}for (int i=0, t=p.size()-pre; i<t; ++i) {if (p[i] == ".") continue;if (p[i] == "..") {if (fs[dir].parent < 0) return false;dir = fs[dir].parent;} else {const vector<int>& sub = fs[dir].sub; bool ok = false;for (int j=sub.size()-1; j>=0; --j) if (fs[sub[j]].name == p[i]) {if (fs[sub[j]].dir) dir = sub[j], ok = true;break;}if (!ok) return false;}}name = p.empty() || s.back() == '/' ? "" : p.back();return true;
}bool valid_name(const string& s) {if (s.empty() || s.size() > 255) return false;for (int i=0, t=s.size(); i<t; ++i)if ((s[i]=='.' && (t==1 || (s[i+1]=='.'))) || (s[i]!='.' && !isalpha(s[i]) && !isdigit(s[i]))) return false;return true;
}bool cmp_p(int i, int j) {return fs[i].full_path < fs[j].full_path;
}bool cmp_s(int i, int j) {return fs[i].size < fs[j].size || (fs[i].size == fs[j].size && fs[i].full_path < fs[j].full_path);
}bool cmp_S(int i, int j) {return fs[i].size > fs[j].size || (fs[i].size == fs[j].size && fs[i].full_path < fs[j].full_path);
}void visit_fs(int dir, vector<int>& out, const string& name, bool r, bool h, bool d = true, bool f = true) {for (int i=0, t=fs[dir].sub.size(); i<t; ++i) {const file& fi = fs[fs[dir].sub[i]];if ((!fi.hidden || h) && ((fi.dir && d) || (!fi.dir && f)) && (name.empty() || fi.name == name))out.push_back(fs[dir].sub[i]);if (fi.dir && r) visit_fs(fs[dir].sub[i], out, name, r, h, d, f);}
}string info(const file& f) {ostringstream ss; ss << f.full_path << ' ' << f.size;if (f.hidden) ss << " hidden";if (f.dir) ss << " dir";return ss.str();
}void new_session() {fs.clear(); fs.push_back(file("", "/", -1, 0, true)); curr_dir = 0;
}void cd(const string& mp, vector<string>& out) {int dir; string name;if (!valid_path(mp, dir, name, false)) return out.push_back(path_not_found);curr_dir = dir;
}void touch(const string& mp, const params& p, vector<string>& out) {int dir; string name;if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);if (!valid_name(name)) return out.push_back(bad_usage);for (int i=fs[dir].sub.size()-1; i>=0; --i) if (fs[fs[dir].sub[i]].name == name) {if (fs[fs[dir].sub[i]].dir) return out.push_back(dir_exists);fs[fs[dir].sub[i]].size = p.size; fs[fs[dir].sub[i]].hidden = p.h;return;}file fi(name, (dir ? fs[dir].full_path + '/' : "/") + name, dir, p.size, false, p.h);int s = fs.size(); fs[dir].sub.push_back(s); fs.push_back(fi);
}void mkdir(const string& mp, const params& p, vector<string>& out) {int dir; string name;if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);if (!valid_name(name)) return out.push_back(bad_usage);for (int i=fs[dir].sub.size()-1; i>=0; --i) if (fs[fs[dir].sub[i]].name == name)return out.push_back(f_dir_exists);file fi(name, (dir ? fs[dir].full_path + '/' : "/") + name, dir, 0, true, p.h);int s = fs.size(); fs[dir].sub.push_back(s); fs.push_back(fi);
}void find(const string& mp, const params& p, vector<string>& out) {int dir; string name;if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);vector<int> res;visit_fs(dir, res, name, p.r, p.h);if (res.empty()) return out.push_back(file_not_found);sort(res.begin(), res.end(), cmp_p);for (int i=0, t=res.size(); i<t; ++i) out.push_back(info(fs[res[i]]));
}void ls(const string& mp, const params& p, vector<string>& out) {int dir; string name;if (!valid_path(mp, dir, name, false)) return out.push_back(path_not_found);vector<int> res;visit_fs(dir, res, "", p.r, p.h, !p.f, !p.d);if (res.empty()) return out.push_back(empty_l);sort(res.begin(), res.end(), p.s ? cmp_s : (p.S ? cmp_S : cmp_p));for (int i=0, t=res.size(); i<t; ++i) out.push_back(info(fs[res[i]]));
}void run_command(const string& cmd, vector<string>& out) {if (cmd.empty()) return;istringstream ss(cmd); string s; ss >> s;if (s != "cd" && s != "touch" && s!= "mkdir" && s != "find" && s != "ls" && s != "pwd" && s != "exit" && s != "grep") {out.push_back(no_such_command);} else {string mp; params p; ULL v; string s1; ss >> s1;while (!s1.empty()) {if (s1[0] == '-') {if (isalpha(s1[1])) {if (s1[1] == 'r') p.r = true;else if (s1[1] == 'h') p.h = true;else if (s1[1] == 'd') p.d = true;else if (s1[1] == 'f') p.f = true;else if (s1[1] == 's') p.s = true;else if (s1[1] == 'S') p.S = true;} else if (istringstream(s1.substr(1)) >> v) p.size = v;else return out.push_back(bad_usage);} else if (!mp.empty()) return out.push_back(bad_usage);else mp = s1;s1.clear(); ss >> s1;}if (s == "cd") mp.empty() ? out.push_back(bad_usage) : cd(mp, out);else if (s == "touch") mp.empty() ? out.push_back(bad_usage) : touch(mp, p, out);else if (s == "mkdir") mp.empty() ? out.push_back(bad_usage) : mkdir(mp, p, out);else if (s == "find") mp.empty() ? out.push_back(bad_usage) : find(mp, p, out);else if (s == "ls") ls(mp, p, out);else if (s == "pwd") out.push_back(mp.empty() ? fs[curr_dir].full_path : bad_usage);else if (s == "grep" || (s == "exit" && !mp.empty())) out.push_back(bad_usage);else new_session();}
}void run_cmdline(const string& line) {vector<string> cmds, out;for (int i=0, t=line.size(), s=0, q=0; i<=t; ++i) if (i==t || (line[i] == '|' && !q)) {cmds.push_back(string(line.begin()+s, line.begin()+i)); s = i+1;} else if (line[i] == '"') q ^= 1;if (cmds.empty()) return;run_command(cmds[0], out);for (int i=1, t=cmds.size(); i<t; ++i) {istringstream ss(cmds[i]); string s, p; ss >> s; getline(ss, p); trim(p);if (s != "grep" || p.size() < 2 || p[0] != '"' || p.back() != '"') {cout << bad_usage << endl;return;}if (out.empty() || p.size()==2) continue;vector<string> filtered; s = p.substr(1, p.size()-2);for (int j=0, k=out.size(); j<k; ++j) if (out[j].find(s) != string::npos) filtered.push_back(out[j]);out.swap(filtered);}for (int i=0, t=out.size(); i<t; ++i) cout << out[i] << endl;
}int main() {string line; new_session();while (getline(cin, line)) run_cmdline(line);return 0;
}