이번 윈도우 10 레드스톤부터 윈도우 10 내에 내장된 bash shell (우분투)를 지원한다는 소식은 알 만한 분들은 이미 다 알고 계실 것 같습니다.


이 글에서는 그 우분투의 설치 및 bash 실행에 대한 방법과, 또 해당 우분투를 최신 배포판으로 업그레이드하는 방법, 마지막으로 우분투 이미지를 (재설치 등의 이유로) 제거하는 방법을 소개하려 합니다.








Bash on Windows (우분투) 설치





1. '제어판 - 프로그램 - 프로그램 및 기능 - Windows 기능 켜기/끄기 - Linux용 Windows 하위 시스템' 체크 후 확인 버튼 클릭합니다.


Windows 기능 창


이후 재부팅 안내가 나오면 재부팅을 한 후 기다립니다.





2. cmd나 PowerShell 둘 중 아무 거나 실행시킨 후 'bash' 명령을 입력합니다.

(또는 그냥 '윈도우 키'를 누른 후 바로 'bash' 를 쳐서 (즉, 검색창에 bash를 검색해서) 바로 명령 실행해도 됩니다)



처음 실행 시 초기 설치가 진행됩니다.


(1) Windows 계열 Shell prompt를 통해 실행시키는 화면


(2) 검색창 (윈도우 키를 누른 후 'bash' 타이핑)을 통해 실행시키는 화면



도중에 계정 이름과 암호를 묻게 되면 원하는 아이디와 패스워드를 입력합니다.






3. 설치가 끝나면 정상적으로 우분투 14.04 가 설치된 것을 확인할 수 있습니다.






(번외 ps. 'C 드라이브'와 같은 각 로컬 파티션 ('내 컴퓨터'의 '로컬 디스크') 들은 /mnt 경로에 해당 드라이브 문자로 마운트되어 있습니다)






Bash on Windows (우분투) 배포판 업그레이드





14.04도 꽤 괜찮은 배포판이지만, 아무래도 수 년 전의 구형 배포판이다 보니 최신 버전의 유틸리티가 지원되지 않는 경우도 많고 해서 (...혹시 직접 소스를 컴파일해 쓰신다면 말리지는 않겠지만...) 업그레이드해야 할 필요성을 느낄 수 있습니다.


윈10 레드스톤 초기 때는 배포판 업그레이드 시도가 실패했는데, 이번에는 큰 문제 없이 되는 것 같아 올려봅니다.

(※ 다만 부작용은 있습니다. 아래에 설명해 두겠지만, 그 점 감안해서 진행해주시기 바랍니다.)




1. /etc/apt/sources.list 파일을 수정하기 위해 에디터로 엽니다. (에디터는 편한 것으로 사용하세요)


우분투 설치 시 기본으로 있는 vim 에디터 사용.

단 처음 보는 사람에겐 그다지 권장하지 않는 에디터이다




2. 'trusty' 부분을 원하는 배포판 코드네임(예 : Ubuntu 16.04의 경우 'xenial')으로 바꿉니다.


출시 시기(Version)에 따른 코드네임(Adjective)https://wiki.ubuntu.com/DevelopmentCodeNames 에서 확인할 수 있습니다.


수정 전 스샷



아래는 수정 후의 스샷입니다. 우분투 16.04 으로 올리고 싶었으므로 'trusty' 를 모두 'xenial' 로 바꾸었습니다.


(다만 리포지토리 자체도 아무래도 외국 서버보다 상대적으로 속도가 빠른 한국 미러로 바꾸었는데, 바꾸시려는 분은 아래 스샷의 주소를 참고하시기 바랍니다)


수정 후 스샷





3. 아래의 명령을 실행합니다. 진행 중 아래에 있는 스샷과 같이 몇몇 오류 메세지가 뜰 수 있습니다.


sudo apt update

sudo apt full-upgrade


에러 메세지


업그레이드 완료 화면




4. 모든 설치가 끝나고 exit 후 다시 bash 로 들어오면 해당 버전의 배포판이 설치된 것을 알 수 있습니다.


버전 확인


16.04 기준 gcc 최신 버전이 설치된 모습. 14.04 버전에서는 apt를 이용해 설치될 수 없는 버전이다


추후 개인적으로 별도 커스텀 prompt를 적용한 모습






