CMD를 자바코드로 구현해보자.

김용진·2025년 3월 23일
0

프로젝트

목록 보기
3/3
post-thumbnail

코드

package project.cmd;

import java.io.*;
import java.text.*;
import java.util.*;

public class FileApp {

	private static String path = "c:/";
	private static Scanner sc = new Scanner(System.in);


	// 파일 확인
	private static boolean validFile(File file, String dir) {
		return  file.exists() && file.isFile() && file.getName().equalsIgnoreCase(dir);
	}
	// 폴더 확인
	private static boolean validFolder(File file, String dir) {
		return  file.exists() && file.isDirectory() && file.getName().equalsIgnoreCase(dir);
	}
	// 디렉토리 확인
	private static boolean validDirectory(File file, String dir) {
		return file.exists() && file.getName().equalsIgnoreCase(dir);
	}
	// 읽기 확인
	private static boolean validRead(File file) {
		return file.exists() && file.canRead();
	}
		
	
	// 디렉토리 파일 출력 명령문
	private static void dir_cmd() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm");
		DecimalFormat df = new DecimalFormat("#,###");

		File temp = new File(path);
		File[] contents = temp.listFiles();
		
		
		long total = 0;
		System.out.println("경로 " + path);
		System.out.println();
		for (File file : contents) {
			System.out.printf("%-25s", sdf.format(new Date(file.lastModified())));
			if (file.isDirectory()) {
				System.out.printf("%-10s%-10s%-20s", "<DIR>", "", file.getName());
			} else {
				System.out.printf("%-10s%-10s%-20s", "", df.format(file.length()), file.getName());
				total += file.length();
			}
			System.out.println();
		}
		System.out.printf("       %d개 파일   %s 바이트 \n", contents.length, df.format(total));
	}
		
	
	// 전체 파일 출력명령문
	private static void dirAll_cmd() {
		File temp = new File(path);
		File[] contents = temp.listFiles();

		dir_cmd();
		 if (contents != null) {
			for (File file : contents) {
		        if (file.isDirectory()) {
		            path = file.getPath();
		            dir_cmd();
		        }
		        path = file.getParent();
			}
		 }
	}
	

	// 디렉토리 이동 명령문
	private static void cd_cmd(String[] arg) {
		if (arg.length != 2) {
			System.out.println("사용법 : cd 이동할 경로");
			return;
		}
		File file = new File(path + arg[1]);
		if (validDirectory(file,arg[1])) {
			path += arg[1];
		} else {
			System.out.println(arg[0] + "> 해당 폴더는 존재하지 않습니다.");
		}
	}

	// 이름 다시 짓기 명령문
	private static void rename_cmd(String[] arg) {
		if (arg.length != 3) {
			System.out.println("사용법 : rename 기존 파일명 변경할 파일명");
			return;
		}

		File file = new File(path +"/"+ arg[1]);
		if (validFile(file,arg[1]) || validFolder(file,arg[1])) {
			file.renameTo(new File(path +"/"+arg[2]));
		} else {
			System.out.println(arg[1] + "이름의 해당 파일은 존재하지 않습니다.");
		}
	}

	
	//복사 명령문
	private static void copy_cmd(String[] arg) throws Exception {
		if (arg.length != 3) {
			System.out.println("사용법 : copy 원본 파일 복사 파일명");
			return;
		}
		System.out.println(path+"/"+arg[1]);
		File file = new File(path+"/"+arg[1]);
		File new_file = new File(path+"/"+arg[2]);
		
		if (validFile(file,arg[1])) {
			copy(file, new_file);
		}else if(validFolder(file,arg[1])) {
			copyDirectory(file, new_file);
		}
		else {
			System.out.println(arg[1] + "이름의 해당 파일은 존재하지 않습니다.");
		}
	}
	
	// 파일 읽기 명령문
		private static void type_cmd(String[] arg) throws Exception {
			if (arg.length != 2) {
				System.out.println("사용법 : type 찾고자 하는 파일명");
				return;
			}
			File file = new File(path+"/"+arg[1]);
			if (validRead(file)) {
				BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
				while (true) {
					String str = br.readLine();
					if (str == null)
						break;
					System.out.println(str);
				}
				br.close();
			} else {
				System.out.println(arg[1] + "이름의 해당 파일은 존재하지 않습니다.");
			}
		}
	
	// 순수 파일 복사 
		private static void copy(File dir, File new_dir) throws IOException{
			try (FileInputStream fis = new FileInputStream(dir);
				     FileOutputStream fos = new FileOutputStream(new_dir)) {
				    byte[] buffer = new byte[4096];
				    int bytesRead;
				    while ((bytesRead = fis.read(buffer)) != -1) {
				        fos.write(buffer, 0, bytesRead);
				    }
				    fos.flush();
				}
		}

		// 파일이 아닌 디렉토리일 경우 넘어가는 코드
		private static void copyDirectory(File dir, File new_dir) throws IOException {
	        File[] children = dir.listFiles();
	        
	        if (children != null) {
	            
	        	for (File child : children) {
	                File destChild = new File(new_dir, child.getName());
	                
	                if (child.isDirectory()) {
	                    destChild.mkdirs();
	                    copyDirectory(child, destChild);
	                } else {
	                    copy(child, destChild);
	                }
	                
	            }
	        }
	        
	        
	    }
	

	public static void main(String[] args) throws Exception {
		while (true) {
			System.out.print(path + ">");
			String str = sc.nextLine();
			if ("exit".equals(str)) {
				// 프로그램 종료
				System.exit(0);
			}
			String[] arg = str.split(" ");
			switch (arg[0]) {
			case "cd": {
				// 이동하기
				cd_cmd(arg);
				break;
			}
			case "dir": {
				// 파일 내용 출력
				dir_cmd();
				break;
			}
			case "*": {
				// 파일 전체 내용 출력
				dirAll_cmd();
				break;
			}
			case "rename": {
				// 파일 이름 다시 만들기
				rename_cmd(arg);
				break;
			}
			case "copy": {
				// 파일 카피
				copy_cmd(arg);
				break;
			}
			case "type": {
				// 타입 파일오픈해서 열기
				type_cmd(arg);
				break;
			}
			default:
				throw new IllegalArgumentException("Unexpected value: " + arg[0]);
			}
			
		}
	}
}

