基本思路:
首先我们要知道什么是shell。在linux中,shell只是一个操作系统的外壳,用户在命令行中输入命令,经由shell传入操作系统中调用系统函数。说具体点,就是输入一条指令,如ls命令,系统是调用/usr/bin文件夹下的ls文件进行操作的,所以系统没有找不到这些命令对于的文件就报错。
所以我们做一个shell接口,就是通过java新建一个子进程,命令从父进程传给子进程,子进程帮我们去执行,然后将执行的结果放回给父进程打印出来。这就是这个实验的原理。
实验主要有三部分:
1.创建外部进程:
java中有两种方式:用ProcessBuilder类的start方法。用Runtime类的exec方法。本实验用的是前两种。
执行命令的时候,我们要向ProcessBuilder传递参数,第一个参数需要是程序名,如:/usr/bin/ls等。
我们看下面的代码:
ProcessBuilder pb = new ProcessBuilder();
ArrayList<String> param = new ArrayList<>();
param.add("/usr/bin/"+strs[i]); //注意,前缀/usr/bin/。第一个参数需要类似/usr/bin/ls的,所以要拼接一下
param.add(...) //...是要加入的参数
pb.command(param); //添加参数
我们要实现的是输入命令ls,子进程去调用程序,返回结果回来。我们执行命令的程序都在/usr/bin中,所以要加统一的前缀。
2.更改目录:
因为要修改工作目录,所以调用ProcessBuilder directory(File Directory)方法来修改目录
3.添加历史记录特性
用一个ArrayList变量来记录历史命令即可。
具体java代码如下:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.io.File;
public class SimpleShell {
public static void main(String[] args) throws IOException {
String commandLine = null;
Process pro = null;
int flag = 0;
ArrayList<String> history = new ArrayList<>(); //保存指令历史
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
ProcessBuilder pb = new ProcessBuilder();
while(true){
if(flag == 0){ //如果标记为0,则正常执行;标记为1,则不用接收输入
System.out.print("jsh>");
commandLine = console.readLine().trim(); //删除前后空格
}
if(commandLine.equals("")){ //遇到空,继续
continue;
}
if(commandLine.equals("exit")){ //退出
return;
}
ArrayList<String> param = new ArrayList<>();
//param.add("/c");
history.add(commandLine); //保存指令
if(commandLine.equals("cd")){ //若输入的命令是cd,切换到home目录下
pb.directory(new File(System.getProperty("user.dir")));
System.out.println(System.getProperty("user.dir"));
flag = 0; //设置标记值
continue;
}else if(commandLine.length()>2
&&commandLine.substring(0,2).equals("cd")
&&commandLine.charAt(2) == ' '){ //命令是cd dir 格式的
String dirName = ""; //默认格式是 cd /home
if(commandLine.length()>3&&commandLine.charAt(3) != '/'){ //若格式是cd test
dirName = System.getProperty("user.dir")+"/";
}
pb.directory(new File(dirName+commandLine.substring(3)));
System.out.println(dirName+commandLine.substring(3));
flag = 0; //设置标记值
continue;
}else if(commandLine.equals("history")){ //若是history指令,则输出历史
for(int i=0;i<history.size();i++){
System.out.println(i+" "+history.get(i));
}
flag = 0; //设置标记值
continue;
}else if(commandLine.equals("!!")){
if(history.size() == 1){
System.out.println("no history command!");
flag = 0; //设置标记值
}else{
commandLine = history.get(history.size()-2);
flag = (flag == 1?0:1); //设置标记值
}
continue;
}else if(commandLine.charAt(0)=='!'){
//转换命令后的数字
String numStr = commandLine.substring(1);
int num = 0;
try {
num = Integer.parseInt(numStr);
} catch(Exception e) {
System.out.println("error:invaild param");
flag = 0; //设置标记值
continue;
}
if(num>=0 && num<(history.size()-1)){ //!后面的数字需要符合条件
//将指令替换为历史指令
commandLine = history.get(num);
flag = (flag == 1?0:1); //设置标记值
continue;
}else{
flag = 0; //设置标记值
}
}
flag = 0; //设置标记值
String[] strs = commandLine.split(" ");
//添加到参数中
for(int i=0;i<strs.length;i++){
if(i==0){
param.add("/usr/bin/"+strs[i]);
}else{
param.add(strs[i]);
}
}
pb.command(param); //添加参数
pb.redirectErrorStream(true); //合并正确流和错误流
try {
pro = pb.start();
} catch(IOException e) {
System.out.println("error:invaild input");
continue;
}
BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream()));
// BufferedReader brError = new BufferedReader(new InputStreamReader(pro.getErrorStream()));
String line;
// if(brError.readLine() != null){ //子程序执行出错
// System.out.println("error:invaild input");
// brError.close();
// }else{ //输出子程序的结果
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
// }
}
}
}