※ 배포판 업그레이드 시 주의 및 참고 사항



1. 이런 업그레이드 작업 자체가 윈도우 내의 '색다른'(?) 변종 리눅스 내에서 이뤄진 탓에, 오류가 있을 수 있으며 정상 작동 보증을 받기는 힘듭니다.


대표적으로 제가 겪고 있는 문제 중 하나는 sudo 첫 실행 시 'sudo: no tty present and no askpass program specified' 라는 에러를 내고 아무 작업도 실행되지 않는 것인데, 아무래도 업데이트 후 내부적인 tty 동작에 무언가 문제가 있는 것 같습니다.


이 문제의 해결책은 아래와 같습니다.


sudo -S [명렁어]


업데이트 후 sudo 가 생각대로 잘 되지 않는다


이 문제는 sudo 를 처음 실행했을 때 password 를 입력받아야 하는데, Bash on Windows 의 어떤 내부 사정으로 인해 tty로부터 password 를 입력받을 수 없는 상황이 원인입니다. sudo 중 -S (대문자 S) 옵션은 sudo가 password 를 tty 로부터가 아닌 stdin (즉 현재 입력 중인 터미널) 로부터 직접 받을 수 있게 해 줍니다.


따라서 처음 sudo를 실행했을 때 password 를 입력해야 하는데, 그걸 'sudo -S' 와 같이 실행시키면 정상적으로 password 를 받아 실행되는 것을 볼 수 있습니다.



일단 한 번 sudo -S 를 사용해서 입력받은 password 가 유지되는 상황이라면 (sudo 실행 때마다 매번 password를 받지는 않죠), 당분간은 그냥 원래의 sudo 를 계속 사용할 수 있습니다. 즉 sudo -S 는 '패스워드를 입력해야 할 필요가 있을 경우'에만 사용하면 됩니다.

간단하게는 sudo 실행 시 저런 에러 메세지가 뜬다면 그 때만 -S 옵션을 붙여 재실행하면 되겠죠.



저 -S 옵션에 관해서 일일히 신경쓰기 싫다면 ~/.bashrc (현재 유저만 적용) 또는 /etc/bash.bashrc (모든 유저에게 적용하고 싶을 경우) 파일 맨 아래쪽에 아래와 같은 명령어 한 줄을 추가하셔도 됩니다.


alias sudo='sudo -S'


bashrc 파일 내에 적용된 alias


이렇게 하면 해당 유저가 실행한 bash 안에서는 'sudo' 가 'sudo -S'로 자동으로 변환 인식됩니다. 그냥 평소처럼 sudo 라고만 쓸 수 있게 되죠.


다만 위 에러 내용을 보았을 때, tty 를 사용하는 유틸리티는 비슷한 문제를 발생시킬 가능성이 있다고 볼 수 있습니다. 최신 배포판으로 업데이트하기 전에 관련 내용을 확인해 둬야겠죠.




이처럼 배포되는 변종 14.04 외에는 마소&캐노니컬 에서 정식 지원되지 않는 범위라 예상치 못한 다양한 문제가 있을 수 있으니, 그런 문제가 발생하면 다시 윈도우 정식 배포판으로 내려올 생각은 미리 해 두시는 게 좋습니다.






2. 커널 업그레이드는 안 되는 것으로 보입니다.


일단 apt install 로 최신 커널 설치 자체는 되는데, 문제는 윈도우 계열 쉘 프롬프트에서 'bash' 를 입력해서 실행시키는 것 자체가 이미 초기 커널을 베이스로 하는 것을 기본값으로 두고 있기에 설치된 커널로 '부팅'하는 게 불가능합니다.


뭐 사실 애초에 기존 커널 자체도 윈도우 내에서 돌아가기 위해 상당한 깊이로 수정되었을 것이기에, 설사 새 커널로 바꿀 수 있도록 지원한다 하더라도 실제로는 안 됐을 가능성이 높았을 것 같군요.








Bash on Windows (우분투) 이미지 삭제 (및 재설치)




우분투를 재설치해야 하는 경우가 있을 수 있습니다. 또는 더이상 사용하지 않지만 이미지 용량 때문에 삭제를 해야 할 수도 있겠죠. 이 경우 처음의 제어판에 있던 '프로그램 기능'에서 체크를 해제하는 것만으로는 이미지가 삭제되지 않습니다. 별도의 명령어를 통해 삭제해줘야 합니다.