프로젝트로 만든 cmd를 자바 코드로 구현하는 토이 프로젝트다.

링크

깃 허브

뭘 만들었는가?

최대한 cmd에서 기능하는 기능을 구현하려고 만든것들 이며
만든것은

  • 현재 디렉토리 파일 확인
  • 현재 디렉토리+하위 디렉토리 파일확인
  • 파일 카피
  • 폴더 카피
  • 메모장 열어서 콘솔에 노출하기
  • 디렉토리 이동

이렇게 6가지를 구현하였다.

만들면서 직면한 문제?

구현하면서 가장 까다로웠던 것은 파일 경로확인, 재귀함수 구현, 파일 복사 저장타입 문제 였던것 같다.

파일 경로는 각 기능에서 올바른 경로를 받아와야 inputstream이나 File 라이브러리가 제대로 작동 하기 때문에 systeam.out을 통해서 매 동작마다 올바르게 받아 오는지 확인 하는 작업을 수행했다.

덕분에 path부분을 난잡하게 만들어서 매우 아쉽다. 이부분을 좀 더 다듬는다면 좀 더 직관적인 코드가 만들어 지지 않을까? 하는 생각이다.

파일 복사의 경우 UTF-8저장만 고려하면 되는게 아닐까? 하는 생각으로 아무 생각없이 BufferedReader를 사용해서 만들었다. OutputStream만 하면 끝나겠지 하고 만들었더니, ANSI는 둘째치고 windows로 저장되었던게 복사를 하니 unix로 만들어지는 불상사가 생겼다.

분명 windows인데... 저장하니까 Unix라고!?

보통 이러면 무슨 문제가 생기는지 한번도 고려한적이 없었다

그러나 저장된 파일을 열어보니 위와 같이 불필요한 기호가 생겼다.

무지몽매한 나의 지식으로는 한계가 있어 gpt의 도움을 받으니 다음과 같은 말을 알려줬다

BufferedReader의 readLine() 메서드는 한 줄씩 읽을 때 줄바꿈 문자를 제거합니다.
즉, 원본 파일이 Windows 스타일의 줄바꿈(CRLF, "\r\n")을 가지고 있더라도, readLine()은 이를 제거하고 순수 텍스트 라인만 반환합니다.
그 후에 OutputStream(또는 OutputStreamWriter)으로 "\n"을 추가하면, 이는 Unix 스타일의 줄바꿈(LF)으로 처리되므로 결과 파일이 Unix 스타일로 만들어지는 것입니다.

