🧩 项目介绍
该项目使用 Node.js 实现了一个模拟的 Linux 终端环境,支持多种常见的 Linux 命令(如 ls
, cd
, cat
, mkdir
, rm
等),所有文件操作都在内存中进行,并持久化到本地文件系统中。适合用于学习 Shell 命令实现原理、文件系统结构或作为教学演示工具。
📦 依赖安装
确保你已安装 Node.js(建议版本 14+),然后运行以下命令安装依赖:
npm install
🚀 启动项目
在项目根目录下运行:
node index.js
你会看到命令提示符:
simu-shell:~$ _
此时你可以输入 Linux 命令进行操作。
📚 支持命令列表
以下是你可以在模拟终端中使用的命令及其基本用法说明:
命令 | 用法示例 | 功能说明 |
---|---|---|
help |
help |
显示所有可用命令 |
exit |
exit |
退出模拟终端 |
clear |
clear |
清空终端屏幕 |
history |
history |
查看历史命令 |
pwd |
pwd |
显示当前路径 |
ls |
ls |
列出当前目录下的文件 |
ll |
ll |
显示当前目录下的详细文件信息(带类型、大小、修改时间等) |
cd |
cd /home/user |
切换目录 |
mkdir |
mkdir newdir |
创建目录 |
rmdir |
rmdir emptydir |
删除空目录 |
rm |
rm file.txt rm -r dir |
删除文件或目录(递归) |
touch |
touch newfile.txt |
创建空文件 |
echo |
echo "Hello" > file.txt |
将字符串写入文件 |
cat |
cat file.txt |
查看文件内容 |
cp |
cp src.txt dest.txt |
复制文件或目录 |
mv |
mv oldname.txt newname.txt |
移动或重命名文件/目录 |
head |
head file.txt head -n 5 file.txt |
查看文件前几行 |
tail |
tail file.txt tail -n 5 file.txt |
查看文件最后几行 |
grep |
grep "hello" file.txt |
在文件中查找字符串 |
find |
find file.txt |
查找文件 |
stat |
stat file.txt |
显示文件或目录的详细信息 |
vim |
vim file.txt |
编辑文件 |
yum |
yum install https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.26-winx64.zip |
下载文件 |
zip |
zip -r archive.zip test.txt |
压缩文件 |
unzip |
unzip archive.zip -d unzip_target |
解压文件 |
rz |
rz a.txt |
上传文件 |
sz |
sz a.txt |
下载文件 |
💡 示例操作流程
你可以在模拟终端中依次执行以下命令来测试功能:
help
ls
mkdir test
cd test
touch file.txt
echo "Hello World" > file.txt
cat file.txt
cp file.txt copy.txt
ls
mv copy.txt renamed.txt
cat renamed.txt
rm renamed.txt
ls
cd ..
rm -r test
🧪 测试脚本
项目中提供了 test.md
文件,里面包含了完整的测试命令集,建议你在终端中逐步运行测试命令以验证所有功能是否正常。
🧱 数据持久化机制
项目使用了内存中的虚拟文件系统(VFS),并通过以下方式持久化:
- 文件内容保存在
storage/files/
目录下,使用 UUID 命名; - 文件系统结构保存在
vfs_data.json
中,每次操作后会自动保存。
📁 项目结构说明
nodejs模拟Linux环境/
├── index.js # 主程序入口
├── shell.js # 命令处理核心逻辑
├── commands/ # 各命令的实现
├── vfs.js # 虚拟文件系统核心
├── storage.js # 文件系统结构的持久化
├── fileStorage.js # 文件内容的持久化
├── utils.js # 工具函数(如引号解析)
├── test.md # 测试命令列表
├── README.md # 本文件
└── package.json # 项目依赖配置
✅ 项目特点
- 🧠 使用纯 Node.js 实现,无需依赖外部库(除
uuid
); - 💾 支持数据持久化,重启后可恢复文件系统状态;
- 📚 支持大多数常见 Linux 命令;
- 🛠️ 结构清晰,便于扩展新命令或修改现有逻辑;
- 🧪 提供完整测试用例,方便验证功能。
📎 扩展建议
你可以根据需要扩展以下功能:
- 添加新的命令(如
chmod
,chmod
,grep -r
); - 支持管道(
|
)和重定向(>>
,<
); - 支持用户权限管理;
- 添加命令自动补全;
- 添加图形化界面(Electron);
- 支持多用户系统。
源码下载
核心代码
tool/index.js
// index.js
const readline = require('readline');
const { processCommand } = require('./shell');
const { loadHistory, saveHistory } = require('./historyStorage');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'simu-shell:~$ ',
});
// 启动时清屏
process.stdout.write('\x1B[2J\x1B[0f');
rl.history = loadHistory();
rl.historySize = 100;
rl.prompt();
rl.on('line', (line) => {
const trimmed = line.trim();
processCommand(trimmed, rl, () => {
rl.prompt();
});
}).on('close', () => {
saveHistory(rl.history);
console.log('\n退出模拟终端');
process.exit(0);
});
tool/vfs.js
// vfs.js
const path = require('path');
const fs = require('./storage').load({
'/': {
type: 'dir',
children: {
home: {
type: 'dir',
children: {
user: {
type: 'dir',
children: {
'file.txt': { type: 'file', content: 'Hello World' },
'notes.md': { type: 'file', content: '# My Notes' }
}
}
}
},
bin: {
type: 'dir',
children: {}
}
}
}
});
const storage = require('./storage');
// 每次修改后自动保存
function persist() {
storage.save(fs);
}
// 提供一个统一的写入接口
function updateFilesystem(mutateFn) {
mutateFn(fs);
persist();
}
function readdir(path, callback) {
const parts = path.split('/').filter(p => p !== '');
let current = fs['/'];
for (let part of parts) {
if (current && current.type === 'dir' && current.children[part]) {
current = current.children[part];
} else {
return callback(`找不到目录: ${path}`);
}
}
callback(null, Object.keys(current.children));
}
function chdir(path, currentDir, callback) {
const resolvedPath = resolvePath(path, currentDir);
const parts = resolvedPath.split('/').filter(p => p !== '');
let current = fs['/'];
for (let part of parts) {
if (current && current.type === 'dir' && current.children[part]) {
current = current.children[part];
} else {
return callback(`找不到目录: ${resolvedPath}`);
}
}
callback(null, resolvedPath);
}
function resolvePath(path, currentDir) {
if (path.startsWith('/')) return path;
if (currentDir === "/") return normalizePath(`/${path}`);
return normalizePath(`${currentDir}/${path}`);
}
function normalizePath(inputPath) {
// 使用 path.normalize 解析 .. 等相对路径
let normalized = path.normalize(inputPath)
.replace(/^(\.\.\/|\/)?/, '') // 移除开头的 ./ ../ /
.replace(/\\/g, '/'); // 统一为正斜杠
if (normalized.startsWith("/")) {
return normalized;
}
return '/' + normalized;
}
function getNodeByPath(path) {
const parts = path.split('/').filter(p => p !== '');
let current = fs['/'];
for (let part of parts) {
if (current && current.type === 'dir' && current.children[part]) {
current = current.children[part];
} else {
return null;
}
}
return current;
}
function getDirStats(node) {
let totalSize = 0;
let latestTime = new Date(node.mtime);
function traverse(current) {
if (current.type === 'file') {
totalSize += current.size;
const mtime = new Date(current.mtime);
if (mtime > latestTime) latestTime = mtime;
} else if (current.type === 'dir') {
for (let child of Object.values(current.children)) {
traverse(child);
}
}
}
traverse(node);
return {
size: totalSize,
mtime: latestTime.toISOString()
};
}
module.exports = {
fs,
readdir,
chdir,
resolvePath,
normalizePath,
updateFilesystem,
getNodeByPath,
getDirStats
};
tool/shell.js
// shell.js
const vfs = require('./vfs');
const fs = vfs.fs;
const commands = {
cat: require('./commands/cat'),
cd: require('./commands/cd'),
clear: require('./commands/clear'),
cp: require('./commands/cp'),
echo: require('./commands/echo'),
exit: require('./commands/exit'),
find: require('./commands/find'),
grep: require('./commands/grep'),
head: require('./commands/head'),
help: require('./commands/help'),
history: require('./commands/history'),
ll: require('./commands/ll'),
ls: require('./commands/ls'),
mkdir: require('./commands/mkdir'),
mv: require('./commands/mv'),
pwd: require('./commands/pwd'),
rm: require('./commands/rm'),
rmdir: require('./commands/rmdir'),
stat: require('./commands/stat'),
tail: require('./commands/tail'),
touch: require('./commands/touch'),
vim: require('./commands/vim'),
yum: require('./commands/yum'),
zip: require('./commands/zip'),
unzip: require('./commands/unzip'),
rz: require('./commands/rz'),
sz: require('./commands/sz'),
};
let currentDir = '/home/user';
function processCommand(input, rl, promptCall) {
const args = input.trim().split(/\s+/);
const cmd = args[0];
if (!commands[cmd]) {
console.log(`命令未找到: ${cmd}`);
promptCall();
return;
} else if (cmd === 'history') {
commands[cmd].execute([], currentDir, rl);
promptCall();
return;
} else if (cmd === 'vim') {
commands[cmd].execute(args, currentDir, rl);
return;
} else if (cmd === 'yum') {
commands[cmd].execute(args, currentDir, rl);
return;
} else if (cmd === 'exit') {
commands[cmd].execute(rl);
return;
}
commands[cmd].execute(args, currentDir, (newDir) => {
if (newDir) currentDir = newDir;
});
promptCall();
}
module.exports = { processCommand };