삭제하는 명령어는 간단합니다. cmd나 PowerShell 에서 아래의 명령어를 치면 됩니다.


lxrun /uninstall /full


우분투 이미지 삭제 화면


y 를 입력하고 엔터를 치면 빠르게 우분투가 제거됩니다.


이후 재설치를 원한다면 처음처럼 그냥 'bash' 를 실행시키면 다시 설치 안내가 뜨며 이미지를 다운로드 받는 과정부터 시작하게 됩니다.



저작자 표시 비영리
신고

설정

트랙백

댓글

리눅스(라즈베리파이)에 pptp 서버를 임시로 구축해봤는데, 서버를 설치하여 실행은 잘 되지만 시스템을 다시 시작했을 경우에 서비스가 자동으로 시작이 되지 않았습니다.


재부팅 후, pptp 서비스를 이용한 접속을 시도했지만 실패하였




여기에는 chkconfig (1) 을 이용한 방법과, systemctl (2) 을 이용한 방법이 있습니다.


한 쪽은 특정 시스템에서 지원되지 않거나 등의 문제가 있을 수 있습니다. (예를 들어 제 경우, 라즈베리파이 환경에서 chkconfig 을 통한 pptpd 자동 시작이 되지 않아 systemctl 을 이용한 방법으로 해결했습니다)





1. chkconfig 을 이용한 방법


sudo chkconfig [서비스명] on





chkconfig --list [서비스명] 로 확인해 보면 위 스샷과 같이 2~5 runlevel 에서 on (자동 시작) 되어있는 걸 확인할 수 있습니다.







2. systemctl 을 이용한 방법


sudo systemctl enable [서비스명]





재부팅을 해 보면




서비스가 잘 활성화되어 있음을 확인할 수 있습니다.







재부팅 후 별도 작업을 하지 않아도 pptp 연결이 잘 되고, 따라서 서비스 실행이 자동으로 되었음을 확인할 수 있습니다.

저작자 표시 비영리
신고

설정

트랙백

댓글

윈폼 프로그래밍을 하다 한 가지 난관에 봉착했었는데, 바로 어떤 패키지를 사용하기 위해서는 '목표 라이브러리 (dll)'이 들어있는 디렉터리의 수동 설정이 필요하다는 점이었습니다.



디렉터리를 설정하는데 직접 선택할 수 있게만 해 두었다!

이런 망할 X들...



저 부분(VlcLibDirectory) 자체는 DirectoryInfo 형식으로, 저기에서 경로를 선택하면 해당 경로가 DirectoryInfo 형식으로 만들어진 후에 변환을 거쳐 .resx 파일 내에 저장이 되는 것을 알았습니다.

그래서 해당 정보가 저장되어 있는 .resx 파일을 열어보았으나 알 수 없는 문자열의 연속이 데이터로 저장되어 있을 뿐이었습니다.

(예 : AAEAAAD/////AQAAAAAAAAAEAQAAABdTeXN0ZW0uSU8uRGlyZWN0b3J5SW5mbwIAAAAMT3JpZ2luYWxQYXRoCEZ1b

GxQYXRoAQEGAgAAAAZDOlx2bGMJAgAAAAs=)


이 문자열 자체가 c# 내의 어떤 object가 변환된 상태이므로, 사용자 경로로 초기화를 시킨 DirectoryInfo 를 따로 저런 텍스트로 변환한 뒤에 직접 .resx 파일 내에 입력해주면 데이터가 들어가지 않을까 생각을 했었습니다.


그리고 들어갔습니다.(!)

많은 삽질을 거듭했었지만...


그리하여 그 변환 소스 코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
private System.String ConvertToBase64String(System.Object input)        
{            
    System.IO.MemoryStream tmpstream = new System.IO.MemoryStream();
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bin_formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 
    bin_formatter.Serialize(tmpstream, input);
    System.Byte[] byte_arr = tmpstream.ToArray();
    System.Char[] char_arr = new System.Char[1024];
    System.Convert.ToBase64CharArray(byte_arr, 0, byte_arr.Length, char_arr, 0);
    System.String result_str = new System.String(char_arr);
 
    return result_str;
}
 