그렇다, readLine()메서드를 사용해서 값을 불러오고 그걸 하나씩 읽어 저장하는 방식을 사용했더니 줄바꿈이나 기타 명령문을 전부 지우고 저장하는, unix방식으로 저장해버리는 것이였다.

게다가 이러한 방식으로는 메모장같은건 만들지 몰라도 이미지는 복사를 못하는걸 알게 되었다.

그럼 다른 방식을 하면되는게 아닌가? byte단위로 쪼개서 저장하고 그걸 읽어오면 되는게 아닌가? 라는 생각을 했고

private static void copy(File dir, File new_dir) throws IOException{
			try (FileInputStream fis = new FileInputStream(dir);
				     FileOutputStream fos = new FileOutputStream(new_dir)) {
				    byte[] buffer = new byte[4096];
				    int bytesRead;
				    while ((bytesRead = fis.read(buffer)) != -1) {
				        fos.write(buffer, 0, bytesRead);
				    }
				    fos.flush();
				}
		}

위와 같이 바이트를 담을 배열을 만들어서 이를 이용해서 버퍼에 담아 read하도록 만들었더니 성공적으로 담아왔다.

이미지 역시 깨지지 않고 잘 가져왔다 매우 성공적이였다.

마지막으로 폴더를 복사하는것에 대해서 문제가 생겼었다.
파일이야 어차피 1개를 복사하는 간단(?)한 문제였으나 폴더를 복사하기 위해서는 폴더 안에 파일리스트들의 경로를 읽어서 다시 카피명령문으로 보내야했기 때문에 재귀함수가 필요했다.

거기서 폴더명은 바뀌는 폴더명으로 바뀐다 할지라도, 안의 파일명은 바뀌면 안돼는 조건이 있다는걸 인지하고 만들어야 했다.


		if (validFile(file,arg[1])) {
			copy(file, new_file);
		}else if(validFolder(file,arg[1])) {
			copyDirectory(file, new_file);
		}

일단은 이것이 파일인지, 폴더인지 확인하는 if문을 만들었고 그에 따라 다음 메서드로 보내도록 만들어 구분을 지어줬다.

// 파일이 아닌 디렉토리일 경우 넘어가는 코드
		private static void copyDirectory(File dir, File new_dir) throws IOException {
	        File[] children = dir.listFiles();
	        
	        if (children != null) {
	            
	        	for (File child : children) {
	                File destChild = new File(new_dir, child.getName());
	                
	                if (child.isDirectory()) {
	                    destChild.mkdirs();
	                    copyDirectory(child, destChild);
	                } else {
	                    copy(child, destChild);
	                }
	                
	            }
	        }
	        
	        
	    }

다음 디렉토리를 받아 파일 리스트를 만들고, 리스트를 for문으로 돌릴 때 그것이 폴더일 경우 mkdirs()메서드로 만들고, 다시 이 경로들을 업데이트해서 보냈고, 파일일 경우 카피하도록 copy메서드로 보냈다.

이렇게 함으로서 리스트가 제대로 반영 되게 만들었다.

아쉬운점

토이 프로젝트로서 정해진 시간에 구현해야하다보니 파일 복사명령문 중복처리를 고려를 못했다. 원래는 '파일명 - 복사본' 이렇게 저장되도록 유도하는 명령문을 처리했어야 했는데 그게 아쉽다...

두번째로는 dir 명령문 보낼때와 dir /s 명령문 처리다. 원래대로라면 arg[1]에 값이 /s인지, 비어있는지 확인하고 전체를 불러올지, 혹은 현재 디렉토리만 서칭할지 만들어야했다 사실 머리속으로는 인지했는데 이것도 나의 나태함이 코드를 못짠게 아닐까.. 한다

세번째로는 path처리다, 현재 코드를 보면 알다시피 어떤건 /를 붙이고 어떤건 덜 붙이고 난잡하다. 이를 통일한다면 하나의 메서드로 처리할 수 있을것 같은데 이부분도 고려를 더 해봐야 할 것 같다.

네번째로는 다형성 부여다 boolean으로 처리하는 명령문이 사실 조건만 다르고 나머진 같은 형식이라서 추상메서드를 구현해보고 싶었다. swith-case문 역시 가능하다고 들었는데 나의 지식이 아직은 모자른것 같다.

다섯번째로는 gpt의존인것같다. 코드공부를 허투루한건지 머릿속에 알고리즘이 딱하고 나오질 못하고있다. 나도 안보고 샤샤샥 하고 코드를 짜고싶은데 한참 모자르다 ㅜㅜ

profile
메모리폼

0개의 댓글