问题
在使用winform进行上位机开发时,需要运行一段shell脚本程序,该脚本原本是运行在Linux设备中的,主要用于分析文本文件,用到了cat、awk、grep、sed四个常用 操作。
解决思路
在windows下,可以使用类UNIX模拟环境(如:Cygwin、msys等)来实现在 windows之下运行linux脚本。为了减少开发量,可以利用多进程方式调用模拟环境运行脚本,然后把标准输出显示出来。但是这些环境都比较庞大,把整个类UNIX模拟环境打包到上位机里面,将会大大增加软件包的尺寸。因此,需要提取用到的工具进行打包。
实践
执行命令行
执行命令行时,可以使用同步方式,等整个脚本执行结束时再返回,并把所有的输出一并返回;也可以使用异步方式,脚本中如果有标准输出,会实时把消息通过事件传出来。这里使用异步方式。
为方便操作,这里封装了一个异步的指令执行器:
class AsynCmdHandler
{
public delegate void CmdExitCallback();
public delegate void CmdOutputDataReceiver(string data);
public delegate void CmdErrorDataReceiver(string data);
private Process p = new Process();
private ProcessStartInfo si = new ProcessStartInfo();
/// <summary>
/// 命令行结束后返回
/// </summary>
public event CmdExitCallback FinishCallback;
/// <summary>
/// 标准输出数据接收事件
/// </summary>
public event CmdOutputDataReceiver OutputDataReceiver;
/// <summary>
/// 错误数据接收事件
/// </summary>
public event CmdErrorDataReceiver ErrorDataReceiver;
/// <summary>
/// 执行命令行指令
/// </summary>
/// <param name="cmd">指令内容,包括命令行参数</param>
public void RunCmd(string cmd)
{
var path = Path.Combine(Environment.SystemDirectory, @"cmd.exe");
si.FileName = path;
if (!cmd.StartsWith(@"/"))
{
cmd = @"/c " + cmd;
}
si.Arguments = cmd;
si.UseShellExecute = false;
si.RedirectStandardOutput = true;
si.RedirectStandardError = true;
si.RedirectStandardInput = true;
si.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo = si;
p.EnableRaisingEvents = true; // 该参数为true时,如果程序退出会通过CmdExited返回
p.ErrorDataReceived += CmdErrorDataReceived;
p.OutputDataReceived += CmdOutputDataReceived;
p.Exited += CmdExited;
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
}
/// <summary>
/// 写入“标准输入”数据
/// </summary>
/// <param name="data">数据内容</param>
public void Write(string data)
{
p.StandardInput.WriteLine(data);
}
/// <summary>
/// 强制退出进程,并资源
/// </summary>
public void Close()
{
if(p != null)
{
p.Kill();
p.Close();
p = null;
}
}
private void CmdExited(object sender, EventArgs e)
{
if(p!= null)
{
p.Close();
}
FinishCallback?.Invoke(); // 进程执行完毕,通知应用层
}
private void CmdOutputDataReceived(object sender, DataReceivedEventArgs e)
{
OutputDataReceiver?.Invoke(e.Data);
}
private void CmdErrorDataReceived(object sender, DataReceivedEventArgs e)
{
ErrorDataReceiver?.Invoke(e.Data);
}
}
运行shell脚本
shell精简
以下方法基于MSYS2环境,MSYS2是一个MSYS的独立改写版本,主要用于 shell 命令行开发环境。最新版本安装包90M,不过只需要用到其实中很少的几个文件。
其实,运行shell只需要使用sh.exe
就可以了,sh.exe
在msys2安装目录的bin目录下。
图1. sh.exe依赖
如图1所示,sh.exe
还依赖了msys-2.0.dll
文件,因此,最基本的shell运行环境,只需要用到这两个文件,把这两个文件拷贝出来。以下是一个最简单的helloword脚本hello.sh
:
echo 'Hello, World!'
用命令行sh.exe hello.sh
执行,可得到如下结果:
图2. 运行第一个脚本
运行的时候出现一个警告,只需要使用sh.exe创建一个/tmp
目录就可以,当然,如果知道/
目录在什么位置的话,也可以直接手动创建,本次测试sh.exe是放在桌面上,/
目录就是桌面,因此只需要在桌面上新那一个tmp
文件夹,警告就会消除。
为了方便管理,可以把sh.exe
和msys-2.0.dll
放到shell
文件夹中去。执行命令的时候使用如下执行:
shell/sh.exe hello.sh
对于mkdir
、awk
、grep
、cat
等指令,同样需要把可执行程序文件以及依赖的动态库拷贝过来。推荐使用Dependency Walker
来 查看可执行文件的依赖情况。