cs



string result_string = ConvertToBase64String(원하는_object); 이렇게 사용하면 원하는 object (제가 알기로 모든 type, 즉 int, string 등등은 물론이고 DirectoryInfo 등등도 포함) 가 대응하는 string으로 변환됩니다. 그리고 이 출력된 코드를 복사한 뒤 .resx 파일을 메모장 등등으로 열고,




위 스샷처럼 value 안에 넣으면 정상 적용됩니다. 원하는 object 자체가 resource 파일(.resx) 안에 포함된 셈이죠.


(이 경우에만 테스트해 보았고 급하게 문제 해결을 위해 작성한 코드라, 범용성이나 코드 완전성은 떨어질 수 있습니다)

저작자 표시 비영리
신고

설정

트랙백

댓글

우분투 15.04 설치 직후, 초기 상태인데도 기본 설치된 오피스인 리브레오피스(LibreOffice)를 실행하자마자 GUI 화면이 crash를 일으키며 강제로 로그인 창으로 돌아가는 현상이 있었습니다. (이 프로그램 뿐만 아니라 emacs를 GUI로 사용할 때 등 같은 문제가 발견되고는 했습니다.)


증상 발생 시 다른 tty은 이상이 없는데 GUI만 문제가 있는 것을 보아 Xorg 관련 문제로 예상되었고, 확인 결과 xorg의 ATI 드라이버가 문제점이었습니다.


이 문제는 우분투 15.04 + AMD (ATI) 그래픽카드 조합에서 발생하였다는 점을 참고하시기 바랍니다.






해결 방법



1. 우선 grub 화면에서 [Ubuntu 고급 설정] 안에 있는 가장 최신 커널 버전의 '(recovery mode)' 로 부팅합니다. 도중에 무언가 선택하는 화면이 나온다면 'resume' 을 선택하여 recovery mode 부팅을 계속하세요.



2. [시스템 설정] - [소프트웨어 & 업데이트] - [추가 드라이버] 에서, fglrx-updates 를 선택합니다. 그 후 '바뀐 내용 적용'을 눌러 적용이 완료될 때까지 기다립니다.






3. 터미널을 연 후, xserver-xorg-video-ati 를 삭제합니다.


sudo apt-get purge xserver-xorg-video-ati





4. 일반 부팅 모드로 재부팅합니다.


저작자 표시 비영리
신고

설정

트랙백

댓글

Visual Studio 에서 유니버설 앱(Universal App) 프로젝트 생성 후 작업을 하면, 컴파일 때마다 다음과 같은 warning이 뜰 때가 있습니다.





APPX4001: Build property AppxBundlePlatforms is not explicitly set and is calculated based on currently building architecture. Use 'Create App Package' wizard or edit project file to set it.



원인은 간단한데, 말 그대로 'AppxBundlePlatforms' 가 설정되지 않았다는 문제입니다.







해결책



1. '프로젝트 > 스토어 > 앱 패키지 만들기' 에서 설정하여 경고 제거 (개발자 계정 필요)



2. 직접 해당 프로젝트.csproj 파일을 열어, <PropertyGroup></PropertyGroup> 안에

<AppxBundlePlatforms>neutral</AppxBundlePlatforms>

문구를 삽입




저작자 표시 비영리
신고

설정

트랙백

댓글

코드 작성 중 간단한 예제를 저장해둡니다.



뷰의 터치가 되고 있는지 안 되고 있는지에 따라 동작하는 터치 리스너




1. OnTouchListener 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private View.OnTouchListener touchListener = new View.OnTouchListener() {
 
    public boolean onTouch(View v, MotionEvent event) {
 
        switch (event.getAction()) {
 
            // if pressed
            case MotionEvent.ACTION_DOWN: {
 
        /* 터치하고 있는 상태 */
 
                break;
            }
 
            // if released
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP: {
 
        /* 터치가 안 되고 있는 상태 */
 
                break;
            }
 
            default: {
                break;
            }
        }
 
        return false;   // false : 이 OnTouchListener 이후에도 다른 Listener들이 동작하게 함 (OnClickListener 등등)
    }
};
cs


위에서 MotionEvent.ACTION_CANCEL 이걸 빼먹는 예제들이 있어서 뻘 고생 좀 했는데..

