🧩 項目介紹
該項目使用 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);
- 支持多用戶系統。
源碼下載
Node.js 模擬 Linux 環境
核心代碼
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.jsconst 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 };