Shell、Process与脚本引擎
Shell、Process与脚本引擎:C# .NET企业级实现一、系统架构设计与UML建模1.1 整体架构设计usesusesusescreatesmanagescontainsusesDotNetShell-prompt: string-history: List<string>-environment: Dictionary<string, string>-aliases: Dictionar
Shell、Process与脚本引擎:C# .NET企业级实现
一、系统架构设计与UML建模
1.1 整体架构设计
1.2 核心交互序列图
1.3 三者的关系与区别
维度 Shell Process 脚本引擎
本质 命令行解释器(.NET应用程序) 操作系统进程实例 语言解释器/编译器
职责 用户交互、命令分发、环境管理 执行程序、管理资源 解析、编译、执行脚本
创建方式 Main方法启动 Process.Start() 引擎工厂创建
生命周期 用户会话期间 程序执行期间 脚本执行期间
资源管理 管理线程池、连接池、缓存 管理句柄、内存、线程 管理符号表、JIT编译、垃圾回收
通信机制 标准I/O、管道、消息队列 IPC、管道、共享内存 API调用、委托、动态绑定
.NET实现 封装Process类、并发集合 Process/ProcessStartInfo Roslyn、DLR、动态编译
二、C# .NET完整实现
2.1 项目结构与解决方案
<!-- DotNetShell.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<!-- 脚本引擎依赖 -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.9.2" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Scripting" Version="4.9.2" />
<PackageReference Include="IronPython" Version="3.4.1" />
<PackageReference Include="IronRuby" Version="1.1.4" />
<PackageReference Include="NLua" Version="1.6.2" />
<PackageReference Include="Jint" Version="3.0.0" />
<PackageReference Include="ClearScript" Version="7.4.5" />
<PackageReference Include="PowerShell" Version="7.4.1" />
<!-- 工具库 -->
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0" />
<!-- WebAssembly支持 -->
<PackageReference Include="Wasmtime" Version="14.0.0" />
<!-- 测试 -->
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="Moq" Version="4.20.70" />
</ItemGroup>
</Project>
2.2 公共类型定义
// DotNetShell.Core/Types/CommandTypes.cs
namespace DotNetShell.Core.Types
{
public class ParsedCommand
{
public string RawCommand { get; set; } = string.Empty;
public List<string> Arguments { get; set; } = new();
public string StandardInput { get; set; } = string.Empty;
public string StandardOutput { get; set; } = string.Empty;
public string StandardError { get; set; } = string.Empty;
public bool AppendOutput { get; set; }
public bool RunInBackground { get; set; }
public bool IsPiped { get; set; }
public ParsedCommand? NextCommand { get; set; }
public Dictionary<string, string> EnvironmentVariables { get; set; } = new();
public string WorkingDirectory { get; set; } = string.Empty;
public bool IsBuiltin => Arguments.Count > 0 &&
BuiltinCommandRegistry.IsBuiltin(Arguments[0]);
}
public enum ProcessStatus
{
Running,
Suspended,
Exited,
Terminated
}
public class ProcessInfo
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; } = string.Empty;
public string CommandLine { get; set; } = string.Empty;
public ProcessStatus Status { get; set; }
public DateTime StartTime { get; set; }
public DateTime? ExitTime { get; set; }
public int? ExitCode { get; set; }
public int? JobId { get; set; }
public string WorkingDirectory { get; set; } = string.Empty;
public Dictionary<string, string> Environment { get; set; } = new();
}
public class ScriptResult
{
public bool Success { get; set; }
public object? Value { get; set; }
public string Error { get; set; } = string.Empty;
public TimeSpan ExecutionTime { get; set; }
public string Language { get; set; } = string.Empty;
}
public class ScriptContext
{
public Dictionary<string, string> Environment { get; set; } = new();
public string WorkingDirectory { get; set; } = string.Empty;
public List<string> Arguments { get; set; } = new();
public IShellContext? Shell { get; set; }
public SecurityPolicy SecurityPolicy { get; set; } = new();
public class SecurityPolicy
{
public bool AllowFileSystemAccess { get; set; } = true;
public bool AllowNetworkAccess { get; set; } = false;
public bool AllowReflection { get; set; } = false;
public bool AllowNativeCode { get; set; } = false;
public List<string> AllowedAssemblies { get; set; } = new();
public List<string> AllowedNamespaces { get; set; } = new();
public long MaxMemoryBytes { get; set; } = 100 * 1024 * 1024; // 100MB
public TimeSpan ExecutionTimeout { get; set; } = TimeSpan.FromSeconds(30);
}
}
public class ShellConfiguration
{
public string Prompt { get; set; } = "dsh> ";
public string HistoryFilePath { get; set; } = "~/.dnsh_history";
public int HistorySize { get; set; } = 1000;
public bool EnableColors { get; set; } = true;
public bool EnableCompletion { get; set; } = true;
public int MaxProcesses { get; set; } = 100;
public TimeSpan ExecutionTimeout { get; set; } = TimeSpan.FromSeconds(30);
public string InitScriptPath { get; set; } = "~/.dnshrc";
public string DefaultScriptLanguage { get; set; } = "csharp";
public static ShellConfiguration Default => new();
}
}
2.3 Shell核心实现
// DotNetShell/Shell/DotNetShell.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotNetShell.Core.Types;
using DotNetShell.Process;
using DotNetShell.Scripting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Spectre.Console;
namespace DotNetShell.Shell
{
public interface IShellContext
{
string CurrentDirectory { get; }
Dictionary<string, string> EnvironmentVariables { get; }
void WriteLine(string text, ConsoleColor? color = null);
void WriteError(string message);
Task<int> ExecuteCommandAsync(string command);
}
public class DotNetShell : IShellContext, IAsyncDisposable
{
private readonly ShellConfiguration _configuration;
private readonly ILogger<DotNetShell> _logger;
private readonly ProcessManager _processManager;
private readonly CommandParser _commandParser;
private readonly MultiScriptEngine _scriptEngine;
private readonly ConcurrentDictionary<string, string> _environment;
private readonly ConcurrentDictionary<string, string> _aliases;
private readonly List<string> _history;
private readonly Dictionary<string, IBuiltinCommand> _builtins;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly IServiceProvider _serviceProvider;
private volatile bool _isRunning;
private int _lastExitCode;
private string _currentDirectory;
public event EventHandler<CommandExecutedEventArgs>? CommandExecuted;
public event EventHandler<CommandErrorEventArgs>? CommandError;
public event EventHandler? ShellStarted;
public event EventHandler? ShellStopping;
public DotNetShell(ShellConfiguration? configuration = null,
ILogger<DotNetShell>? logger = null,
IServiceProvider? serviceProvider = null)
{
_configuration = configuration ?? ShellConfiguration.Default;
_logger = logger ?? CreateDefaultLogger();
_serviceProvider = serviceProvider ?? CreateServiceProvider();
_environment = new ConcurrentDictionary<string, string>(
StringComparer.OrdinalIgnoreCase);
_aliases = new ConcurrentDictionary<string, string>(
StringComparer.OrdinalIgnoreCase);
_history = new List<string>();
_builtins = new Dictionary<string, IBuiltinCommand>(
StringComparer.OrdinalIgnoreCase);
_cancellationTokenSource = new CancellationTokenSource();
_processManager = new ProcessManager(_configuration.MaxProcesses);
_commandParser = new CommandParser();
_scriptEngine = new MultiScriptEngine(this);
Initialize();
}
private void Initialize()
{
// 初始化环境变量
InitializeEnvironment();
// 初始化内置命令
InitializeBuiltins();
// 初始化脚本引擎
InitializeScriptEngines();
// 设置当前目录
_currentDirectory = Directory.GetCurrentDirectory();
_environment["PWD"] = _currentDirectory;
// 设置控制台
Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;
Console.CancelKeyPress += OnConsoleCancelKeyPress;
}
private void InitializeEnvironment()
{
// 从父进程继承环境变量
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
{
if (entry.Key is string key && entry.Value is string value)
{
_environment[key] = value;
}
}
// 设置Shell特定环境变量
_environment["SHELL"] = "DotNetShell";
_environment["TERM"] = Environment.GetEnvironmentVariable("TERM") ?? "xterm-256color";
_environment["HOME"] = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
_environment["USER"] = Environment.UserName;
_environment["HOST"] = Environment.MachineName;
}
private void InitializeBuiltins()
{
// 注册内置命令
_builtins["cd"] = new ChangeDirectoryCommand();
_builtins["chdir"] = _builtins["cd"];
_builtins["dir"] = new DirectoryListingCommand();
_builtins["ls"] = _builtins["dir"];
_builtins["type"] = new TypeFileCommand();
_builtins["cat"] = _builtins["type"];
_builtins["copy"] = new CopyFileCommand();
_builtins["cp"] = _builtins["copy"];
_builtins["move"] = new MoveFileCommand();
_builtins["mv"] = _builtins["move"];
_builtins["del"] = new DeleteFileCommand();
_builtins["rm"] = _builtins["del"];
_builtins["mkdir"] = new CreateDirectoryCommand();
_builtins["md"] = _builtins["mkdir"];
_builtins["rmdir"] = new RemoveDirectoryCommand();
_builtins["rd"] = _builtins["rmdir"];
_builtins["echo"] = new EchoCommand();
_builtins["print"] = _builtins["echo"];
_builtins["set"] = new SetEnvironmentVariableCommand();
_builtins["export"] = _builtins["set"];
_builtins["alias"] = new AliasCommand();
_builtins["unalias"] = new UnaliasCommand();
_builtins["history"] = new HistoryCommand();
_builtins["jobs"] = new JobsCommand();
_builtins["fg"] = new ForegroundJobCommand();
_builtins["bg"] = new BackgroundJobCommand();
_builtins["kill"] = new KillCommand();
_builtins["ps"] = new ProcessListCommand();
_builtins["cls"] = new ClearScreenCommand();
_builtins["clear"] = _builtins["cls"];
_builtins["help"] = new HelpCommand();
_builtins["?"] = _builtins["help"];
_builtins["exit"] = new ExitCommand();
_builtins["quit"] = _builtins["exit"];
_builtins["source"] = new SourceCommand();
_builtins["."] = _builtins["source"];
}
private void InitializeScriptEngines()
{
// 注册各种脚本引擎
_scriptEngine.RegisterExecutor("csharp", new CSharpScriptExecutor(this));
_scriptEngine.RegisterExecutor("vb", new VisualBasicScriptExecutor(this));
_scriptEngine.RegisterExecutor("powershell", new PowerShellExecutor(this));
_scriptEngine.RegisterExecutor("python", new PythonExecutor(this));
_scriptEngine.RegisterExecutor("ruby", new RubyExecutor(this));
_scriptEngine.RegisterExecutor("lua", new LuaExecutor(this));
_scriptEngine.RegisterExecutor("javascript", new JavaScriptExecutor(this));
_scriptEngine.RegisterExecutor("typescript", new TypeScriptExecutor(this));
// 更多引擎...
}
public async Task RunAsync()
{
if (_isRunning)
throw new InvalidOperationException("Shell is already running");
_isRunning = true;
ShellStarted?.Invoke(this, EventArgs.Empty);
// 显示欢迎信息
WriteLine("DotNetShell v1.0.0 - Enterprise Multi-language Shell", ConsoleColor.Cyan);
WriteLine("Type 'help' for available commands", ConsoleColor.DarkGray);
WriteLine(string.Empty);
// 加载初始化脚本
await LoadInitScriptAsync();
// 主循环
while (_isRunning && !_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
await ProcessInputAsync();
}
catch (OperationCanceledException)
{
// 正常取消
break;
}
catch (Exception ex)
{
WriteError($"Shell error: {ex.Message}");
_logger.LogError(ex, "Error in shell main loop");
}
}
await StopAsync();
}
private async Task ProcessInputAsync()
{
// 读取输入
string input = await ReadLineAsync();
if (string.IsNullOrWhiteSpace(input))
return;
// 添加到历史记录
_history.Add(input);
if (_history.Count > _configuration.HistorySize)
_history.RemoveAt(0);
// 执行命令
int exitCode = await ExecuteCommandAsync(input);
// 更新最后退出码和环境变量
_lastExitCode = exitCode;
_environment["ERRORLEVEL"] = exitCode.ToString();
_environment["?"] = exitCode.ToString();
}
private async Task<string> ReadLineAsync()
{
if (_configuration.EnableCompletion)
{
// 使用Spectre.Console提供更好的交互体验
return await AnsiConsole.PromptAsync(
new TextPrompt<string>(_configuration.Prompt)
.PromptStyle("green")
.AllowEmpty()
.AddChoice("exit")
.AddChoice("help")
.AddChoice("clear")
);
}
else
{
Console.Write(_configuration.Prompt);
return Console.ReadLine() ?? string.Empty;
}
}
public async Task<int> ExecuteCommandAsync(string command)
{
if (string.IsNullOrWhiteSpace(command))
return 0;
var stopwatch = Stopwatch.StartNew();
try
{
// 检查是否是脚本模式
if (IsScriptMode(command, out string language, out string code))
{
return await ExecuteScriptCodeAsync(language, code);
}
// 扩展别名
command = ExpandAliases(command);
// 扩展环境变量
command = ExpandEnvironmentVariables(command);
// 解析命令
ParsedCommand parsedCommand = _commandParser.Parse(command, _environment);
// 检查是否是内置命令
if (parsedCommand.IsBuiltin)
{
return await ExecuteBuiltinCommandAsync(parsedCommand);
}
// 检查是否是脚本文件
if (IsScriptFile(parsedCommand.Arguments[0]))
{
return await ExecuteScriptFileAsync(parsedCommand);
}
// 执行外部命令
return await ExecuteExternalCommandAsync(parsedCommand);
}
catch (Exception ex)
{
WriteError($"Command execution failed: {ex.Message}");
CommandError?.Invoke(this, new CommandErrorEventArgs
{
Command = command,
Error = ex.Message,
Timestamp = DateTime.UtcNow
});
return 1;
}
finally
{
stopwatch.Stop();
_logger.LogDebug("Command executed in {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
}
}
private bool IsScriptMode(string command, out string language, out string code)
{
language = string.Empty;
code = string.Empty;
// 检查是否是指定语言的脚本
foreach (var lang in _scriptEngine.GetSupportedLanguages())
{
string prefix = lang + ">";
if (command.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
language = lang;
code = command[prefix.Length..].TrimStart();
return true;
}
}
return false;
}
private async Task<int> ExecuteScriptCodeAsync(string language, string code)
{
try
{
var context = new ScriptContext
{
Environment = new Dictionary<string, string>(_environment),
WorkingDirectory = _currentDirectory,
Shell = this,
SecurityPolicy = new ScriptContext.SecurityPolicy
{
ExecutionTimeout = _configuration.ExecutionTimeout
}
};
object? result = await _scriptEngine.EvaluateAsync(language, code, context);
if (result != null)
{
WriteLine(result.ToString() ?? string.Empty);
}
return 0;
}
catch (Exception ex)
{
WriteError($"Script error: {ex.Message}");
return 1;
}
}
private string ExpandAliases(string command)
{
string[] parts = command.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 0 && _aliases.TryGetValue(parts[0], out string? alias))
{
return alias + (parts.Length > 1 ? " " + parts[1] : "");
}
return command;
}
private string ExpandEnvironmentVariables(string command)
{
string result = command;
foreach (var kvp in _environment)
{
result = result.Replace($"${kvp.Key}", kvp.Value)
.Replace($"${{{kvp.Key}}}", kvp.Value)
.Replace($"%{kvp.Key}%", kvp.Value);
}
return result;
}
private async Task<int> ExecuteBuiltinCommandAsync(ParsedCommand command)
{
string commandName = command.Arguments[0];
if (_builtins.TryGetValue(commandName, out IBuiltinCommand? builtin))
{
var args = command.Arguments.Skip(1).ToList();
return await builtin.ExecuteAsync(args, this);
}
WriteError($"Builtin command not found: {commandName}");
return 1;
}
private bool IsScriptFile(string path)
{
if (string.IsNullOrEmpty(path))
return false;
string extension = Path.GetExtension(path).ToLowerInvariant();
return extension switch
{
".cs" => true,
".vb" => true,
".ps1" => true,
".py" => true,
".rb" => true,
".lua" => true,
".js" => true,
".ts" => true,
_ => false
};
}
private async Task<int> ExecuteScriptFileAsync(ParsedCommand command)
{
string filePath = Path.Combine(_currentDirectory, command.Arguments[0]);
if (!File.Exists(filePath))
{
WriteError($"Script file not found: {filePath}");
return 1;
}
string extension = Path.GetExtension(filePath).ToLowerInvariant();
string language = extension switch
{
".cs" => "csharp",
".vb" => "vb",
".ps1" => "powershell",
".py" => "python",
".rb" => "ruby",
".lua" => "lua",
".js" => "javascript",
".ts" => "typescript",
_ => throw new NotSupportedException($"Unsupported script extension: {extension}")
};
try
{
var context = new ScriptContext
{
Environment = new Dictionary<string, string>(_environment),
WorkingDirectory = _currentDirectory,
Arguments = command.Arguments.Skip(1).ToList(),
Shell = this,
SecurityPolicy = new ScriptContext.SecurityPolicy
{
ExecutionTimeout = _configuration.ExecutionTimeout
}
};
object? result = await _scriptEngine.ExecuteFileAsync(language, filePath, context);
if (result != null)
{
WriteLine(result.ToString() ?? string.Empty);
}
return 0;
}
catch (Exception ex)
{
WriteError($"Script execution failed: {ex.Message}");
return 1;
}
}
private async Task<int> ExecuteExternalCommandAsync(ParsedCommand command)
{
if (command.IsPiped && command.NextCommand != null)
{
// 管道命令
var commands = new List<ParsedCommand>();
ParsedCommand? current = command;
while (current != null)
{
commands.Add(current);
current = current.NextCommand;
}
var processes = await _processManager.CreatePipelineAsync(commands);
// 等待所有进程完成
int exitCode = 0;
foreach (var process in processes)
{
exitCode = await _processManager.WaitForProcessAsync(process.Id);
}
return exitCode;
}
else
{
// 单个命令
var process = await _processManager.CreateProcessAsync(command);
if (command.RunInBackground)
{
WriteLine($"[{process.Id}] {command.RawCommand}", ConsoleColor.DarkGray);
return 0;
}
else
{
return await _processManager.WaitForProcessAsync(process.Id);
}
}
}
private async Task LoadInitScriptAsync()
{
string initScriptPath = _configuration.InitScriptPath.Replace("~",
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
if (File.Exists(initScriptPath))
{
try
{
string[] lines = await File.ReadAllLinesAsync(initScriptPath);
foreach (string line in lines)
{
string trimmed = line.Trim();
if (!string.IsNullOrEmpty(trimmed) && !trimmed.StartsWith('#'))
{
await ExecuteCommandAsync(trimmed);
}
}
}
catch (Exception ex)
{
WriteError($"Failed to load init script: {ex.Message}");
}
}
}
public async Task StopAsync()
{
if (!_isRunning)
return;
ShellStopping?.Invoke(this, EventArgs.Empty);
_isRunning = false;
_cancellationTokenSource.Cancel();
// 清理资源
await _processManager.DisposeAsync();
await _scriptEngine.DisposeAsync();
// 保存历史记录
await SaveHistoryAsync();
Console.CancelKeyPress -= OnConsoleCancelKeyPress;
}
private async Task SaveHistoryAsync()
{
try
{
string historyPath = _configuration.HistoryFilePath.Replace("~",
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
string directory = Path.GetDirectoryName(historyPath)!;
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
await File.WriteAllLinesAsync(historyPath, _history);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save history");
}
}
private void OnConsoleCancelKeyPress(object? sender, ConsoleCancelEventArgs e)
{
e.Cancel = true; // 阻止进程终止
WriteLine("^C", ConsoleColor.Yellow);
_processManager.KillForegroundProcess();
}
// IShellContext 实现
public string CurrentDirectory => _currentDirectory;
public Dictionary<string, string> EnvironmentVariables =>
new Dictionary<string, string>(_environment);
public void WriteLine(string text, ConsoleColor? color = null)
{
if (color.HasValue)
{
var oldColor = Console.ForegroundColor;
Console.ForegroundColor = color.Value;
Console.WriteLine(text);
Console.ForegroundColor = oldColor;
}
else
{
Console.WriteLine(text);
}
}
public void WriteError(string message)
{
WriteLine($"Error: {message}", ConsoleColor.Red);
}
// 内置命令接口
public interface IBuiltinCommand
{
Task<int> ExecuteAsync(List<string> args, IShellContext shell);
}
// 内置命令实现示例
public class ChangeDirectoryCommand : IBuiltinCommand
{
public async Task<int> ExecuteAsync(List<string> args, IShellContext shell)
{
string target = args.FirstOrDefault() ??
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
if (target == "-")
{
// 切换到上一个目录
if (shell.EnvironmentVariables.TryGetValue("OLDPWD", out string? oldPwd))
{
target = oldPwd;
}
else
{
shell.WriteError("OLDPWD not set");
return 1;
}
}
try
{
string oldDir = shell.CurrentDirectory;
string newDir = Path.GetFullPath(Path.Combine(shell.CurrentDirectory, target));
Directory.SetCurrentDirectory(newDir);
// 更新环境变量
shell.EnvironmentVariables["OLDPWD"] = oldDir;
shell.EnvironmentVariables["PWD"] = newDir;
return 0;
}
catch (Exception ex)
{
shell.WriteError($"cd: {target}: {ex.Message}");
return 1;
}
}
}
public class SourceCommand : IBuiltinCommand
{
public async Task<int> ExecuteAsync(List<string> args, IShellContext shell)
{
if (args.Count == 0)
{
shell.WriteError("source: filename argument required");
return 1;
}
string filePath = Path.Combine(shell.CurrentDirectory, args[0]);
if (!File.Exists(filePath))
{
shell.WriteError($"source: file not found: {filePath}");
return 1;
}
try
{
string[] lines = await File.ReadAllLinesAsync(filePath);
foreach (string line in lines)
{
string trimmed = line.Trim();
if (!string.IsNullOrEmpty(trimmed) && !trimmed.StartsWith('#'))
{
await shell.ExecuteCommandAsync(trimmed);
}
}
return 0;
}
catch (Exception ex)
{
shell.WriteError($"source: {ex.Message}");
return 1;
}
}
}
// 其他内置命令实现...
private static ILogger<DotNetShell> CreateDefaultLogger()
{
return LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
}).CreateLogger<DotNetShell>();
}
private static IServiceProvider CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddLogging();
return services.BuildServiceProvider();
}
public async ValueTask DisposeAsync()
{
await StopAsync();
_cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
}
}
// 事件参数类
public class CommandExecutedEventArgs : EventArgs
{
public string Command { get; set; } = string.Empty;
public int ExitCode { get; set; }
public DateTime Timestamp { get; set; }
}
public class CommandErrorEventArgs : EventArgs
{
public string Command { get; set; } = string.Empty;
public string Error { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
}
}
2.4 进程管理器实现
// DotNetShell/Process/ProcessManager.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using DotNetShell.Core.Types;
namespace DotNetShell.Process
{
public class ProcessManager : IAsyncDisposable
{
private readonly int _maxProcesses;
private readonly ConcurrentDictionary<int, ProcessInfo> _processes;
private readonly ConcurrentDictionary<int, JobInfo> _jobs;
private readonly SemaphoreSlim _processLock;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Task _monitorTask;
private Process? _foregroundProcess;
public ProcessManager(int maxProcesses = 100)
{
_maxProcesses = maxProcesses;
_processes = new ConcurrentDictionary<int, ProcessInfo>();
_jobs = new ConcurrentDictionary<int, JobInfo>();
_processLock = new SemaphoreSlim(1, 1);
_cancellationTokenSource = new CancellationTokenSource();
_monitorTask = Task.Run(MonitorProcessesAsync);
}
public async Task<Process> CreateProcessAsync(ParsedCommand command)
{
await _processLock.WaitAsync();
try
{
if (_processes.Count >= _maxProcesses)
{
throw new InvalidOperationException(
$"Maximum number of processes ({_maxProcesses}) reached");
}
if (command.Arguments.Count == 0)
{
throw new ArgumentException("No command specified");
}
var startInfo = new ProcessStartInfo
{
FileName = command.Arguments[0],
Arguments = string.Join(" ", command.Arguments.Skip(1)),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = !string.IsNullOrEmpty(command.StandardInput),
RedirectStandardOutput = !string.IsNullOrEmpty(command.StandardOutput) || command.IsPiped,
RedirectStandardError = !string.IsNullOrEmpty(command.StandardError)
};
// 设置工作目录
if (!string.IsNullOrEmpty(command.WorkingDirectory))
{
startInfo.WorkingDirectory = command.WorkingDirectory;
}
// 设置环境变量
foreach (var kvp in command.EnvironmentVariables)
{
startInfo.EnvironmentVariables[kvp.Key] = kvp.Value;
}
// 创建进程
var process = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
// 设置标准输入重定向
if (!string.IsNullOrEmpty(command.StandardInput))
{
if (command.StandardInput == "/dev/null" ||
command.StandardInput == "NUL")
{
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.StandardInputEncoding = null;
}
else if (File.Exists(command.StandardInput))
{
process.StartInfo.RedirectStandardInput = true;
}
}
// 启动进程
if (!process.Start())
{
throw new InvalidOperationException($"Failed to start process: {command.Arguments[0]}");
}
// 设置输出重定向文件
if (!string.IsNullOrEmpty(command.StandardOutput))
{
var outputFile = new FileStream(command.StandardOutput,
command.AppendOutput ? FileMode.Append : FileMode.Create,
FileAccess.Write);
process.StandardOutput.BaseStream.CopyToAsync(outputFile);
}
if (!string.IsNullOrEmpty(command.StandardError))
{
var errorFile = new FileStream(command.StandardError,
command.AppendOutput ? FileMode.Append : FileMode.Create,
FileAccess.Write);
process.StandardError.BaseStream.CopyToAsync(errorFile);
}
// 创建进程信息
var processInfo = new ProcessInfo
{
Id = process.Id,
ParentId = System.Diagnostics.Process.GetCurrentProcess().Id,
Name = Path.GetFileName(command.Arguments[0]),
CommandLine = $"{command.Arguments[0]} {string.Join(" ", command.Arguments.Skip(1))}",
Status = Types.ProcessStatus.Running,
StartTime = DateTime.Now,
WorkingDirectory = startInfo.WorkingDirectory,
Environment = new Dictionary<string, string>(command.EnvironmentVariables)
};
_processes[process.Id] = processInfo;
// 如果是后台进程,创建作业
if (command.RunInBackground)
{
int jobId = _jobs.Count + 1;
var jobInfo = new JobInfo
{
Id = jobId,
ProcessIds = new List<int> { process.Id },
Command = command.RawCommand,
Status = JobStatus.Running
};
_jobs[jobId] = jobInfo;
processInfo.JobId = jobId;
// 分离进程
process.Exited += (sender, e) => OnProcessExited(process, jobId);
}
else
{
_foregroundProcess = process;
}
// 监听进程退出
process.Exited += (sender, e) => OnProcessExited(process, null);
return process;
}
finally
{
_processLock.Release();
}
}
public async Task<List<Process>> CreatePipelineAsync(List<ParsedCommand> commands)
{
if (commands.Count == 0)
throw new ArgumentException("No commands provided for pipeline");
await _processLock.WaitAsync();
try
{
if (_processes.Count + commands.Count > _maxProcesses)
{
throw new InvalidOperationException(
$"Maximum number of processes ({_maxProcesses}) would be exceeded");
}
var processes = new List<Process>();
Process? previousProcess = null;
StreamWriter? previousOutputStream = null;
for (int i = 0; i < commands.Count; i++)
{
var command = commands[i];
if (command.Arguments.Count == 0)
{
throw new ArgumentException($"Command {i} has no arguments");
}
var startInfo = new ProcessStartInfo
{
FileName = command.Arguments[0],
Arguments = string.Join(" ", command.Arguments.Skip(1)),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = i > 0, // 第一个进程不需要重定向输入
RedirectStandardOutput = i < commands.Count - 1, // 最后一个进程不需要重定向输出
RedirectStandardError = !string.IsNullOrEmpty(command.StandardError)
};
// 设置工作目录
if (!string.IsNullOrEmpty(command.WorkingDirectory))
{
startInfo.WorkingDirectory = command.WorkingDirectory;
}
// 设置环境变量
foreach (var kvp in command.EnvironmentVariables)
{
startInfo.EnvironmentVariables[kvp.Key] = kvp.Value;
}
var process = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
// 启动进程
if (!process.Start())
{
throw new InvalidOperationException(
$"Failed to start process {i}: {command.Arguments[0]}");
}
// 连接管道
if (previousOutputStream != null)
{
await previousOutputStream.WriteAsync(
await previousProcess!.StandardOutput.ReadToEndAsync());
previousOutputStream.Close();
}
if (i < commands.Count - 1)
{
previousOutputStream = process.StandardInput;
}
previousProcess = process;
processes.Add(process);
// 创建进程信息
var processInfo = new ProcessInfo
{
Id = process.Id,
ParentId = System.Diagnostics.Process.GetCurrentProcess().Id,
Name = Path.GetFileName(command.Arguments[0]),
CommandLine = $"{command.Arguments[0]} {string.Join(" ", command.Arguments.Skip(1))}",
Status = Types.ProcessStatus.Running,
StartTime = DateTime.Now,
WorkingDirectory = startInfo.WorkingDirectory,
Environment = new Dictionary<string, string>(command.EnvironmentVariables)
};
_processes[process.Id] = processInfo;
}
// 创建作业
int jobId = _jobs.Count + 1;
var jobInfo = new JobInfo
{
Id = jobId,
ProcessIds = processes.Select(p => p.Id).ToList(),
Command = string.Join(" | ", commands.Select(c => c.RawCommand)),
Status = JobStatus.Running
};
_jobs[jobId] = jobInfo;
// 设置作业ID到进程信息
foreach (var process in processes)
{
if (_processes.TryGetValue(process.Id, out ProcessInfo? info))
{
info.JobId = jobId;
}
}
// 监听所有进程退出
foreach (var process in processes)
{
process.Exited += (sender, e) => OnPipelineProcessExited(process, jobId);
}
return processes;
}
finally
{
_processLock.Release();
}
}
public async Task<int> WaitForProcessAsync(int processId)
{
if (!_processes.TryGetValue(processId, out ProcessInfo? processInfo))
{
throw new ArgumentException($"Process {processId} not found");
}
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
await process.WaitForExitAsync();
return process.ExitCode;
}
catch (ArgumentException)
{
// 进程已退出
return processInfo.ExitCode ?? -1;
}
}
public async Task<bool> KillProcessAsync(int processId, bool force = false)
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
if (force)
{
process.Kill();
}
else
{
// 尝试优雅关闭
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
process.CloseMainWindow();
}
else
{
process.Kill(); // Unix上通常直接kill
}
// 等待进程退出
if (!await process.WaitForExitAsync(5000))
{
process.Kill();
}
}
return true;
}
catch (Exception)
{
return false;
}
}
public void KillForegroundProcess()
{
if (_foregroundProcess != null && !_foregroundProcess.HasExited)
{
try
{
_foregroundProcess.Kill();
}
catch
{
// 忽略异常
}
}
}
public List<ProcessInfo> GetProcesses()
{
return _processes.Values.ToList();
}
public List<JobInfo> GetJobs()
{
return _jobs.Values.ToList();
}
private void OnProcessExited(Process process, int? jobId)
{
if (_processes.TryGetValue(process.Id, out ProcessInfo? processInfo))
{
processInfo.Status = Types.ProcessStatus.Exited;
processInfo.ExitTime = DateTime.Now;
processInfo.ExitCode = process.ExitCode;
if (jobId.HasValue)
{
if (_jobs.TryGetValue(jobId.Value, out JobInfo? jobInfo))
{
jobInfo.ProcessIds.Remove(process.Id);
if (jobInfo.ProcessIds.Count == 0)
{
jobInfo.Status = JobStatus.Completed;
_jobs.TryRemove(jobId.Value, out _);
}
}
}
_processes.TryRemove(process.Id, out _);
}
}
private void OnPipelineProcessExited(Process process, int jobId)
{
OnProcessExited(process, jobId);
}
private async Task MonitorProcessesAsync()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
await Task.Delay(1000, _cancellationTokenSource.Token);
// 清理已退出的进程
var deadProcesses = new List<int>();
foreach (var kvp in _processes)
{
var processInfo = kvp.Value;
if (processInfo.Status == Types.ProcessStatus.Running)
{
try
{
var process = System.Diagnostics.Process.GetProcessById(kvp.Key);
if (process.HasExited)
{
processInfo.Status = Types.ProcessStatus.Exited;
processInfo.ExitTime = DateTime.Now;
processInfo.ExitCode = process.ExitCode;
deadProcesses.Add(kvp.Key);
}
}
catch (ArgumentException)
{
// 进程已不存在
processInfo.Status = Types.ProcessStatus.Terminated;
processInfo.ExitTime = DateTime.Now;
processInfo.ExitCode = -1;
deadProcesses.Add(kvp.Key);
}
}
}
// 移除已退出的进程
foreach (int pid in deadProcesses)
{
_processes.TryRemove(pid, out _);
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception)
{
// 忽略监控异常
}
}
}
public async ValueTask DisposeAsync()
{
_cancellationTokenSource.Cancel();
try
{
await _monitorTask;
}
catch (OperationCanceledException)
{
// 正常取消
}
// 终止所有子进程
foreach (int pid in _processes.Keys)
{
await KillProcessAsync(pid, true);
}
_cancellationTokenSource.Dispose();
_processLock.Dispose();
}
}
public class JobInfo
{
public int Id { get; set; }
public List<int> ProcessIds { get; set; } = new();
public string Command { get; set; } = string.Empty;
public JobStatus Status { get; set; }
}
public enum JobStatus
{
Running,
Suspended,
Completed,
Terminated
}
}
2.5 多语言脚本引擎实现
// DotNetShell/Scripting/MultiScriptEngine.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotNetShell.Core.Types;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.ClearScript;
using Microsoft.ClearScript.V8;
using IronPython.Hosting;
using IronRuby;
using NLua;
using Jint;
using PowerShell = System.Management.Automation.PowerShell;
namespace DotNetShell.Scripting
{
public class MultiScriptEngine : IAsyncDisposable
{
private readonly ConcurrentDictionary<string, IScriptExecutor> _executors;
private readonly ScriptSandbox _sandbox;
private readonly AssemblyLoadContext _scriptAssemblyContext;
private readonly ConcurrentDictionary<string, CompiledScript> _compiledCache;
private readonly CancellationTokenSource _cancellationTokenSource;
public MultiScriptEngine(IShellContext shell)
{
_executors = new ConcurrentDictionary<string, IScriptExecutor>(
StringComparer.OrdinalIgnoreCase);
_sandbox = new ScriptSandbox();
_scriptAssemblyContext = new ScriptAssemblyLoadContext();
_compiledCache = new ConcurrentDictionary<string, CompiledScript>();
_cancellationTokenSource = new CancellationTokenSource();
InitializeExecutors(shell);
}
private void InitializeExecutors(IShellContext shell)
{
// 注册各种脚本引擎
RegisterExecutor("csharp", new CSharpScriptExecutor(shell));
RegisterExecutor("vb", new VisualBasicScriptExecutor(shell));
RegisterExecutor("powershell", new PowerShellExecutor(shell));
RegisterExecutor("python", new PythonScriptExecutor(shell));
RegisterExecutor("ruby", new RubyScriptExecutor(shell));
RegisterExecutor("lua", new LuaScriptExecutor(shell));
RegisterExecutor("javascript", new JavaScriptExecutor(shell));
RegisterExecutor("typescript", new TypeScriptExecutor(shell));
RegisterExecutor("perl", new PerlScriptExecutor(shell));
RegisterExecutor("r", new RScriptExecutor(shell));
RegisterExecutor("kotlin", new KotlinScriptExecutor(shell));
RegisterExecutor("scala", new ScalaScriptExecutor(shell));
RegisterExecutor("clojure", new ClojureScriptExecutor(shell));
RegisterExecutor("wasm", new WebAssemblyExecutor(shell));
}
public void RegisterExecutor(string language, IScriptExecutor executor)
{
_executors[language] = executor;
}
public IScriptExecutor? GetExecutor(string language)
{
return _executors.TryGetValue(language, out var executor) ? executor : null;
}
public IEnumerable<string> GetSupportedLanguages()
{
return _executors.Keys;
}
public async Task<object?> EvaluateAsync(string language, string code, ScriptContext context)
{
if (!_executors.TryGetValue(language, out var executor))
{
throw new NotSupportedException($"Language '{language}' is not supported.");
}
// 在沙箱中执行
return await _sandbox.ExecuteAsync(async () =>
{
var stopwatch = Stopwatch.StartNew();
try
{
// 设置资源限制
using var memoryMonitor = new MemoryMonitor(context.SecurityPolicy.MaxMemoryBytes);
using var timeoutMonitor = new TimeoutMonitor(context.SecurityPolicy.ExecutionTimeout);
// 执行脚本
var result = await executor.ExecuteAsync(code, context);
stopwatch.Stop();
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
throw new ScriptExecutionException(
$"Script execution failed: {ex.Message}", ex);
}
}, context.SecurityPolicy);
}
public async Task<object?> ExecuteFileAsync(string language, string filePath, ScriptContext context)
{
if (!_executors.TryGetValue(language, out var executor))
{
throw new NotSupportedException($"Language '{language}' is not supported.");
}
// 检查文件权限
_sandbox.CheckFileAccess(filePath, context.SecurityPolicy);
// 读取文件内容
string code = await File.ReadAllTextAsync(filePath);
// 更新上下文
var updatedContext = context with
{
WorkingDirectory = Path.GetDirectoryName(filePath) ?? context.WorkingDirectory
};
return await EvaluateAsync(language, code, updatedContext);
}
public async Task<CompiledScript> CompileAsync(string language, string code)
{
string cacheKey = $"{language}:{code.GetHashCode():X8}";
if (_compiledCache.TryGetValue(cacheKey, out var compiled))
{
return compiled;
}
if (!_executors.TryGetValue(language, out var executor))
{
throw new NotSupportedException($"Language '{language}' is not supported.");
}
compiled = await executor.CompileAsync(code);
_compiledCache[cacheKey] = compiled;
return compiled;
}
public async ValueTask DisposeAsync()
{
_cancellationTokenSource.Cancel();
foreach (var executor in _executors.Values)
{
if (executor is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else if (executor is IDisposable disposable)
{
disposable.Dispose();
}
}
_executors.Clear();
_compiledCache.Clear();
_cancellationTokenSource.Dispose();
}
}
// 脚本执行器接口
public interface IScriptExecutor
{
string LanguageName { get; }
string[] FileExtensions { get; }
Task<object?> ExecuteAsync(string code, ScriptContext context);
Task<CompiledScript> CompileAsync(string code);
}
public abstract class CompiledScript
{
public abstract Task<object?> ExecuteAsync(ScriptContext context);
}
// C# 脚本执行器 (基于Roslyn)
public class CSharpScriptExecutor : IScriptExecutor, IAsyncDisposable
{
private readonly IShellContext _shell;
private readonly ScriptOptions _scriptOptions;
private readonly ConcurrentDictionary<string, ScriptRunner<object>> _scriptCache;
public string LanguageName => "csharp";
public string[] FileExtensions => new[] { ".cs", ".csx" };
public CSharpScriptExecutor(IShellContext shell)
{
_shell = shell;
_scriptCache = new ConcurrentDictionary<string, ScriptRunner<object>>();
// 配置脚本选项
_scriptOptions = ScriptOptions.Default
.WithReferences(
typeof(object).Assembly,
typeof(Console).Assembly,
typeof(System.Linq.Enumerable).Assembly,
typeof(System.Dynamic.ExpandoObject).Assembly,
typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly)
.WithImports(
"System",
"System.Collections.Generic",
"System.Linq",
"System.Text",
"System.IO",
"System.Threading.Tasks")
.WithEmitDebugInformation(true)
.WithFileEncoding(Encoding.UTF8);
}
public async Task<object?> ExecuteAsync(string code, ScriptContext context)
{
string cacheKey = $"csharp:{code.GetHashCode():X8}";
var scriptRunner = _scriptCache.GetOrAdd(cacheKey, _ =>
{
var script = CSharpScript.Create<object>(
code,
_scriptOptions,
typeof(ScriptGlobals));
return script.CreateDelegate();
});
var globals = new ScriptGlobals
{
Shell = _shell,
Context = context,
Env = new Dictionary<string, string>(context.Environment),
Args = context.Arguments.ToArray(),
WorkingDirectory = context.WorkingDirectory
};
ScriptState<object> state = await scriptRunner(globals);
return state.ReturnValue;
}
public async Task<CompiledScript> CompileAsync(string code)
{
var script = CSharpScript.Create<object>(
code,
_scriptOptions,
typeof(ScriptGlobals));
var compiled = await script.CompileAsync();
return new CSharpCompiledScript(script);
}
public async ValueTask DisposeAsync()
{
_scriptCache.Clear();
await Task.CompletedTask;
}
public class ScriptGlobals
{
public IShellContext? Shell { get; set; }
public ScriptContext? Context { get; set; }
public Dictionary<string, string>? Env { get; set; }
public string[]? Args { get; set; }
public string? WorkingDirectory { get; set; }
public void Print(object? value) => Console.WriteLine(value);
public void Echo(object? value) => Console.WriteLine(value);
}
private class CSharpCompiledScript : CompiledScript
{
private readonly Script<object> _script;
public CSharpCompiledScript(Script<object> script)
{
_script = script;
}
public override async Task<object?> ExecuteAsync(ScriptContext context)
{
var globals = new ScriptGlobals
{
Context = context,
Env = new Dictionary<string, string>(context.Environment),
Args = context.Arguments.ToArray(),
WorkingDirectory = context.WorkingDirectory
};
ScriptState<object> state = await _script.RunAsync(globals);
return state.ReturnValue;
}
}
}
// JavaScript 执行器 (基于ClearScript/V8)
public class JavaScriptExecutor : IScriptExecutor, IDisposable
{
private readonly IShellContext _shell;
private readonly V8ScriptEngine _engine;
private readonly ConcurrentDictionary<string, V8Script> _scriptCache;
public string LanguageName => "javascript";
public string[] FileExtensions => new[] { ".js", ".es", ".mjs" };
public JavaScriptExecutor(IShellContext shell)
{
_shell = shell;
_engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging);
_scriptCache = new ConcurrentDictionary<string, V8Script>();
InitializeEngine();
}
private void InitializeEngine()
{
// 注入全局对象
_engine.AddHostObject("shell", HostItemFlags.GlobalMembers, _shell);
_engine.AddHostObject("console", HostItemFlags.GlobalMembers, new ConsoleObject());
_engine.AddHostObject("process", HostItemFlags.GlobalMembers, new ProcessObject());
// 注入常用函数
_engine.AddHostObject("print", HostItemFlags.GlobalMembers,
new Action<object?>(value => Console.WriteLine(value)));
_engine.AddHostObject("require", HostItemFlags.GlobalMembers,
new Func<string, object>(RequireModule));
// 设置超时
_engine.ScriptTimeout = 30000; // 30秒
}
public async Task<object?> ExecuteAsync(string code, ScriptContext context)
{
string cacheKey = $"js:{code.GetHashCode():X8}";
var compiledScript = _scriptCache.GetOrAdd(cacheKey, _ =>
{
return _engine.Compile(code);
});
// 设置上下文变量
_engine.Script.__context = context;
_engine.Script.__env = context.Environment;
_engine.Script.__args = context.Arguments.ToArray();
_engine.Script.__dirname = context.WorkingDirectory;
return await Task.Run(() => _engine.Evaluate(compiledScript));
}
public Task<CompiledScript> CompileAsync(string code)
{
var compiled = _engine.Compile(code);
return Task.FromResult<CompiledScript>(
new JavaScriptCompiledScript(compiled, _engine));
}
private object RequireModule(string moduleName)
{
// 简化版模块加载
if (moduleName == "fs")
{
return new FileSystemModule();
}
else if (moduleName == "path")
{
return new PathModule();
}
throw new ScriptEngineException($"Module '{moduleName}' not found");
}
public void Dispose()
{
_scriptCache.Clear();
_engine.Dispose();
}
private class ConsoleObject
{
public void log(params object?[] args)
{
Console.WriteLine(string.Join(" ", args.Select(a => a?.ToString())));
}
public void error(params object?[] args)
{
var oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine(string.Join(" ", args.Select(a => a?.ToString())));
Console.ForegroundColor = oldColor;
}
public void warn(params object?[] args)
{
var oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(string.Join(" ", args.Select(a => a?.ToString())));
Console.ForegroundColor = oldColor;
}
}
private class ProcessObject
{
public string cwd() => Directory.GetCurrentDirectory();
public void chdir(string path) => Directory.SetCurrentDirectory(path);
public Dictionary<string, string> env =>
Environment.GetEnvironmentVariables()
.Cast<System.Collections.DictionaryEntry>()
.ToDictionary(e => e.Key.ToString()!, e => e.Value?.ToString() ?? "");
}
private class JavaScriptCompiledScript : CompiledScript
{
private readonly V8Script _script;
private readonly V8ScriptEngine _engine;
public JavaScriptCompiledScript(V8Script script, V8ScriptEngine engine)
{
_script = script;
_engine = engine;
}
public override Task<object?> ExecuteAsync(ScriptContext context)
{
return Task.Run(() =>
{
// 设置上下文
_engine.Script.__context = context;
_engine.Script.__env = context.Environment;
_engine.Script.__args = context.Arguments.ToArray();
return _engine.Evaluate(_script);
});
}
}
}
// Python 执行器 (基于IronPython)
public class PythonScriptExecutor : IScriptExecutor, IDisposable
{
private readonly IShellContext _shell;
private readonly Microsoft.Scripting.Hosting.ScriptEngine _engine;
private readonly Microsoft.Scripting.Hosting.ScriptScope _scope;
public string LanguageName => "python";
public string[] FileExtensions => new[] { ".py", ".pyw" };
public PythonScriptExecutor(IShellContext shell)
{
_shell = shell;
_engine = Python.CreateEngine();
_scope = _engine.CreateScope();
InitializeEngine();
}
private void InitializeEngine()
{
// 设置搜索路径
var paths = _engine.GetSearchPaths();
paths.Add(Path.Combine(Environment.CurrentDirectory, "python"));
paths.Add(Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.UserProfile), ".python"));
_engine.SetSearchPaths(paths);
// 注入全局变量
_scope.SetVariable("shell", _shell);
_scope.SetVariable("print", new Action<object?>(Console.WriteLine));
// 注入内置模块
_scope.Engine.Execute(@"
import sys
import clr
clr.AddReference('System')
from System import *
", _scope);
}
public async Task<object?> ExecuteAsync(string code, ScriptContext context)
{
return await Task.Run(() =>
{
try
{
// 设置上下文
_scope.SetVariable("__context__", context);
_scope.SetVariable("__env__", context.Environment);
_scope.SetVariable("__args__", context.Arguments);
_scope.SetVariable("__dirname__", context.WorkingDirectory);
var scriptSource = _engine.CreateScriptSourceFromString(
code, Microsoft.Scripting.SourceCodeKind.Statements);
return scriptSource.Execute(_scope);
}
catch (Microsoft.Scripting.SyntaxErrorException ex)
{
throw new ScriptExecutionException($"Python syntax error: {ex.Message}", ex);
}
catch (Exception ex)
{
throw new ScriptExecutionException($"Python execution error: {ex.Message}", ex);
}
});
}
public Task<CompiledScript> CompileAsync(string code)
{
var scriptSource = _engine.CreateScriptSourceFromString(
code, Microsoft.Scripting.SourceCodeKind.Statements);
var compiledCode = scriptSource.Compile();
return Task.FromResult<CompiledScript>(
new PythonCompiledScript(compiledCode, _scope));
}
public void Dispose()
{
// IronPython引擎没有明确的Dispose方法
}
private class PythonCompiledScript : CompiledScript
{
private readonly Microsoft.Scripting.Hosting.CompiledCode _compiledCode;
private readonly Microsoft.Scripting.Hosting.ScriptScope _scope;
public PythonCompiledScript(
Microsoft.Scripting.Hosting.CompiledCode compiledCode,
Microsoft.Scripting.Hosting.ScriptScope scope)
{
_compiledCode = compiledCode;
_scope = scope;
}
public override Task<object?> ExecuteAsync(ScriptContext context)
{
return Task.Run(() =>
{
_scope.SetVariable("__context__", context);
_scope.SetVariable("__env__", context.Environment);
return _compiledCode.Execute(_scope);
});
}
}
}
// PowerShell 执行器
public class PowerShellExecutor : IScriptExecutor, IDisposable
{
private readonly IShellContext _shell;
public string LanguageName => "powershell";
public string[] FileExtensions => new[] { ".ps1", ".psm1", ".psd1" };
public PowerShellExecutor(IShellContext shell)
{
_shell = shell;
}
public async Task<object?> ExecuteAsync(string code, ScriptContext context)
{
return await Task.Run(() =>
{
using var powerShell = PowerShell.Create();
// 设置执行策略
powerShell.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned");
powerShell.Invoke();
powerShell.Commands.Clear();
// 设置变量
foreach (var kvp in context.Environment)
{
powerShell.Runspace.SessionStateProxy.SetVariable(kvp.Key, kvp.Value);
}
powerShell.Runspace.SessionStateProxy.SetVariable("Shell", _shell);
powerShell.Runspace.SessionStateProxy.SetVariable("Context", context);
powerShell.Runspace.SessionStateProxy.Path.SetLocation(context.WorkingDirectory);
// 执行脚本
powerShell.AddScript(code);
var results = powerShell.Invoke();
if (powerShell.HadErrors)
{
var errors = string.Join("\n",
powerShell.Streams.Error.Select(e => e.ToString()));
throw new ScriptExecutionException($"PowerShell errors:\n{errors}");
}
// 返回最后一个结果
return results.LastOrDefault()?.BaseObject;
});
}
public Task<CompiledScript> CompileAsync(string code)
{
// PowerShell通常是解释执行的,这里返回一个包装器
return Task.FromResult<CompiledScript>(new PowerShellCompiledScript(code));
}
public void Dispose()
{
// PowerShell对象在using语句中已清理
}
private class PowerShellCompiledScript : CompiledScript
{
private readonly string _code;
public PowerShellCompiledScript(string code)
{
_code = code;
}
public override Task<object?> ExecuteAsync(ScriptContext context)
{
using var powerShell = PowerShell.Create();
foreach (var kvp in context.Environment)
{
powerShell.Runspace.SessionStateProxy.SetVariable(kvp.Key, kvp.Value);
}
powerShell.Runspace.SessionStateProxy.Path.SetLocation(context.WorkingDirectory);
powerShell.AddScript(_code);
var results = powerShell.Invoke();
return Task.FromResult(results.LastOrDefault()?.BaseObject);
}
}
}
// Lua 执行器 (基于NLua)
public class LuaScriptExecutor : IScriptExecutor, IDisposable
{
private readonly IShellContext _shell;
private readonly Lua _lua;
public string LanguageName => "lua";
public string[] FileExtensions => new[] { ".lua" };
public LuaScriptExecutor(IShellContext shell)
{
_shell = shell;
_lua = new Lua();
InitializeEngine();
}
private void InitializeEngine()
{
// 注册全局函数
_lua.RegisterFunction("print", this, GetType().GetMethod(nameof(LuaPrint)));
_lua.RegisterFunction("shell", _shell, _shell.GetType().GetMethod("WriteLine"));
// 设置标准库
_lua.DoString(@"
math = require('math')
string = require('string')
table = require('table')
io = require('io')
os = require('os')
");
}
public static void LuaPrint(params object[] args)
{
Console.WriteLine(string.Join("\t", args));
}
public async Task<object?> ExecuteAsync(string code, ScriptContext context)
{
return await Task.Run(() =>
{
try
{
// 设置全局变量
_lua["_ENV"] = context.Environment;
_lua["_ARGS"] = context.Arguments.ToArray();
_lua["_CWD"] = context.WorkingDirectory;
return _lua.DoString(code)[0];
}
catch (NLua.Exceptions.LuaException ex)
{
throw new ScriptExecutionException($"Lua error: {ex.Message}", ex);
}
});
}
public Task<CompiledScript> CompileAsync(string code)
{
// Lua通常是解释执行的
return Task.FromResult<CompiledScript>(new LuaCompiledScript(code, _lua));
}
public void Dispose()
{
_lua?.Dispose();
}
private class LuaCompiledScript : CompiledScript
{
private readonly string _code;
private readonly Lua _lua;
public LuaCompiledScript(string code, Lua lua)
{
_code = code;
_lua = lua;
}
public override Task<object?> ExecuteAsync(ScriptContext context)
{
return Task.Run(() =>
{
_lua["_ENV"] = context.Environment;
_lua["_ARGS"] = context.Arguments.ToArray();
return _lua.DoString(_code)[0];
});
}
}
}
// 更多脚本引擎实现...
// 异常类
public class ScriptExecutionException : Exception
{
public ScriptExecutionException(string message, Exception? innerException = null)
: base(message, innerException)
{
}
}
public class ScriptEngineException : Exception
{
public ScriptEngineException(string message, Exception? innerException = null)
: base(message, innerException)
{
}
}
// 沙箱和安全相关类
public class ScriptSandbox
{
public async Task<T> ExecuteAsync<T>(Func<Task<T>> action, ScriptContext.SecurityPolicy policy)
{
// 实现资源限制和权限检查
using var cts = new CancellationTokenSource(policy.ExecutionTimeout);
try
{
return await Task.Run(async () =>
{
using var memoryLimiter = new MemoryLimiter(policy.MaxMemoryBytes);
return await action();
}, cts.Token);
}
catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
{
throw new TimeoutException($"Script execution timeout after {policy.ExecutionTimeout}");
}
}
public void CheckFileAccess(string filePath, ScriptContext.SecurityPolicy policy)
{
if (!policy.AllowFileSystemAccess)
{
throw new SecurityException("File system access is not allowed");
}
// 检查路径是否在允许的列表中
if (policy.AllowedPaths.Count > 0)
{
bool allowed = policy.AllowedPaths.Any(path =>
filePath.StartsWith(path, StringComparison.OrdinalIgnoreCase));
if (!allowed)
{
throw new SecurityException($"Access to '{filePath}' is not allowed");
}
}
}
}
public class MemoryLimiter : IDisposable
{
private readonly long _maxBytes;
private readonly long _initialMemory;
public MemoryLimiter(long maxBytes)
{
_maxBytes = maxBytes;
_initialMemory = GC.GetTotalMemory(false);
}
public void Check()
{
long currentMemory = GC.GetTotalMemory(false);
long usedMemory = currentMemory - _initialMemory;
if (usedMemory > _maxBytes)
{
throw new OutOfMemoryException(
$"Script exceeded memory limit: {usedMemory:N0} > {_maxBytes:N0} bytes");
}
}
public void Dispose()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
public class ScriptAssemblyLoadContext : AssemblyLoadContext
{
public ScriptAssemblyLoadContext() : base(isCollectible: true)
{
}
protected override Assembly? Load(AssemblyName assemblyName)
{
// 允许加载系统程序集
if (assemblyName.Name?.StartsWith("System.") == true ||
assemblyName.Name?.StartsWith("Microsoft.") == true)
{
return Assembly.Load(assemblyName);
}
// 对于其他程序集,可以实施安全策略
return null;
}
}
}
三、设计工具与流程
3.1 .NET企业级设计模式
3.2 C#/.NET特性应用
- 异步编程模式
public async Task<int> ExecuteCommandAsync(string command)
{
// 使用async/await进行异步操作
var parsed = await _parser.ParseAsync(command);
// 使用WhenAll并行执行多个任务
var tasks = parsed.Commands.Select(cmd => ExecuteSingleCommandAsync(cmd));
var results = await Task.WhenAll(tasks);
return results.LastOrDefault();
}
// 使用ValueTask优化性能
public async ValueTask<object?> EvaluateScriptAsync(string language, string code)
{
if (_compiledCache.TryGetValue(code, out var compiled))
{
return await compiled.ExecuteAsync();
}
// 编译并缓存
var newCompiled = await CompileAsync(language, code);
_compiledCache[code] = newCompiled;
return await newCompiled.ExecuteAsync();
}
- 记录类型和模式匹配
// 使用记录类型表示不可变数据
public record CommandResult(
int ExitCode,
string Output,
string Error,
TimeSpan ExecutionTime,
DateTime Timestamp)
{
public bool IsSuccess => ExitCode == 0;
}
// 使用模式匹配进行条件处理
public string FormatResult(CommandResult result) => result switch
{
{ IsSuccess: true, Output: var output } when !string.IsNullOrEmpty(output) =>
$"Success: {output}",
{ IsSuccess: true } => "Command completed successfully",
{ Error: var error } when !string.IsNullOrEmpty(error) =>
$"Error: {error}",
_ => $"Failed with exit code {result.ExitCode}"
};
- 依赖注入和配置
public static IServiceCollection AddDotNetShell(this IServiceCollection services,
IConfiguration configuration)
{
// 注册配置
services.Configure<ShellConfiguration>(configuration.GetSection("Shell"));
// 注册核心服务
services.AddSingleton<IShellContext, DotNetShell>();
services.AddSingleton<ProcessManager>();
services.AddSingleton<MultiScriptEngine>();
// 注册脚本引擎
services.AddSingleton<IScriptExecutor, CSharpScriptExecutor>();
services.AddSingleton<IScriptExecutor, JavaScriptExecutor>();
services.AddSingleton<IScriptExecutor, PowerShellExecutor>();
// 更多引擎...
// 注册内置命令
services.Scan(scan => scan
.FromAssemblyOf<IBuiltinCommand>()
.AddClasses(classes => classes.AssignableTo<IBuiltinCommand>())
.As<IBuiltinCommand>()
.WithSingletonLifetime());
return services;
}
- 源生成器优化
// 使用源生成器自动生成命令补全数据
[AutoCompleteSourceGenerator]
public partial class CommandCompleter
{
[AutoComplete("cd")]
public static IEnumerable<string> GetDirectoryCompletions(string partial)
{
return Directory.EnumerateDirectories(".", $"{partial}*")
.Select(Path.GetFileName)
.Where(name => name != null)!;
}
[AutoComplete("git")]
public static IEnumerable<string> GetGitCompletions(string partial)
{
return new[] { "clone", "pull", "push", "commit", "status" }
.Where(cmd => cmd.StartsWith(partial));
}
}
四、测试与验证
4.1 单元测试示例
// DotNetShell.Tests/ShellTests.cs
using Xunit;
using Moq;
using DotNetShell.Shell;
using DotNetShell.Core.Types;
namespace DotNetShell.Tests
{
public class ShellTests : IAsyncLifetime
{
private DotNetShell _shell;
private Mock<ILogger<DotNetShell>> _loggerMock;
public async Task InitializeAsync()
{
_loggerMock = new Mock<ILogger<DotNetShell>>();
var config = new ShellConfiguration
{
Prompt = "test> ",
EnableColors = false,
EnableCompletion = false
};
_shell = new DotNetShell(config, _loggerMock.Object);
}
public Task DisposeAsync()
{
return _shell.DisposeAsync().AsTask();
}
[Fact]
public async Task TestChangeDirectory()
{
// 测试cd命令
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
try
{
int exitCode = await _shell.ExecuteCommandAsync($"cd {tempDir}");
Assert.Equal(0, exitCode);
Assert.Equal(tempDir, _shell.CurrentDirectory);
}
finally
{
Directory.Delete(tempDir);
}
}
[Fact]
public async Task TestEnvironmentVariables()
{
// 测试环境变量设置
int exitCode = await _shell.ExecuteCommandAsync("set TEST_VAR=test_value");
Assert.Equal(0, exitCode);
string value = _shell.EnvironmentVariables["TEST_VAR"];
Assert.Equal("test_value", value);
}
[Fact]
public async Task TestScriptExecution()
{
// 测试C#脚本执行
int exitCode = await _shell.ExecuteCommandAsync("cs> 1 + 2 * 3");
Assert.Equal(0, exitCode);
}
[Fact]
public async Task TestPipelineExecution()
{
// 创建测试文件
string testFile = Path.GetTempFileName();
await File.WriteAllLinesAsync(testFile, new[] { "line1", "line2", "line3" });
try
{
// 测试管道命令
int exitCode = await _shell.ExecuteCommandAsync(
$"type {testFile} | findstr line2");
Assert.Equal(0, exitCode);
}
finally
{
File.Delete(testFile);
}
}
[Fact]
public async Task TestTimeout()
{
// 测试脚本超时
var config = new ShellConfiguration
{
ExecutionTimeout = TimeSpan.FromSeconds(1)
};
using var shell = new DotNetShell(config);
// 执行一个长时间运行的脚本
await Assert.ThrowsAsync<TimeoutException>(() =>
shell.ExecuteCommandAsync("cs> while(true) { }"));
}
}
}
// DotNetShell.Tests/ScriptingTests.cs
namespace DotNetShell.Tests.Scripting
{
public class ScriptingTests
{
[Fact]
public async Task TestCSharpScripting()
{
var shellMock = new Mock<IShellContext>();
var executor = new CSharpScriptExecutor(shellMock.Object);
object? result = await executor.ExecuteAsync("1 + 2 * 3", new ScriptContext());
Assert.Equal(7, result);
}
[Fact]
public async Task TestJavaScriptScripting()
{
var shellMock = new Mock<IShellContext>();
using var executor = new JavaScriptExecutor(shellMock.Object);
object? result = await executor.ExecuteAsync("3 * 4 + 5", new ScriptContext());
Assert.Equal(17.0, Convert.ToDouble(result));
}
[Theory]
[InlineData("csharp", "Console.WriteLine(\"Hello\");", null)]
[InlineData("javascript", "console.log('Hello');", null)]
[InlineData("python", "print('Hello')", null)]
[InlineData("powershell", "Write-Host 'Hello'", null)]
public async Task TestMultiLanguageSupport(string language, string code, object? expected)
{
var shellMock = new Mock<IShellContext>();
var engine = new MultiScriptEngine(shellMock.Object);
try
{
object? result = await engine.EvaluateAsync(language, code, new ScriptContext());
if (expected != null)
{
Assert.Equal(expected, result);
}
}
catch (NotSupportedException) when (language == "python" || language == "powershell")
{
// 某些引擎可能需要额外的依赖
Assert.True(true); // 跳过测试
}
}
}
}
4.2 性能基准测试
// DotNetShell.Benchmarks/ProcessBenchmarks.cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using DotNetShell.Process;
using DotNetShell.Core.Types;
namespace DotNetShell.Benchmarks
{
[MemoryDiagnoser]
[ThreadingDiagnoser]
public class ProcessBenchmarks : IAsyncDisposable
{
private ProcessManager _processManager;
private ParsedCommand _simpleCommand;
[GlobalSetup]
public void Setup()
{
_processManager = new ProcessManager(100);
_simpleCommand = new ParsedCommand
{
Arguments = new List<string> { "cmd.exe", "/c", "echo benchmark" },
RunInBackground = false
};
}
[Benchmark]
public async Task ProcessCreation()
{
using var process = await _processManager.CreateProcessAsync(_simpleCommand);
await _processManager.WaitForProcessAsync(process.Id);
}
[Benchmark]
public async Task PipelineCreation()
{
var commands = new List<ParsedCommand>
{
new() { Arguments = new List<string> { "cmd.exe", "/c", "echo test1\ntest2\ntest3" } },
new() { Arguments = new List<string> { "findstr", "test2" } }
};
var processes = await _processManager.CreatePipelineAsync(commands);
foreach (var process in processes)
{
await _processManager.WaitForProcessAsync(process.Id);
}
}
[Benchmark]
public async Task ScriptEvaluation()
{
var shellMock = new Mock<IShellContext>();
var executor = new CSharpScriptExecutor(shellMock.Object);
string code = @"
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
return sum;
";
await executor.ExecuteAsync(code, new ScriptContext());
}
public async ValueTask DisposeAsync()
{
await _processManager.DisposeAsync();
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<ProcessBenchmarks>();
Console.WriteLine(summary);
}
}
}
五、部署与使用
5.1 构建与安装
# 克隆项目
git clone https://github.com/yourname/dotnetshell.git
cd dotnetshell
# 恢复NuGet包
dotnet restore
# 构建项目
dotnet build --configuration Release
# 运行测试
dotnet test
# 发布独立应用
dotnet publish -c Release -r win-x64 --self-contained true
dotnet publish -c Release -r linux-x64 --self-contained true
dotnet publish -c Release -r osx-x64 --self-contained true
# 安装为全局工具
dotnet tool install -g DotNetShell
# 使用Docker构建
docker build -t dotnetshell .
docker run -it --rm dotnetshell
# 使用Kubernetes部署
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
5.2 配置文件
// appsettings.json
{
"Shell": {
"Prompt": "λ> ",
"HistoryFilePath": "~/.dnsh_history",
"HistorySize": 5000,
"EnableColors": true,
"EnableCompletion": true,
"MaxProcesses": 1000,
"ExecutionTimeout": "00:00:30",
"InitScriptPath": "~/.dnshrc",
"DefaultScriptLanguage": "csharp"
},
"Environment": {
"EDITOR": "code",
"PAGER": "less",
"LC_ALL": "en_US.UTF-8",
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
},
"Aliases": {
"ll": "dir /w",
"gs": "git status",
"gp": "git pull",
"gcm": "git commit -m"
},
"Scripting": {
"Languages": {
"csharp": {
"Enabled": true,
"AllowReflection": false,
"MaxMemoryBytes": 104857600
},
"powershell": {
"Enabled": true,
"ExecutionPolicy": "RemoteSigned"
},
"python": {
"Enabled": true,
"SearchPaths": [
"./python",
"~/.python"
]
},
"javascript": {
"Enabled": true,
"Timeout": 30000
}
},
"Security": {
"DefaultPolicy": {
"AllowFileSystemAccess": true,
"AllowNetworkAccess": false,
"AllowNativeCode": false,
"MaxMemoryBytes": 104857600,
"ExecutionTimeout": "00:00:30"
},
"RestrictedPolicy": {
"AllowFileSystemAccess": false,
"AllowNetworkAccess": false,
"MaxMemoryBytes": 10485760,
"ExecutionTimeout": "00:00:05"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
},
"File": {
"Path": "~/.dnsh/logs/dnsh.log",
"MaxSize": 10485760,
"MaxFiles": 10
}
},
"Remote": {
"Ssh": {
"Enabled": false,
"Port": 2222,
"AuthorizedKeysFile": "~/.ssh/authorized_keys"
},
"Web": {
"Enabled": false,
"Url": "http://localhost:8080",
"EnableWebSocket": true
}
}
}
六、总结与展望
6.1 系统特点
- 企业级.NET实现
· 完全的异步支持
· 强大的类型系统
· 完善的异常处理
· 丰富的生态系统 - 多语言脚本支持
· C#/VB.NET (Roslyn)
· PowerShell
· Python (IronPython)
· JavaScript/TypeScript
· Lua, Ruby, R等
· WebAssembly - 安全与隔离
· 代码沙箱
· 资源限制
· 权限控制
· 程序集隔离 - 现代化特性
· 异步流处理
· 模式匹配
· 记录类型
· 源生成器
6.2 .NET实现优势
特性 优势
跨平台 Windows/Linux/macOS全支持
高性能 AOT编译, 值类型, 内存池
安全性 托管代码, 内存安全, 类型安全
工具链 Visual Studio, JetBrains Rider
生态系统 NuGet包管理器, 丰富的库
云原生 Docker, Kubernetes, 微服务支持
6.3 扩展方向
- 更多脚本语言
· F#脚本支持
· Go (通过Go.NET)
· Rust (通过Rust.NET)
· SQL (Transact-SQL) - 云集成
· Azure Functions集成
· AWS Lambda支持
· 容器化部署
· Serverless架构 - AI增强
· 智能命令补全
· 自然语言命令
· 代码生成
· 自动文档 - 协作功能
· 实时协同编辑
· 终端共享
· 会话录制
· 审计跟踪
这个C# .NET实现的Shell系统充分利用了.NET生态系统的强大能力,构建了一个功能完整、安全可靠、性能优异的命令行环境。通过现代化的语言特性和企业级的设计模式,系统具有良好的可维护性、可扩展性和高性能,适合各种规模的企业应用场景。
更多推荐
所有评论(0)