저 ACTION_CANCEL 을 추가해야 단순히 뷰 위에서만 터치가 떨어지는 것 뿐만이 아니라(ACTION_UP), 터치하던 중 드래그같은 동작으로 뷰 범위를 벗어나거나 하는 '터치 취소'의 경우(즉 우리가 인식하는 터치가 취소되었다 하는 모든 경우)도 모두 잡아내더군요.




2. OnTouchListener 부착

1
(리스너를 부착할 뷰 인스턴스).setOnTouchListener(touchListener);
cs




이후 터치가 될 때마다 동작이 됩니다.


저작자 표시 비영리
신고

설정

트랙백

댓글

리눅스 우분투에서 배터리 잔량 등의 정보를 확인하는 방법 중, 터미널(CLI)로 확인하는 방법은 다음과 같습니다.



유틸리티 upower 를 활용하여 확인이 가능한데,


1. upower -e



먼저

upower -e

를 입력하면 배터리 정보를 보는 데 필요한 경로를 표시해줍니다.



2-1. upower -i [경로]



그 다음

upower -i [경로]

로 해당 배터리의 상태를 자세히 확인할 수 있습니다.



2-2. upower -i [경로] | grep percentage



또는 간단히 배터리의 특정 상태만 표시하고 싶다면 grep을 이용하면 됩니다. 예를 들어 위 경우 배터리 잔량을 확인하고 싶다면,

upower -i [경로] | grep percentage

만 입력하면 간단하게 배터리 잔량만 표시되게 됩니다.



※ 이 모든 게 귀찮고 한 번에 바로 알 수 있는 방법 없나 하시면 upower -e | upower -i | grep percentage 를 입력하셔도 됩니다. 다만 단순히 이렇게 실행하면 원하는 정보를 얻지 못할 가능성도 있기에..


저작자 표시 비영리
신고

설정

트랙백

댓글

[프로그래밍] 급여 지급 프로그램



다운로드


- 첨부파일 :

team_11_matlab_proj_v1.2.zip





개요


수십 시간을 넘게 들여 팀 프로젝트로 제출한 프로그램.

다만 제출이 목적이라 좀 발적화의 부분이라든지 땜빵 코드가 몇 있을 수는 있으나, 기능상의 문제는 없다고 판단됩니다.

 




사용법


프로그램 실행시 가장 먼저 뜨는 main 창

분석을 위한 데이터를 입력하는 창

직원 월급여 리스트에 월급을 추가하는 사진. 입력 박스에 입력 중일 때 배경이 노란색으로 일시적인 변경이 이루어짐

 


중복 선택한 후, 다중으로 한꺼번에 수정 또는 삭제도 가능. '추가' 버튼은 리스트에 무언가 선택되었을 때 '수정' 버튼으로 바뀜


선택한 직원 삭제


입력된 문자열이 숫자가 아닐 때나 음수 등등 유효하지 않은 잘못된 입력일 경우, 입력 박스 바탕이 빨간색으로 변경되며 경고

기본적으로 '저장' 버튼은 비활성화 상태이고, '회사 수입' 금액의 총합이 '2년간 회사 총 급여 예산'과 일치할 때만 활성화

저장하면 main 창에서 새로 저장된 정보로 화면 갱신

데이터 분석 및 출력창

직원 데이터 분석 그래프 및 표. 위의 그래프는 (막대 그래프와 추이 그래프 모두) 서서히 애니메이션 효과와 함께 나타난다.

이런 그래프와 표 등의 데이터를, 위쪽에 있는 4개의 버튼을 이용해 따로 여러 파일 형식으로 저장할 수도 있다.

모든 직원 정보 csv 저장 버튼을 통해 저장된 csv 파일. 모든 직원의 데이터가 전부 출력되었다.

이 밖에도 다양한 데이터 파일 출력을 지원 

 

 




matlab을 앞으로 프로그래밍에 쓸 일은 거의 없을 것 같으니, 어찌 보면 처음이자 마지막 matlab 프로그래밍이라 볼 수 있겠네요.


저작자 표시 비영리
신고

설정

트랙백

댓글

move_cursor.c



다운로드


- 첨부파일 :

move_cursor.c





개요


리눅스 터미널 상에서 커서를 (x, y) 지점으로 이동시킵니다.

화면 내용을 바꾸거나 지울 때 유용합니다.





소스 코드


void move_cur(int x, int y)
{
     printf("\033[%dd\033[%dG", y, x);
}





저작자 표시 비영리
신고

설정

트랙백

댓글

csvwrite_cell.m



다운로드


- 첨부파일 :

csvwrite_cell.m





개요


MATLAB 에서 cell array를 csv 파일로 저장하려 할 때, cell2mat를 통해 기존 csvwrite 함수를 활용하는 방법이 있지만 cell 안에 문자열 데이터가 있을 경우 cell2mat 부터 에러가 납니다.

 

이런 문자열이 섞인 cell array(xls 형식과 흡사한)를 바로 csv로 출력하는 간단한 함수를 matlab 과제 중 필요에 의해 만들어봤는데, 컴공이라 가능성은 별로 없지만 혹시나 나중에 쓸 일이 있을까 해서 저장해둡니다.


물론 최적화가 되어 있는지는 별개의 문제(...)





사용법


csvwrite_cell( 파일로 저장할 cell array, 저장할 경로, 모드(.csv 또는 .txt) ) 





소스 코드


function csvwrite_cell(celltable, path, mode)

% write 모드 확인 %
if ( mode == 'csv' ) % csv 모드
    line_feed = '\n';
    separator = ',';
elseif ( mode == 'txt' ) % txt 모드
    line_feed = '\r\n';
    separator = '\t';
else % 기타
    line_feed = '\n';
    separator = ',';
end

fp = fopen(path, 'w'); % 파일 포인터 열기
size_of_table = size(celltable); % cell array 크기


if ( mode == 'csv' ) % csv 모드
    for ( count_row = 1:size_of_table(1) ) % row를 1부터 끝까지
        for ( count_col = 1:size_of_table(2) ) % col을 1부터 끝까지
            % 문자열일 때 %
            if ( ischar(cell2mat(celltable(count_row, count_col))) == 1 )
                fprintf(fp, '"%s"', cell2mat(celltable(count_row, count_col)));
            % 실수일 때 %
            else
                fprintf(fp, '%f', cell2mat(celltable(count_row, count_col)));
            end

            if ( rem(count_col, size_of_table(2)) == 0 )
                fprintf(fp, line_feed); % 개행해야 할 때는 개행문자
            else
                fprintf(fp, separator); % 기타의 경우는 구분자
            end
        end
    end
    
elseif ( mode == 'txt' ) % txt 모드
    for ( count_row = 1:size_of_table(1) ) % row를 1부터 끝까지
        for ( count_col = 1:size_of_table(2) ) % col을 1부터 끝까지
            % 문자열일 때 %
            if ( ischar(cell2mat(celltable(count_row, count_col))) == 1 )
                fprintf(fp, '%s', cell2mat(celltable(count_row, count_col)));
            % 정수일 때 %
            else
                fprintf(fp, '%d', cell2mat(celltable(count_row, count_col)));
            end

            if ( rem(count_col, size_of_table(2)) == 0 )
                fprintf(fp, line_feed); % 개행해야 할 때는 개행문자
            else
                fprintf(fp, separator); % 기타의 경우는 구분자
            end
        end
    end
    
else % 기타
    for ( count_row = 1:size_of_table(1) ) % row를 1부터 끝까지
        for ( count_col = 1:size_of_table(2) ) % col을 1부터 끝까지
            % 문자열일 때 %
            if ( ischar(cell2mat(celltable(count_row, count_col))) == 1 )
                fprintf(fp, '%s', cell2mat(celltable(count_row, count_col)));
            % 실수일 때 %
            else
                fprintf(fp, '%f', cell2mat(celltable(count_row, count_col)));
            end

            if ( rem(count_col, size_of_table(2)) == 0 )
                fprintf(fp, line_feed); % 개행해야 할 때는 개행문자
            else
                fprintf(fp, separator); % 기타의 경우는 구분자
            end
        end
    end
    
end


fclose(fp);





 


 

물론 과제 도중 임시로 만들어서 과제에만 테스트를 해 본 터라 모든 경우에 적용될 수 있다고 보기는 어려움.


저작자 표시 비영리
신고

설정

트랙백

댓글