분류 전체보기 (356)

💻 Programming/Linux

vi 에서 문자열 치환하기

오랜만에 포스팅을 합니다.


뭐, 그냥 종종 사용하는 건데 맨날 까먹어서 계속 구글링하게 만드는 vi에서 문자열 치환하기에 대한 내용입니다.


*nix 환경에서 작업을 하게되면 설정 파일을 수정할 일이 많이 생깁니다.


이때 파일 다운로드 경로나, 이런 저런 경로들을 config 파일 안에 설정해놓고 사용하기도 하는데, 이때 경로 개수가 많고 앞부분의 경로는 동일한 것들이 많을 수 있습니다.


예를들어, /aaa/bbb/myapp/conf/config.properties 파일에 다음과 같은 내용들이 있을 수 있죠.


.

.

myapp.root=/aaa/bbb/myapp

myapp.log=/aaa/bbb/myapp/log

myapp.modules=/aaa/bbb/myapp/modules

.

.


저런식으로 경로가 저장이 되어있는데, 앞부분의 절대경로를 다른 경로로 변경해주고 싶을 때가 있습니다. 파일 사이즈가 작고 4~5개 정도면 하나하나 수정을 해줘도 되겠지만, 파일 사이즈가 크고 경로 설정하는 부분이 여기저기 흩어져 있으면 일일이 찾아서 수정하기가 쉽지 않습니다.


이럴 때 사용하라고 vi에서는 강력한 치환 기능을 제공하고있습니다.


%s/치환하려는문자열정규식/결과문자열정규식/옵션


vi로 문서를 열어놓은 상태에서 :을 입력하여 명령어를 입력할 수 있도록 한 뒤 위 형식에 맞게 문자열 및 옵션을 입력해주면 파일 전체에서 특정 문자열을 찾아서 결과문자열로 변경할 수 있습니다.


위 예를들어 /aaa/bbb/myapp을 /ccc/ddd/app 으로 변경을 하고 싶다면


%s/\/aaa\/bbb\/myapp/\/ccc\/ddd\/app/g


라고 입력해주면 전체 파일의 내용을 검색하여 /aaa/bbb/myapp 문자열을 찾은 뒤 /ccc/ddd/app 으로 치환해줍니다.


명령어를 살펴보면 다음과 같습니다.


%s : 문서 전체

g : 한줄에 동일 패턴이 여러개 존재하는 경우 모두 적용

i : 대소문자 구분 안함




이상으로 간만의 포스팅을 마칩니다~

안녕하세요, 케이치입니다.


오늘도 쉽게 넘어가지를 않고 에러가 발생하네요. 오늘은 윈도우용 도커를 설치를 했다가 jre 8 이 정상적으로 동작을 하지 못하게 된 케이스입니다.


jdk7, 8, jre7, 8을 윈도우10 에 설치해서 사용중인 환경인데요 jdk8을 설치해보신 분들은 아시겠지만 7버전 까지는 c:\Program Files\Java 밑에 설치가 되고 환경설정 변수에도 해당 경로의 bin디렉토리를 설정해줬었는데(C:\Program Files\Java\jre7\bin), 8버전부터는 C:\ProgramData\Oracle\Java\javapath\java.exe 를 환경변수에 가지도록 해놨더군요. 물론 설치 경로는 Program Files\Java\jdk1.8 이었는데도 불구하고 말이죠. 어찌되었건 잘 쓰고있었는데, 문제가 생긴건 챗봇 오픈소스 테스트를 하려고 윈도우용 도커를 설치해서 테스트를 하고난 뒤에 발생했습니다. 갑자기 이클립스(Oxygen)가 실행이 안되더군요. 

뭐지? 뭐지? 하다가, 이클립스를 다시 설치해보고 실행했더니 처음 한번은 잘 됩니다. 다시 이클립스를 종료했다가 실행시키면 또 안되고 참 희한한 현상이 발생했습니다. ㅠㅠ


아무튼 한참을 해메다가 neon 버전으로 다시 설치해서도 해보고 하다가 java를 실행해봤는데 아래와 같은 에러가 발생하더군요.


Error occurred during initialization of VM

java/lang/NoClassDefFoundError: java/lang/Object


허허, 이런 메시지는 또 처음 봐서 구글링을 해봤더니 jdk, jre 설치가 정상적으로 안된 케이스에서 발생하는 경우가 많은 것 같더군요.


조금 전까지 아무 문제 없던게 도커를 설치했더니 자바가 실행이 안된다? 좀 이해가 안되는 부분이라서 java명령어를 실행할 때 Program Files 하위의 java를 쓰는건지 ProgramData하위의 java를 쓰는건지 확인해보고 싶었습니다. 그때 사용한 명령어는 아래와 같습니다.


 for %i in (java.exe) do @echo. %~$PATH:i


윈도우에서 기본적으로 제공해주는 for 명령어를 이용한 것으로 도움말을 출력해보면 아래와 같습니다.



C:\Users\dev>for /?

파일 집합에서 각 파일에 대해 지정된 명령을 실행합니다.


FOR %변수 IN (집합) DO 명령어 [명령어 매개 변수]


  %변수      바꿀 수 있는 매개 변수를 한 문자로 지정합니다.

  (집합)      하나 이상의 파일을 지정합니다. 와일드카드를 사용할 수 있습니다.

  명령어      각 파일에 대해 수행할 명령을 지정합니다.

  명령어-매개 변수

              지정된 명령의 매개 변수나 스위치를 지정합니다.


일괄 프로그램에서 FOR 명령을 쓰려면, '%변수' 대신 '%%변수'를 지정하십시오.

변수 이름에서는 대문자와 소문자를 구별하므로 %i와 %I는 다릅니다.


명령 확장을 사용하면 FOR 명령에 아래와 같은 추가적인 형태가

지원됩니다.


FOR /D %변수 IN (집합) DO 명령 [명령-매개 변수]


   집합에 대표 문자가 있으면 파일 이름 대신 디렉터리 이름과

   일치하도록 지정합니다.


FOR /R [[드라이브:]경로] %변수 IN (집합) DO 명령 [명령-매개 변수]


   [드라이브:]경로를 루트로 하여 디렉터리 트리를 따라 내려가며

   FOR 구문을 트리의 각 디렉터리에서 실행합니다. /R 스위치 뒤에

   디렉터리가 지정되지 않으면 현재 디렉터리가 사용됩니다.

   집합에 마침표(.)가 사용되면 디렉터리 트리만 나열합니다.


FOR /L %변수 IN (시작,단계,끝) DO 명령 [명령-매개 변수]


   집합은 단계별로 증가/감소하는 시작부터 끝까지의 일련의 숫자입니다.

   따라서 (1,1,5)는 1 2 3 4 5를 나타내며 (5,-1,1)은 5 4 3 2 1을

   나타냅니다.


FOR /F ["옵션"] %변수 IN (파일-집합) DO 명령 [명령-매개 변수]

FOR /F ["옵션"] %변수 IN ("문자열") DO 명령어 [명령-매개 변수]

FOR /F ["옵션"] %변수 IN ('명령어') DO 명령어 [명령-매개 변수]


    또는 usebackq 옵션이 있는 경우:


FOR /F ["옵션"] %변수 IN (파일-집합) DO 명령 [명령-매개 변수]

FOR /F ["옵션"] %변수 IN ('문자열') DO 명령어 [명령-매개 변수]

FOR /F ["옵션"] %변수 IN (`명령어`) DO 명령어 [명령-매개 변수]


   파일-집합은 하나 이상의 파일 이름입니다. 파일-집합의 각 파일은

   다음 파일로 이동하기 전에 열기 또는 읽기 등의 작업이 진행됩니다.

   파일을 읽어서 문자열을 한 행씩 분리하고 각 행을 0개 이상의

   토큰으로 구문 분석하는 과정으로 되어 있습니다. For 루프의 본문은발견된 토큰 문자열에 설정된 변수 값(들)과 함께 호출됩니다.

   기본값으로 /F는 파일의 각 행으로부터 분리된 토큰을 첫 번째 공백에전달합니다. 빈 행은 건너뜁니다. "옵션" 매개 변수를 지정하여

   기본 구문 분석 동작을 무시할 수 있습니다. 이것은 다른 구문 분석

   매개 변수를 지정하는 하나 이상의 키워드를 갖는 인용 부호로

   묶인 문자열입니다.

   키워드는 아래와 같습니다.


   eol=c  - 행 끝 설명 문자를 지정합니다 (하나만)

   skip=n  - 파일의 시작 부분에서 무시할 행의 개수를 지정합니다.

   delims=xxx  - 구분 문자 집합을 지정합니다.  이것은 공백 또


계속하려면 아무 키나 누르십시오 . . .


너무 길어서 짧게 끊었습니다. 위에서 사용한 명령어는 유닉스의 which java와 같이 PATH환경변수에 있는 디렉토리 경로를 훑으면서 제일 처음 만나는 java위치를 출력해줍니다. 즉, 윈도우용 which 명령어라고 생각하시면 될 것 같습니다.



이상입니다.




아래는 기록을 위해서 개인적으로 작성했던 노트입니다.

오픈소스 챗봇 테스트를 위해서 Docker 설치하고 테스트 한 이후부터 이클립스 실행이 안되는 현상 발생
    - 다시 동일한 디렉토리에 Oxygen버전 인스톨러를 이용해서 재설치 한 뒤 launch하면 기동이 되나, 재부팅 후에는 역시 또 안됨
    - Neon 버전처럼 패키지로 배포되는 이클립스는 새로 다운받아서 실행해도 실행 안됨
    - Java를 실행해보니 다음과 같은 에러 메시지 발생
        - Error occurred during initialization of VM
          java/lang/NoClassDefFoundError: java/lang/Object
    - 현재 jdk1.7과 1.8 버전이 설치되어있고 jre역시 7, 8 버전이 설치되어있음
    - 아래 명령어를 이용하여 어느 위치의 자바가 실행되고 있는지 확인
        for %i in (java.exe) do @echo. %~$PATH:i
        - C:\ProgramData\Oracle\Java\javapath\java.exe
    - jdk 1.8을 설치하면 환경변수에 위 경로로 세팅이 됨
    - Docker를 설치하고나서 위 java를 실행하면 에러가 발생함
    - jdk1.7, jdk1.8 의 java.exe를 실행하면 정상적으로  실행이 되는 것을 확인
    - jdk1.8을 재설치하여 정상적으로 java가 실행이 되도록 할 수도 있으나 일단은 환경변수 확인
    - 환경변수에는 JAVA_HOME에 jdk1.8의 패스가 잡혀있고 PATH에 %JAVA_HOME%\bin이 있음, 그리고 ProgramData\OracleJava\javapath도 잡혀있음
    - 환경변수에서 ProgramData\OracleJava\javapath 경로는 제거함
    - 다시 이클립스를 재기동 해보니 정상적으로 기동함

    - jre1.8의 java는 정상적으로 실행이 안됨 -> 재설치






안녕하세요 케이치입니다.


오늘은 윈도우에서 특정 포트를 사용중인 프로세스를 죽이는 법을 가지고 왔습니다. 


최근에 Node.js 앱을 테스트해볼 일이 있었는데 고정적으로 특정 포트를 사용하는 앱이었습니다. 윈도우에서 cmd 창을 띄워서 실행시켰다가 Ctrl + C로  프로그램을 중지시켰는데 다시 기동시켰더니 포트가 이미 사용중이라면서 에러가 나더라구요. 그래서 이놈을 어떻게 죽여야 하나 찾아봤습니다.


우선 제 환경은 Windows 10이구요 CMD창을 띄우면 아래처럼 나옵니다.


제가 봤던 에러 메시지는 아래와 같구요


Error: listen EADDRINUSE :::2002


2002번 포트가 이미 사용중이라는 메시지입니다.


그럼 누가 이 포트를 사용중인지 찾아보죠.


cmd 창에 아래 명령어를 입력합니다.


C:\Users\dev>netstat -ano|find "2002"

  TCP    0.0.0.0:2002         0.0.0.0:0           LISTENING       5096

  TCP    [::]:2002              [::]:0                 LISTENING       5096


출력된 내용은 TCP 프로토콜에서 2002번 포트로 리스닝하고있는 프로세스의 ID가 5096번이라는 걸 말해줍니다. 우리는 5096 프로세스를 죽이면 되는거겠죠. 죽이기 전에 위 명령어에 대해서 설명을 좀 드리겠습니다.


netstat -ano는 netstat 명령어를 a, n, o 옵션으로 실행시키라는 의미죠. 여기서 a, n, o에 대한 설명은 netstat /? 를 입력하면 확인할 수 있습니다.


C:\Users\dev>netstat /?


프로토콜 통계와 현재 TCP/IP 네트워크 연결을 표시합니다.


NETSTAT [-a] [-b] [-e] [-f] [-n] [-o] [-p proto] [-r] [-s] [-x] [-t] [interval]


  -a            모든 연결과 수신 대기 포트를 표시합니다.

  -b            각 연결 또는 수신 대기 포트 생성과 관련된 실행 파일을

                표시합니다. 잘 알려진 실행 파일이 여러 독립 구성 요소를

                호스팅할 경우 연결 또는 수신 대기 포트 생성과 관련된

                구성 요소의 시퀀스가 표시됩니다.

                이러한 경우에는 실행 파일 이름이 대괄호로 아래에

                표시되고 위에는 TCP/IP에 도달할 때까지

                호출된 구성 요소가 표시됩니다. 이 옵션은 시간이 오래

                걸릴 수 있으며 사용 권한이 없으면 실패합니다.

  -e            이더넷 통계를 표시합니다. 이 옵션은 -s 옵션과 함께

                사용할 수 있습니다.

  -f            외부 주소의 FQDN(정규화된 도메인 이름)을

                표시합니다.

  -n            주소와 포트 번호를 숫자 형식으로 표시합니다.

  -o            각 연결의 소유자 프로세스 ID를 표시합니다.

  -p proto      proto로 지정한 프로토콜의 연결을 표시합니다. proto는

                TCP, UDP, TCPv6 또는 UDPv6 중 하나입니다. -s 옵션과 함께

                사용하여 프로토콜별 통계를 표시할 경우 proto는 IP, IPv6, ICMP,

                ICMPv6, TCP, TCPv6, UDP 또는 UDPv6 중 하나입니다.

  -q            모든 연결, 수신 대기 포트 및 바인딩된 비수신 대기 TCP

                포트를 표시합니다. 바인딩된 비수신 대기 포트는 활성 연결과 연결되거나

                연결되지 않을 수도 있습니다.

  -r            라우팅 테이블을 표시합니다.

  -s            프로토콜별 통계를 표시합니다. 기본적으로 IP, IPv6, ICMP,

                ICMPv6, TCP, TCPv6, UDP 및 UDPv6에 대한 통계를 표시합니다.

                -p 옵션을 사용하여 기본값의 일부 집합에 대한 통계만

                지정할 수 있습니다.

  -t            현재 연결 오프로드 상태를 표시합니다.

  -x            NetworkDirect 연결, 수신기 및 공유 끝점을

                표시합니다.

  -y            모든 연결에 대한 TCP 연결 템플릿을 표시합니다.

                다른 옵션과 함께 사용할 수 없습니다.

  interval      다음 화면으로 이동하기 전에 지정한 시간(초) 동안 선택한 통계를 다시 표시합니다.

                통계 다시 표시를 중지하려면 <Ctrl+C>를 누르십시오.

                이 값을 생략하면 현재 구성 정보가

                한 번만 출력됩니다.


이렇게 실행된 결과를 파이프( | )를 통해 find 명령어로 전달하여 "2002"를 찾으라고 하고있죠. find 명령어는 리눅스의 grep과 비슷한 기능을 합니다.


find "2002"를 제외한 앞 부분만 실행시키면 2002포트 말고도  다른 여러 포트들에 대한 목록이 함께 출력됩니다. 


자, 그럼 5096번 프로세스를 죽여볼까요?


윈도우에서 프로세스를 죽일 때는 "작업관리자"를 이용하는 방법도 있지만 개발을 하다보면 간혹 작업관리자에서 찾기 힘든 녀석들이 있죠. 그럴 경우 cmd창에서 작업을 하면 훨씬 수월하게 작업을 할 수 있습니다.


프로세스를 죽일 때는 taskkill 명령어를 사용합니다. 그럼 이녀석에 대한 설명을 한번 볼까요?


C:\Users\dev>taskkill /?


TASKKILL [/S 시스템 [/U 사용자 이름 [/P [암호]]]]

         { [/FI 필터] [/PID 프로세스 id | /IM 이미지 이름] } [/T] [/F]


설명:

    이 도구는 프로세스 ID(PID) 또는 이미지 이름으로 작업을 종료하는 데

    사용합니다.


매개 변수 목록:

    /S    시스템           연결할 원격 시스템을 지정합니다.


    /U    [도메인\]사용자  명령을 실행해야 하는 사용자 컨텍스트를

                           지정합니다.


    /P    [암호]           해당 사용자 컨텍스트의 암호를 지정합니다.

                           생략한 경우에는 물어봅니다.


    /FI   필터             작업 집합을 선택하는 필터를 적용합니다.

                           "*"를 사용할 수 있습니다. 예: imagename eq acme*


    /PID  프로세스_ID      종료할 프로세스의 PID를 지정합니다.

                           TaskList를 사용하여 PID를 얻을 수 있습니다.


    /IM   이미지 이름      종료할 프로세스의 이미지 이름을

                           지정합니다. 와일드카드 문자 '*'를 사용하여

                           모든 작업 또는 이미지 이름을 지정할 수 있습니다.


    /T                     지정된 프로세스와 그 프로세스로부터 시작된

                           모든 자식 프로세스를 종료합니다.


    /F                     프로세스를 강제로 종료하도록 지정합니다.


    /?                     이 도움말 메시지를 표시합니다.


필터:

    필터 이름     유효한 연산자             유효한 값

    -----------   ---------------           -------------------------

    STATUS        eq, ne                    RUNNING |

                                            NOT RESPONDING | UNKNOWN

    IMAGENAME     eq, ne                    이미지 이름

    PID           eq, ne, gt, lt, ge, le    PID 값

    SESSION       eq, ne, gt, lt, ge, le    세션 번호.

    CPUTIME       eq, ne, gt, lt, ge, le    CPU 시간 형식

                                            hh:mm:ss

                                            hh - 시간,

                                            mm - 분, ss - 초

    MEMUSAGE      eq, ne, gt, lt, ge, le    메모리 사용(KB)

    USERNAME      eq, ne                    사용자 이름([domain\]user

                                            형식)

    MODULES       eq, ne                    DLL 이름

    SERVICES      eq, ne                    서비스 이름

    WINDOWTITLE   eq, ne                    창 제목


    참고

    ----

    1) /IM 스위치에 대한 와일드카드 문자 '*'는 필터가 적용될 때만

    사용할 수 있습니다.

    2) 원격 프로세스는 항상 강제적으로(/F) 종료될 수 있습니다.

    3) 원격 컴퓨터가 지정되면 "WINDOWTITLE"  및 "STATUS" 필터는

       지원되지 않습니다.


예:

    TASKKILL /IM notepad.exe

    TASKKILL /PID 1230 /PID 1241 /PID 1253 /T

    TASKKILL /F /IM cmd.exe /T

    TASKKILL /F /FI "PID ge 1000" /FI "WINDOWTITLE ne untitle*"

    TASKKILL /F /FI "USERNAME eq NT AUTHORITY\SYSTEM" /IM notepad.exe

    TASKKILL /S 시스템 /U domain\username /FI "USERNAME ne NT*" /IM *

    TASKKILL /S 시스템 /U 사용자 이름 /P 암호 /FI "IMAGENAME eq note*"


설명을 읽어보니 우리에게 필요한 내용이 너무나도 잘 설명되어있네요.


그럼 이제 프로세스를 죽여보겠습니다.


C:\Users\dev>netstat -ano|find "2002"

  TCP    0.0.0.0:2002           0.0.0.0:0              LISTENING       5096

  TCP    [::]:2002              [::]:0                 LISTENING       5096


C:\Users\dev>taskkill /pid 5096

오류: 프로세스(PID 5096)를 종료할 수 없습니다.

원인: 이 프로세스는 /F 옵션을 사용하여 강제로 종료해야 합니다.


C:\Users\dev>taskkill /pid 5096 /f /t

성공: PID 5096인 프로세스(PID 11172인 자식 프로세스)가 종료되었습니다.


보시면 중간에 오류가 한번 나면서 /F옵션을 쓰라고 나오네요. 참고에 원격 프로세스는 /F 옵션을 줘야 종료된다고 나와있는데 그거 때문인것 같습니다. 그래서 F옵션을 추가하고 혹시나 해서 자식프로세스까지 죽이는 T옵션을 추가했습니다. 아!! 그리고  옵션은 대소문자 구문을 하지 않습니다. 윈도우는 영어 대소문자 구분을 하지 않죠. 


이상입니다. 오늘은 여기까지 쓸게요. 오늘도 즐코하세요~








안녕하세요, 케이치입니다.


오늘은 리눅스 서버의 재부팅/재기동 또는 언제 shutdown이 됐었는지를 확인하는 방법을 배웠습니다.


그다지 많이 쓸 일은 없는 명령어이긴 한데 웹사이트에 접속이 안되서 확인해보다가 알게되었네요.


우선 웹서버로 접속을 하는데 아래와 같은 메시지가 떴습니다.

Service Temporarily Unavailable
The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.


뭔소린가 봤더니 점검 중이거나 용량문제로 인해서 접속할 수 없다라는 말이네요.


그래서 서버에 접속해서 혹시 디렉토리 용량이 부족한가 봤는데 아니었습니다.


그럼 왜 안되는지 에러로그부터 확인해야겠죠.


웹서버의 에러로그를 봤는데 에러가 없습니다 ㄷㄷ;;;;; 뭐지 이건;;;


이제 실제 웹앱이 올라가있는 WAS 기동상태를 확인합니다.


두둥!!!! WAS가 죽었네요 ㅜㅜ


뭐지 난 죽인적이 없는데....그래서 다시 WAS의 에러로그를 확인하는데 헐;;;; 여기도 에러로그는 없습니다. 마지막까지 열심히 사람들의 요청에 부응한 흔적밖에 없네요.


뭐지? 서버가 재부팅 됐나? 어떻게 확인하지?


리눅스 서버의 재기동 상태를 확인하는 명령어는 두 가지가 있습니다.


첫째, last 명령어.

둘째, who 명령어.


[keichee@server1 ~]$ last reboot

reboot   system boot  3.10.0-514.16.1. Thu Jun 22 17:13 - 11:32 (140+18:19)

reboot   system boot  3.10.0-327.36.3. Wed Jan  4 22:14 - 17:11 (168+18:57)

reboot   system boot  3.10.0-327.36.3. Thu Nov 17 14:41 - 17:11 (217+02:30)

reboot   system boot  3.10.0-327.36.3. Thu Nov 17 15:06 - 17:11 (217+02:04)

reboot   system boot  3.10.0-327.36.3. Thu Nov 17 11:01 - 17:11 (217+06:09)


last reboot 명령어는 마지막으로 재기동한 시간이 언제인지를 desc 정렬하여 출력합니다. 즉, 제일 위에 있는게 가장 최근에 재기동한 시간을 말합니다.


last reboot 말고도 last shutdown 명령어로 언제 셧다운됐었는지 확인이 가능합니다. 


last -x 는 어떤 사용자가 서버에 접속해서 얼마나 있었는지 확인이 가능합니다. run level이 어떻게 바뀌었는지도 확인이 가능하죠.


[keichee@server1 ~]$ last -x

keichee    pts/0        210.145.229.14   Fri Nov 10 11:09   still logged in

keichee    pts/0        210.145.229.14   Tue Nov  7 09:17 - 14:14  (04:57)

keichee    pts/0        210.145.229.143   Mon Nov  6 09:19 - 18:27  (09:07)

keichee    pts/2        204.130.143.176  Thu Nov  2 22:04 - 00:26  (02:21)

keichee    pts/1        103.170.103.116  Thu Nov  2 22:04 - 00:26  (02:22)

runlevel (to lvl 3)   3.10.0-514.16.1. Thu Jun 22 17:13 - 11:35 (140+18:22)
reboot   system boot  3.10.0-514.16.1. Thu Jun 22 17:13 - 11:35 (140+18:22)
shutdown system down  3.10.0-327.36.3. Thu Jun 22 17:11 - 17:13  (00:01)


그럼 두 번째 명령어였던 who는 어떻게 쓸까요?


기본적으로 who를 쓸 때는 현재 내가 어떤 사용자로 접속을 했는지 확인하기 위해서 사용할 수 있죠.


[keichee@server1 ~]$ who

keichee    pts/0        2017-11-10 11:09 (118.145.269.124)


help 옵션으로 어떤 옵션을 추가로 줄 수 있는지 한번 볼까요?


[keichee@server1 ~]$ who  --help

Usage: who [OPTION]... [ FILE | ARG1 ARG2 ]

Print information about users who are currently logged in.


  -a, --all         same as -b -d --login -p -r -t -T -u

  -b, --boot        time of last system boot

  -d, --dead        print dead processes

  -H, --heading     print line of column headings

  -l, --login       print system login processes

      --lookup      attempt to canonicalize hostnames via DNS

  -m                only hostname and user associated with stdin

  -p, --process     print active processes spawned by init

  -q, --count       all login names and number of users logged on

  -r, --runlevel    print current runlevel

  -s, --short       print only name, line, and time (default)

  -t, --time        print last system clock change

  -T, -w, --mesg    add user's message status as +, - or ?

  -u, --users       list users logged in

      --message     same as -T

      --writable    same as -T

      --help     display this help and exit

      --version  output version information and exit


옵션 중에 -b 옵션이 마지막 시스템 부팅 시간을 출력한다는 것을 확인할 수 있습니다.


그럼 한 번 써보죠.

[keichee@server1 ~]$ who -b

         system boot  2017-06-22 17:13

17년 6월 22일 17시 13분에 시스템이 기동됐음을 알 수 있습니다.


이상입니다.


오늘은 여기까지~ 

리눅스 환경에 배포한 WAR 또는 JAR 파일에 새로운 파일을 추가하거나 기존 파일을 업데이트하는 방법


안녕하세요, 케이치입니다. 


오늘은 리눅스 환경에 배포한 WAR 또는 JAR 파일에 새로운 파일을 추가하거나 기존 파일을 업데이트하는 방법을 알려드리겠습니다. 그닥 이렇게 할 일이 없다보니 할 때마다 계속 까먹어서 그냥 혼자 끄적일겸 적어봅니다.


우선 /mydir/myapp.war 파일이 있다고 가정합니다.


우선 이 파일안에 어떤 파일이 있는지부터 확인해보죠.


$ cd /mydir

$ ll    (또는 ls -l)

-rw-rw-r--. 1 keichee keichee 63378 Nov  6 11:37 myapp.war


$ unzip -l myapp.war


Archive:  myapp.war

  Length      Date    Time    Name

---------  ----------   -----    ----

        0  11-06-2017 11:28   META-INF/

     128  11-06-2017 11:28   META-INF/MANIFEST.MF

        0  11-06-2017 11:28   css/

        0  11-06-2017 11:28   images/

        0  11-06-2017 11:28   WEB-INF/

        0  11-06-2017 11:28   WEB-INF/classes/

        0  11-06-2017 11:28   WEB-INF/classes/com/

        0  11-06-2017 11:28   WEB-INF/classes/com/my/

.
.....중략.....
.
  85308  08-14-2017 17:37   WEB-INF/lib/spring-oxm-4.3.0.RELEASE.jar
 587278  08-14-2017 17:37   WEB-INF/lib/spring-test-4.3.0.RELEASE.jar
 266993  08-14-2017 17:37   WEB-INF/lib/spring-tx-4.3.0.RELEASE.jar
 808847  08-14-2017 17:37   WEB-INF/lib/spring-web-4.3.0.RELEASE.jar
 905320  08-14-2017 17:37   WEB-INF/lib/spring-webmvc-4.3.0.RELEASE.jar
---------                           -------
 44109477                        393 files

WAR파일안에 어떤 파일들이 들어가있는지 디렉토리 경로까지 전부 확인할 수 있습니다.


이제 파일 하나를 추출해보도록 하죠.


추출할 대상은 /META-INF/MANIFEST.MF 입니다. 사이즈가 128이군요.


$ unzip myapp.war META-INF/MANIFEST.MF

Archive:  myapp.war

  inflating: META-INF/MANIFEST.MF

$ ll   (또는 ls -l )

-rw-rw-r--. 1 keichee keichee 44109477 Nov  6 11:37 myapp.war

drwxrwxr-x. 3 keichee keichee       17 Nov  9 14:42 META-INF

META-INF 디렉토리가 생성된 것을 확인할 수 있습니다.


$ cd META-INF

$ ls -l

-rw-rw-r--. 1 keichee keichee 128 Nov  6 11:28 MANIFEST.MF


파일이 있는 것을 확인할 수 있습니다. 사이즈가 128로 위에서 조회한 것과 동일하네요.


그럼 파일을 수정해볼까요?


$ vi MANIFEST.MF


마음대로 내용을 수정해서 파일 사이즈가 달라지도록 해주세요.


$ ls -l

-rw-rw-r--. 1 keichee keichee 145 Nov  9 13:38 MANIFEST.MF


파일의 사이즈와 시간이 변경되었네요. 128 -> 145


그럼 이 파일을 이제 다시 WAR 파일안으로 업데이트해서 넣어보도록 하겠습니다.


$ jar uvf myapp.war META-INF/MANIFEST.MF

adding: META-INF/MANIFEST.MF(in = 145) (out= 120)(deflated 17%)


파일을 추가한다고 나오면서 압축이 얼마나 됐는지가 나오네요. 17%가 압축됐습니다. 참고로 여기서 in은 실제 파일 사이즈out은 압축된 파일사이즈 입니다.


만약 jar 명령어가 없을 경우에는 zip 명령어를 사용할 수도 있습니다.

$ zip -r myapp.war META-INF/MANIFEST.MF

adding: META-INF/MANIFEST.MF(in = 145) (out= 120)(deflated 17%)



이제 다시 myapp.war 파일에 어떤 파일들이 있는지 조회해보도록 하겠습니다.


$ unzip -l myapp.war


Archive:  myapp.war

  Length      Date    Time    Name

---------  ----------   -----    ----

        0  11-06-2017 11:28   META-INF/

     145  11-09-2017 13:38   META-INF/MANIFEST.MF

        0  11-06-2017 11:28   css/

        0  11-06-2017 11:28   images/

        0  11-06-2017 11:28   WEB-INF/

        0  11-06-2017 11:28   WEB-INF/classes/

        0  11-06-2017 11:28   WEB-INF/classes/com/

        0  11-06-2017 11:28   WEB-INF/classes/com/my/

.
.....중략.....
.
  85308  08-14-2017 17:37   WEB-INF/lib/spring-oxm-4.3.0.RELEASE.jar
 587278  08-14-2017 17:37   WEB-INF/lib/spring-test-4.3.0.RELEASE.jar
 266993  08-14-2017 17:37   WEB-INF/lib/spring-tx-4.3.0.RELEASE.jar
 808847  08-14-2017 17:37   WEB-INF/lib/spring-web-4.3.0.RELEASE.jar
 905320  08-14-2017 17:37   WEB-INF/lib/spring-webmvc-4.3.0.RELEASE.jar
---------                           -------
 44109494                        393 files


파일 사이즈와 날짜가 변경이 되었네요.

참고로 여기에 조회되는 파일사이즈는 압축되기 전의 파일 사이즈입니다.


리눅스에서 명령어 히스토리 조회 할 때 날짜 및 시간 함께 표시하기



리눅스 환경에서 어떤 명령어를 입력하면 그 명령어 목록이 history에 저장됩니다. 


그런데 그 목록이 단순히 명령어 목록만 순서대로 나올 뿐 이게 언제 실행이 되었는지는 알 수가 없죠.


일반적으로 history명령어를 실행하면 아래처럼 출력됩니다.



  421  ls -l

  422  ./listAlgorithms.sh

  423  cat ./listAlgorithms.sh

  424  cd ..

  425  ls -l

  426  cd lib

  427  ls -l

  428  ../bin/listAlgorithms.sh

  429  history


여기서 사용자가 입력했던 명령어를 조회할때 언제실행 했는지를 알아야 할 때가 있습니다.


  그럴 경우 사용자 계정의 ~/.bash_profile 파일이나 /etc/profile에 HISTTIMEFORMAT 변수를 추가해 주면 history를 조회할 때 각 명령어를 입력한 날짜와 시간이 표시 됩니다.


1. ~/.bash_profile이나 /etc/profile에 아래 내용을 추가합니다.


  $ vi /etc/profile

.

.  

HISTTIMEFORMAT="%F %T - "

export HISTTIMEFORMAT

.

.



2. 적용


  $ . /etc/profile

또는

  $ source /etc/profile


3. 확인


  $ history

  421  2017-11-02 18:48:16 - vi /etc/profile
  422  2017-11-02 18:48:22 - . /etc/profile
  423  2017-11-02 18:48:25 - history


여기서 %F는 년월일을 표시하는 거고 %T는 시간을 의미합니다.


이 외에도 history 목록 개수를 지정하거나 history를 저장할 파일을 변경할 수 도 있습니다.


역시 .bash_profile 을 수정해서 아래 내용을 넣어주시면 됩니다.

물론 적용도 시켜줘야겠죠.

export HISTSIZE=450      -> 히스토리 명령어로 조회시 목록에 조회될 개수 export HISTFILESIZE=450    -> 히스토리 파일에 저장할 목록 개수 export HISTFILE=~/history    -> 히스토리를 저장할 파일

히스토리 목록에서 연속으로 입력한 동일한 명령어는 저장하지 않는 기능도 있습니다. 

export HISTCONTROL=ignoredups


💻 Programming

[ Node.js in Action ] 13장 요약

/**
 *  웹서버 그 이상의 것들
 * - Socket IO
 * - TCP/IP 네트워킹
 * - OS와 상호작용하기 위한 툴
 * - 커맨드라인 툴 개발하기
 */


13.1. Socket.io


Socket IO 는 WebSocket과 유사한 기능을 제공하면서도, broadcasting, volatile 메시징을 편리하게 구현할 수 있는 API를 제공합니다.

WebSocket은 WebSocket 프로토콜을 사용하는데 아직 완성된 프로토콜이 아니라서 브라우저 마다 지원여부에도 차이가 있다는 문제가 있습니다. Socket IO를 이용할 경우 이 문제를 해결할 수 있는데, Socket IO는 WebSocket을 지원하지 않는 브라우저의 경우에 각 브라우저별로 다양한 방법을 통해 내부적으로 메시지를 보낼 수 있다고 합니다.


 A minimal Socket.IO application that pushes the server’s time to connected clients
 A Socket.IO application that triggers page refreshes when CSS files are edited


13.1.1 초간단 Socket.IO 애플리케이션 만들기


<< 간단한 Socket IO clock-server 프로그램 >> ( 시간이 표시가 안되는데 이유를 모르겠음 ;;;; )


13.1.2 Socket.IO를 이용한 페이지와 CSS reloading 하기


웹페이지 디자인을 할 때 일반적인 순서

1 Open the web page in multiple browsers.
2 Look for styling on the page that needs adjusting.
3 Make changes to one or more stylesheets.
4 Manually reload all the web browsers.
5 Go back to step 2.


<< 위 4번 과정을 쉽게 만들어주는 프로그램 예제 >> ( 이놈도 제대로 동작을 하지 않음...;;;)


13.1.3 Socket.IO의 또 다른 사용법


Socket.IO는 프로그레스 바를 계속 업데이트해서 수치가 올라가는 것 보여주는 기능에 적합함




13.2 TCP/IP 네트워킹


노드는 네트워킹 애플리케이션에 매우 적합. 네트워킹은 많은 I/O를 요구하기 때문.


 Working with buffers and binary data
 Creating a TCP server
 Creating a TCP client


13.2.1 Buffers와 Binary Data 다루기


Buffer 데이타 타입은 노드에서 제공하는 특별한 데이타 타입이다.

고정 길이의 raw binary data처럼 동작하며, C에서의 malloc()이나 C++, JAVA에서의 new 키워드라고 생각해도 된다.

기본적으로 Stream 클래스에 의해 데이타 이벤트 내에 반환된다.

Buffer는 숫자 0~ 255까지만 저장할 수 있는 non-resizable 배열이다.


TEXT DATA VS. BINARY DATA

1. -----------------------------------

var b = new Buffer("121234869");
console.log(b.length);
9
console.log(b);
<Buffer 31 32 31 32 33 34 38 36 39>


2. -----------------------------------

var b = new Buffer(4);
b.writeInt32LE(121234869, 0);
console.log(b.length);
4
console.log(b);
<Buffer b5 e5 39 07>

-----------------------------------


13.2.2 TCP 서버 만들기 


Socket 클래스는 client와 server 양쪽에서 모두 사용됨

It’s a Stream subclass that’s both readable and writable (bidirectional).

That is, it emits data events when input data has been read from the socket,

and it has write() and end() functions for sending output data.


WRITING DATA


var net = require('net');
net.createServer(function (socket) {
socket.write('Hello World!\r\n');
socket.end();
}).listen(1337);
console.log('listening on port 1337');


$ telnet localhost 1337


READING DATA


socket.on('data', function (data) {
console.log('got "data"', data);
});


data 인자는 기본적으로 Buffer인스턴스이다.

setEncoding()을 이용해서 decoded String을 인자로 넘길 수도 있다.


socket.on('end', function () {
console.log('socket has ended');
});


end 이벤트를 리스닝함으로써 불필요한 데이터 전송을 하지 않을 수 있다.


SAMPLE TEST  


var net = require('net');
var socket = net.connect({ host: process.argv[2], port: 22 });
socket.setEncoding('utf8');
socket.once('data', function (chunk) {
console.log('SSH server version: %j', chunk.trim());
socket.end();
});


$ node client.js github.com


SOCKET.PIPE() 을 사용하여 두 개의 스트림 연결하기


The pipe() 함수는 readable 스트림에서 데이터를 읽어와서 쓰기가능한 스트림에 데이터를 쓰는 역할을 합니다.


비정상 연결끊김 핸들링하기

클라이언트가 netcat을 썼을 때 Ctrl-D 대신 Ctrl-C로 커넥션을 종료시키면 서버쪽 소켓이 깔끔하게 종료가 되지 않는데, 이 부분에 대한 뒷처리를 하려면 아래처럼 close 이벤트를 리스닝하면 된다.


socket.on('close', function () {
console.log('client disconnected');
});


FINAL TEST APP


server


var net = require('net');
net.createServer(function (socket) {
console.log('socket connected!');
socket.on('data', function (data) {
console.log('"data" event', data);
});
socket.on('end', function () {
console.log('"end" event');
});
socket.on('close', function () {
console.log('"close" event');
});
socket.on('error', function (e) {
console.log('"error" event', e);
});
socket.pipe(socket);
}).listen(1337);


client


var net = require('net');
var host = process.argv[2];
var port = Number(process.argv[3]);
var socket = net.connect(port, host);
socket.on('connect', function () {
process.stdin.pipe(socket);
socket.pipe(process.stdout);
process.stdin.resume();
});
socket.on('end', function () {
process.stdin.pause();
});



13.3 Tools for interacting with the operating system


 The global process object—Contains information about the current process, such
as the arguments given to it and the environment variables that are currently set
 The fs module—Contains the high-level ReadStream and WriteStream classes
that you’re familiar with by now, but also houses low-level functions that we’ll
look at
 The child_process module—Contains both low-level and high-level interfaces
for spawning child processes, as well as a special way to spawn node instances
with a two-way message-passing channel


13.3.1 The process global singleton


Every Node process has a single global process object that every module shares access
to. Useful information about the process and the context it’s running in can be found
in this object.


the most interesting feature of the process object is that it’s an EventEmitter instance, which emits very special events,
such as exit and uncaughtException.


USING PROCESS.ENV TO GET AND SET ENVIRONMENT VARIABLES


SPECIAL EVENTS EMITTED BY PROCESS


 exit gets emitted right before the process exits.


process.on('exit', function (code) {
console.log('Exiting...');
});


 uncaughtException gets emitted any time an unhandled error is thrown.


process.on('uncaughtException', function (err) {
console.error('got uncaught exception:', err.message);
process.exit(1);
});
throw new Error('an uncaught exception');


CATCHING SIGNALS SENT TO THE PROCESS 


three signals that Node handles by default


 SIGINT—Sent by your shell when you press Ctrl-C. Node’s default behavior is to
kill the process, but this can be overridden with a single listener for SIGINT on
process.
 SIGUSR1—When this signal is received, Node will enter its built-in debugger.
 SIGWINCH—Sent by your shell when the terminal is resized. Node resets
process.stdout.rows and process.stdout.columns and emits a resize event
when this is received.


13.3.2 Using the filesystem module


MOVING A FILE 


fs.rename()

원격으로 이동시키려면 stream을 이용해서 복사해야 함. rename은 원격지로 이동하는 것을 지원하지 않음.


WATCHING A DIRECTORY OR FILE FOR CHANGES 


fs.watch() - 플랫폼의 네이티브 파일 변경 알림 API를 이용함. (플랫폼 차이로 인해 unreliable 함)

fs.watchFile()


USING COMMUNITY MODULES: FSTREAM AND FILED 


fstream을 이용하면 cp -rp srcDir destDir 명령어를 쉽게 구현할 수 있다.


filed 인스턴스는 req, res 객체에 접근이 가능하다.

http.createServer(function (req, res) {
req.pipe(filed('path/to/static/files')).pipe(res);
});

위 코드는 파일이 캐쉬되었을 때 304 Not Modified 오류를 뱉어내고 파일을 디스크에서 열거나 읽는 행위는 하지 않는다.



13.3.3 Spawning external processes


 cp.exec()—A high-level API for spawning commands and buffering the result
in a callback
 cp.spawn()—A low-level API for spawning single commands into a Child-
Process object
 cp.fork()—A special way to spawn additional Node processes with a built-in
IPC channel


자식 프로세스의 장단점 (PROS AND CONS TO CHILD PROCESSES)

 

cons : child process 로 실행되는 애플리케이션이 이미 클라이언트에 설치가 되어있어야 한다.

pros : 다른 언어로 쓰여진 애플리케잇션을 이용해서 좀 더 rich한 애플리케이션 개발이 가능하다.


cp.exec()로 명령어 결과 버퍼링하기(BUFFERING COMMAND RESULTS USING CP.EXEC())


cp.exec(), is useful for when you want to invoke a command, and you only care about the final result. This API allows you to enter full sequences of commands, including multiple processes piped to one another.


SPAWNING COMMANDS WITH A STREAM INTERFACE USING CP.SPAWN()


cp.spawn() returns a ChildProcess object that you can interact with.


ex)

var child = cp.spawn('ls', [ '-l' ]);
// stdout is a regular Stream instance, which emits 'data',
// 'end', etc.
child.stdout.pipe(fs.createWriteStream('ls-result.txt'));
child.on('exit', function (code, signal) {
// emitted when the child process exits
});


DISTRIBUTING THE WORKLOAD USING CP.FORK()


Like cp.spawn(), cp.fork() returns a ChildProcess object.

The major difference is the API added by the IPC channel: the child process now has a child.send (message) function, and the script being invoked by fork() can listen for process.on('message') events.


피보나치 수열 계산 예제


13.4 커맨드라인 툴 개발하기(Developing command-line tools )


 Parsing command-line arguments
 Working with stdin and stdout streams
 Adding pretty colors to the output using ansi.js


13.4.1 Parsing command-line arguments


Node provides you with the process.argv property, which is an array of strings, which are the arguments that were used when Node was invoked.

The first entry of the array is the Node executable, and the second entry is the name of your script.


ex)

var args = process.argv.slice(2);
console.log(args);


$ node args.js
[]


$ node args.js hello world
[ 'hello', 'world' ]


$ node args.js "tobi is a ferret"
[ 'tobi is a ferret' ]


어떤 옵션이 들어왔느냐에 따라 usage프린트하는 예제


13.4.2 stdin, stdout 가지고 놀기 (Working with stdin and stdout)


 process.stdin—A ReadStream to read input data from
 process.stdout—A WriteStream to write output data to


WRITING OUTPUT DATA WITH PROCESS.STDOUT


console.log() 내에서 process.stdout.write()이 사용됨.


(이클립스에서 argument 설정해주고 실행한다고 가정)

var target = process.argv[2];
process.stdout.write('Entered value is : ' + target);


PROCESS.STDIN 로 입력값 읽어오기 (READING INPUT DATA WITH PROCESS.STDIN )

 

Before you can read from stdin, you must call process.stdin.resume() to indicate that your script is interested in data from stdin.

After that, stdin acts like any other readable stream, emitting data events as data is received from the output of another process, or as the user enters keystrokes into the terminal window.


나이 검증하는 예제


DIAGNOSTIC LOGGING WITH PROCESS.STDERR 


 process.stderr을 직접 사용하지말고 console.error()를 사용하자.


13.4.3 출력에 색 입히기 (Adding colored output)


ANSI 이스케이프 코드 작성하기 (CREATING AND WRITING ANSI ESCAPE CODES )


(cmd 창에서 ansi.js 실행)


console.log('\033[43m\033[35mhello\033[39m\033[49m');

var ansi = require('ansi');
var cursor = ansi(process.stdout);

cursor
.fg.green()
.write('This should be green.')
.fg.reset()
.write('\n');
 



💻 Programming

[ Node.js in Action ] 3장 요약

3장에서는 아래와 같은 내용을 다룹니다.


 Organizing your code into modules
 Coding conventions
 Handling one-off events with callbacks
 Handling repeating events with event emitters
 Implementing serial and parallel flow control
 Leveraging flow-control tools


3.1 모듈을 이용한 코드 재사용 및 정리


PHP나 Ruby같은 언어에서 import하면 global scope에 영향을 미친다.

이런 것을 피하기 위해서 PHP는 namespaces를 사용하고, Ruby는 modules를 사용한다.

하지만 namespaces를 사용한다고 해도 overwrite의 가능성을 완전히 배제할 수는 없다.

Node에서는 이런 위험을 완전히 배제한다.

우선 Node의 module은 gloabl scope를 변경하지 않는다. 또한, module을 만들 때 개발자가 어떤 기능, 변수를 외부에서 사용가능 하게 할지 선택 할 수 있다. ( 이건 자바에서 접근제한자, public 또는 private,를 생각하면 된다. )

Node에서는 외부에 노출시키는 것을 export한다고 한다. ( public으로 만든다는 것을 의미함 )

export의 방법은 첫째, 둘 이상의 기능 및 변수를 export할 때에는 exports객체의 속성을 세팅해주면 된다. 둘째, 하나의 기능이나 변수만 export할 때에는 module.exports 속성을 이용한다.


3.1.1 모듈 생성하기


모듈은 a.하나의 파일이거나, b.디렉토리내에 하나 이상의 파일로 구성될 수 있다.

디렉토리 구조일 경우에는 디렉토리 내부에 index.js파일을 main파일로 사용한다. ( 이 파일 명은 overridden 가능하다. )


하나의 파일을 모듈로 만들 때에는 아래처럼 정의한다.

--------------- convert.js -------------

var num = 0.5;

exports.convertNum = function(myNum) {
     return Math.round(myNum / num);
}

----------------------------------------

위 코드를 보면 exports의 속성으로 convertNum 함수를 정의했다. 이는 외부에서 접근이 가능한 public 함수이다. 하지만 num 변수는 exports속성으로 정의하지 않았으므로 convert.js파일 내에서만 접근 가능한 private 변수이다.


이렇게 만들어진 모듈을 test-convert.js 파일에서 사용하려면 아래처럼 할 수 있다.


--------------- test-convert.js -------------

var convert = require('./convert');

console.log(convert.convertNum(5));

-----------------------------------------

여기서 require()에 명시한 "./convert"는 현재 디렉토리의 convert.js파일을 import한다는 의미이다. "./"를 붙이지 않으면 오류가 난다. require할 때 core module에 대해서는 상대경로를 사용하지 않고 바로 모듈명만 명시하도록 되어있는데 convert.js는 core module이 아니라 방금 우리가 만든 module이기 때문이다.

require()는 convert.js에 명시한 exports의 속성을 return한다. 따라서 var convert에는 convert.js(모듈)에서 정의한 exports 객체의 내용,즉, convertNum 함수가 할당되었다고 할 수 있다. 


3.1.2 module.exports 사용하기 ( single 변수, 함수, 객체 export 시 사용 ) 


module을 객체지향 관점에서 사용하고자 할 때에는 생성자 함수를 return하도록 한 뒤 아래처럼 사용할 수도 있을 것이다.


---------------- convert.js --------------

var Convert = function (num){

     this.num = num;

}

Convert.prototype.convertNum = function(myNum) {
     return Math.round(myNum / num);
}

// exports = Convert; ( 이렇게 사용 금지 )

module.exports = Convert; 

---------------------------------------

------------- test-convert2.js ------------

var Convert = require('./convert');

var num = 0.5

var convert = new Convert(num);

console.log(convert.convertNum(5));

---------------------------------------

위 코드에서 중요한 부분은 module.exports = Convert; 라인이다.


첫 예제에서 exports를 이용해서 property값을 설정함으로써 convert모듈에서 정의한 convertNum() 함수를 사용할 수 있었다. 이는 exports가 module.exports를 참조하는 global변수였기에 가능했다. 따라서 exports = Convert; 라고 해버리면 exports는 더이상 module.exports를 참조하는 변수가 아닌것이 된다. 


3.1.3 node_modules 디렉토리


앞에서 module을 require할 때 core module이 아니면 상대 경로를 적어주고, core module이면 그냥 이름을 써도 된다고 했다.

그러면 내부적으로 어떤 로직을 타길래 core module은 이름만 적어도 되는 걸까? Node가 module을 찾는 로직은 아래와 같다.



 

자, 그럼 내가 만든 모듈을 상대경로 지정을 하지 않고 쓰려면 어떻게 하면 될까?

그냥 node_modules 안에 넣어버리면 된다. 실제로 npm(Node Package Manager)를 이용하여 install을 하면 node_modules 디렉토리에 설치가 된다.


3.1.4 주의사항

첫번째 주의사항은 디렉토리 module 사용상 주의사항이다.

디렉토리 묶음으로 module을 만들었을 때에는 package.json에 명시하지 않는한 index.js파일이 존재해야한다.

아래 그림은 connect module을 설치했을 때 볼 수 있는 패키지 트리이다.


connect 디렉토리 안에 index.js라는 파일이 있는 것을 볼 수 있다. 

이제 이 파일명을 index.js 가 아닌 index_2.js 로 바꿔보자.

그리고 테스트 프로그램을 하나 만들어서 require('connect'); 한 줄만 입력해보고 실행해보자. 

그럼 오류가 날 것이다.

일반적으로 디렉토리 구조의 module에서는 index.js 라는 파일명을 사용하지만, 굳이 바꾸고 싶다면 package.json 파일에 명시해주면 된다.


package.json 파일을 열어서 "main" 을 찾아보자. 아마 없을 것이다. 아래처럼 main속성을 넣어보자.

{  // package.json 1번째 라인

"main":"index_2.js"

--생략--

} // package.json 마지막 라인


이제 다시 실행해보면 에러없이 실행되는 것을 확인할 수 있다. ( core module이니 파일명은 다시 index.js로 바꿔주자. Now~~ )


참고로 아래 그림은 directory 형태의 module을 require했을 때 처리되는 로직이다.



두 번째 주의사항은 "monkey patching"이다. 원숭이 패치? 이름이 참 희한하다.

monkey patching은 module을 객체로 caching하는 Node의 능력 때문에 가능한 것으로, 만약 애플리케이션에서 서로 다른 두 파일이 하나의 module을 require한다고 가정해보자. 이 때 동일한 module을 두 번 require했다고해서 module파일을 두 번 읽어들일 필요가 없도록 Node에서 캐시에 넣어둔다. 하지만, 이 두번 째 require에서 이 캐시에 들어있는 module정보를 변경(alter)할 수 있는 기회를 갖게 된다. 이렇게 변경하는 것을 monkey patching이라고 하는데, 이것을 잘 이용하면 module이 다른 module의 동작(behavior)를 수정할 수 있을 뿐 아니라 개발자가 다른 버전의 module을 만들 필요가 없도록 해준다. ( 이건 동적 BCI라고 이해했다. )


3.2 비동기식 프로그래밍 테크닉


3.2.1 Callback함수를 이용한 one-off 이벤트 핸들링


var fs = require('fs');
fs.readFile('./blah.txt', function(err, data) {
     if (err) { throw err };
     // do something
});


3.2.2 EventEmitter를 이용한 반복 이벤트 핸들링 


var net = require('net');
var server = net.createServer(function(socket) {
     socket.on('data', function(data) {
          socket.write(data);
     });
});
server.listen(8080);


1회성 이벤트 처리시 once 사용.


var net = require('net');
var server = net.createServer(function(socket) {
     socket.once ('data', function(data) {
          socket.write(data);
     });
});
server.listen(8080);


EventEmitter 확장하기


function Watcher(watchDir, processedDir) {
     this.watchDir = watchDir;
     this.processedDir = processedDir;
}

var events = require('events'), util = require('util');
util.inherits(Watcher, events.EventEmitter);     // == Watcher.prototype = new events.EventEmitter();


var fs = require('fs'), watchDir = './watch', processedDir = './done';
Watcher.prototype.watch = function() {
     var watcher = this;
     fs.readdir(this.watchDir, function(err, files) {
          if (err) throw err;
          for(var index in files) {
               watcher.emit('process', files[index]);
          }
     })
}
Watcher.prototype.start = function() {
     var watcher = this;
     fs.watchFile(watchDir, function() {
          watcher.watch();
     });
}

var watcher = new Watcher(watchDir, processedDir);


watcher.on('process', function process(file) {
     var watchFile = this.watchDir + '/' + file;
     var processedFile = this.processedDir + '/' + file.toLowerCase();
     fs.rename(watchFile, processedFile, function(err) {
          if (err) throw err;
     });
});


watcher.start();



3.2.3 비동기식 어플 개발시 주의할 점


동기식에서는 프로그램의 실행 순서가 명확하지만 비동기식에서는 실행 순서가 명확하지 않다. 

Node의 event loop는 비동기 로직이 완전히 완료되었는지 추적을 합니다. 아무리 비동기 로직이라도 완전히 끝나지 않았다면 프로세스는 종료되지 않습니다.


[ Javascript 의 Closure에 대한 개념 링크 ]



3.3 비동기식 로직을 순서대로 처리하기


Nimble 을 이용한 sequencing 처리

Nimble 이외에도 Step, Seq와 같은 module이 이미 존재함. 가져다 쓰면 됨.





[[궁금증 들.....]]

1. 의존성모듈 package.json 파일명을 다른걸로 바꿀 수 있나??

npmjs.com에서 말하는 npm에서 말하는 package라 함은 아래와 같습니다.


A package is any of:

  • a) a folder containing a program described by a package.json file
  • b) a gzipped tarball containing (a)
  • c) a url that resolves to (b)
  • d) a <name>@<version> that is published on the registry with (c)
  • e) a <name>@<tag> that points to (d)
  • f) a <name> that has a latest tag satisfying (e)
  • g) a git url that, when cloned, results in (a).

또한 module이라 함은


  • A folder with a package.json file containing a main field.
  • A folder with an index.js file in it.
  • A JavaScript file.

로 정의를 하고 있습니다. 

따라서, package.json 파일을 다른 파일명으로 사용한 다는 것은 불가능합니다.


출처 : https://docs.npmjs.com/how-npm-works/packages


2. 이벤트 루프 = 멀티쓰레드????, 비동기와 멀티쓰레드의 차이점??

이벤트 루프는 결국 멀티쓰레드, 폴링과 같은 기술을 사용하는 것이라는 결론입니다. 

아래는 참조할 만한 링크들입니다.


http://jeremyko.blogspot.kr/2012/12/nodejs-thread-poollibuv.html    ( 한글 블로그 )


http://nikhilm.github.io/uvbook/basics.html#event-loops


http://stackoverflow.com/questions/21485920/single-threaded-event-loop-vs-multi-threaded-non-blocking-worker-in-node-js



3. monkey patching - 왜 첫 번째 require할 때 수정가능X ?

이건 사실 질문 자체가 잘못되었던 것 같다. monkey patching에 대해서는 아래 링크에 설명이 나와있다.


http://fredkschott.com/post/2014/09/nodejs-dangerous-module-design-patterns/


결국 monkey patching 이라는 것은 기존에 있던 것을 overwrite한다는 것인데 require를 처음 할 때에는 overwrite할 게 없으니까 동일한 모듈을 다른 파일에서 또 require 할 때에 수정할 수 있는 기회를 준다고 책에서 설명을 한 것 같다. 이 "원숭이 패치"에 대해서는 책에서 자세하게 설명을 안하고 있는데 위 링크에서 설명하는 것을 보면 그 위험성 때문에 그런게 아닌가 싶다. 가독성도 떨어질 것 같고..

💻 Programming

[ Node.js In Action ] 1장 요약

1장은 정말 간단하게 노드를 소개합니다.


Node.js 는 asynchronous하고 event-driven이다.


asynchronous는 비동기식이라는 말이고 비동기식이라 함은 어떤 요청이 들어왔을 때 해당 요청을 처리해야하는 녀석을 호출하고 그 녀석이 일을 끝내기를 기다리는 것이 아니라 나는 또 다른 요청을 기다린다는 말이다.

Event-driven이라는 것은 이벤트가 발생하기를 기다리고 있다가 해당 이벤트가 발생할 때 어떤 일을 한다는 것을 말한다. 여기서 이벤트는 사용자가 버튼을 클릭하는 것이 될 수도 있고, 스크롤바가 제일 밑으로 내려갔다거나, 사용자가 마우스를 특정 영역에 올려놓는다거나 하는 등의 동작을 말한다.


Node.js는 DIRTy 애플리케이션을 위한 플랫폼이다. ( 프레임웍이 아니다 )


여기서 DIRTy라 함은 Data-Intense Real-Time 을 말한다. 


끝에 y는 왜 붙였을까? 궁금해졌다.  

"DIRT애플리케이션"과 "DIRTy애플리케이션"이라는 단어를 비교해보면, dirt라는 단어는 먼지, 떼, 진흙 등을 의미하는 명사이고 dirty는 dirt의 형용사로서 더러운, 지저분한 이라는 의미를 갖고있다. 뭐가 더 듣기 좋은지 한국말로 비교해보자. "먼지 어플", "진흙 어플", "떼 어플"이라는 말과 "더러운 어플", "지저분한 어플"이라는 말, 어떤 이름이 더 자연스럽고 이해하기 쉬운가? 실시간으로 데이터 처리량이 많은 어플을 얘기할 때 "먼지 어플"과 "지저분한 어플", 둘 중 하나를 선택한다면 당연히 지저분한 어플을 선택하는게 사람들이 이해하기가 훨씬 쉽다. 그래서 끝에 y를 붙여서 느낌을 살린거다.

뭐 이건 어디까지나 나의 개인적인 생각이다.


저 두 가지 내용이 1장에서 얘기하는 내용이다.


그럼 2장으로 넘어가볼까? 

아래 링크는 노드 튜토리얼입니다.


노드 설치부터 시작해서 Express, connect 모듈 사용해서 정말 간단한 웹사이트 만드는 것 까지 짧고 간결하게 설명해주고 있습니다.


비록 영문이긴 하지만 Node.js를 깊이 공부하기전에 노드가 뭐지? 하시는 분들이 보면 상당히 좋은 영상인것 같아서 공유합니다.


유툽에 한글판 노드 강좌가 없어서 영문판이라도 봐볼까 해서 본건데 생각보다 상당히 도움이 많이 된 튜토리얼이었습니다.


https://www.youtube.com/watch?v=-u-j7uqU7sI&list=PL6gx4Cwl9DGBMdkKFn3HasZnnAqVjzHn_&index=1

https://www.youtube.com/watch?v=-u-j7uqU7sI&list=PL6gx4Cwl9DGBMdkKFn3HasZnnAqVjzHn_&index=1




💻 Programming/CSS

[CSS] 포지션(position) 속성 강좌

CSS position 속성 정복하기

 

안녕하세요, 오늘은 자꾸 헷갈리게 만드는 포지션 속성에 대해서 포스팅을 하려합니다.

 

프론트 개발자가 아니다보니 종종 css 를 사용하게 될때마다 제일 헷갈리는게 바로 이 포지션 속성입니다.

 

포지션 속성은 화면상에서 어떤 엘리먼트(요소)의 위치를 어디다가 줄지를 결정할 수 있도록 해주는 속성입니다.

 

즉, 포지셔닝을 할 방법을 지정해주는 속성입니다.

 

화면을 꾸밀 때 html파일에 태그를 써주는데 이때 적힌 순서대로 위에서 아래로 그냥 보여줄 지, 아니면 상위 엘리먼트와 겹쳐서 보여줄 지, 또는 아예 동떨어져서 저~~~~~아래쪽에 위치시킬지도 결정을 할 수 있도록 해줍니다.

 

직접적으로 위치값을 지정해주는 속성은 아니지만 이 속성값에 따라 위치값이 적용이 될지 안될지가 결정되기 때문에 아주 중요한 속성이라고 할 수 있습니다.

 

자, 그럼 이 포지션 속성의 속성값에 어떤 것들이 있는지 먼저 살펴볼까요?

 

포지션 속성의 속성값은 다음 네 가지가 있습니다.

  • static
  • relative
  • fixed
  • absolute

이런 속성을 갖는 엘리먼트들은 부수적으로 top, bottom, left, 그리고 right 속성을 갖게 됩니다.

 

물론 부수적인 값들을 주지 않을 수도 있습니다. 

 

어쨌든 이런 부수적인 위치값들은 position 속성이 없으면 아무런 영향도 미치지 않습니다. 

 

또한 포지션 값에 따라 다르게 동작하기도 합니다.

 

그럼 제일 첫번째 속성부터 한번 살펴보도록 하겠습니다.

 

1. static 

 

static 속성은 궂이 우리가 입력해주지 않아도 기본적으로 적용이 되어있는 속성입니다.

 

예를 들어, 아래 처럼 div 태그에 position 속성을 주지 않았다고 해보죠.

 

<div>이건 static 포지션 속성이 적용된겁니다.</div>

 

아무런 값도 주지 않았지만 저 div 태그에는 position:static 이라는 속성이 기본적으로 주어집니다. 즉,

 

<div style="position:static;">이건 static 포지션 속성이 적용된겁니다.</div>

 

와 동일한 태그가 되는 것이죠.

 

static 속성은 엘리먼트를 위에서부터 아래쪽으로 순서대로 배열시켜주는 속성입니다.

 

네 가지 속성 중에서 유일하게 top, right, bottom, left 속성의 값을 무시하는 속성입니다.

 

 

2. relative

 

 position: relative; 속성을 갖는 엘리먼트들은 원래 기본적인 포지션에서 상대적인 포지션을 가질 수 있도록 해줍니다.

 

위에서 얘기했던 부수적인 옵션들(top, right, bottom, 그리고 left)을 이용해서 원래의 위치에서 상대적인 위치를 지정해줄 수 있습니다. 

 

여기서 말하는 "원래의 위치"라는 것은  position속성에 아무런 값을 주지 않았을 때, 즉, static 일때의 위치를 말합니다. 

 

또한, 다른 컨텐트가 이 엘리먼트에 의해 생긴 빈 공간에 위치할 수는 없습니다.

 

빈공간이란 원래 위치해야할 곳을 의미합니다.

 

relative 속성과 static 속성의 차이는 

static 속성의 경우 top, right, bottom, left 속성으로 위치를 변경할 수 없지만, 

relative는 그것이 가능하다라는 점입니다. 

 

예제를 통해서 static과 relative의 차이를 한번 볼 까요??

 

html파일을 하나 만들어서 아래 코드를 작성해주세요.

 

 

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
<!DOCTYPE html>
<html>
<head>
<style>
div.static {position: static;border: 3px solid #00f;}
div.relative {position: relative;border: 3px solid #f00;}
</style>
</head>
<body>
 
<div class="static">
This div element has position: static;
</div>
<div class="relative">
This div element has position: relative;
</div>
<div class="static">
This div element has position: static;
</div>
<div class="relative">
This div element has position: relative;
</div>
 
</body>
</html>
cs
 

위 페이지를 열어보면 아래처럼 화면에 출력이 됩니다. 

 

자, 코드를 보면 static 클래스와 relative 클래스 모두 동일한 속성을 갖고있는 것을 알 수 있죠. top, right, bottom, left의 값은 둘 다 없습니다. 이 경우 relative와 static은 아무런 차이가 없습니다. 그저, 태그 순서대로 화면에 출력되죠.

 

이제 static 클래스의 속성에 left:15px 을 한번 줘볼까요?

 

1
2
3
4
<style>
div.static {position: static;border: 3px solid #00f;left: 15px;}
div.relative {position: relative;border: 3px solid #f00;}
</style>
cs

 

결과가 어떻게 나올까요?

 

아무런 차이가 없을 겁니다. static은 top, right, bottom, left 속성값을 무시하기 때문이죠.

 

그럼 이번에는 relative에 left:15px; 속성을 줘 봅시다.

 

 

1
2
3
4
<style>
div.static {position: static;border: 3px solid #00f;}
div.relative {position: relative;border: 3px solid #f00;left: 15px;}
 
</style>
cs

 

 

이번에는 아래 그림처럼 relative 클래스에 해당하는 div 엘리먼트가 움직인 것을 확인할 수 있습니다.

 

 

 

이제 아래처럼 relative클래스에 top:15px; 속성을 추가해 보겠습니다.

 

 

1
2
3
4
<style>
div.static {position: static;border: 3px solid #00f;}
div.relative {position: relative;border: 3px solid #f00;left: 15px;top: 15px;}
 
</style>
cs

 

그럼 어떻게 나올지 상상이 가시나요?

 

결과는 아래처럼 나옵니다.

 

 

 

relative 클래스에 속한 div 엘리먼트들이 아래쪽으로 15px만큼 내려온 것을 확인할 수 있습니다.

 

이제 top속성의 값을 100px로 세팅해보세요. relative 클래스의 div 엘리먼트들이 아래쪽으로 한참 내려가는 것을 확인할 수 있습니다. 여기서 중요한 점은, relative 클래스의 엘리먼트들이 아래로 내려갔다고 해서 static 클래스의 엘리먼트들이 위쪽으로 이동하거나 하지는 않는 다는 것입니다.

 

 

3. fixed

 

fixed 속성은 화면에 보이는 위치가 기준이라고 생각하시면 됩니다. 

 

한번 위치가 결정되면 화면에서 스크롤이 생긴다해도 사라지지 않고 처음 화면에 출력되었던 그자리에 고정적으로 위치합니다. 

 

그래서 속성 이름이 fixed 인겁니다. 

 

이 속성의 경우 relative처럼 top, right, bottom, left 속성으로 위치값을 지정해줄 수 있지만, 

relative 속성과는 달리 원래 있어야 할 위치, 즉, static일 경우에 위치할 곳에 빈공간을 만들지 않습니다.

 

여기서 빈공간을 만든다는 것은 다른 엘리먼트가 그 자리에 위치할 수 없도록 한다는 말과 같습니다.

 

이 속성은 말 그대로 화면을 기준으로 어찌보면 절대적인 위치를 설정하는 것이므로 아마 가장 쉽게 이해할 수 있는 속성이라 생각합니다. 따라서 예제는 생략합니다.

 

 

4. absolute

 

이 속성은 가장 가까운 positioned 조상 엘리먼트에 상대적인 위치를 설정할 수 있도록 해줍니다.

 

fixed 속성이 화면에서 상대적인 위치를 결정할 수 있도록 해주는 속성이라면,

 

absolute는 positioned 조상 엘리먼트에서 상대적인 위치를 결정한다는 점에서 차이가 있습니다.

 

만약, 가장 가까운 positioned 조상 엘리먼트 가 없다면 body를 기준으로 하며 위치값에 따라 스크롤이 생기기도 합니다.

 

여기서, positioned 엘리먼트라는 것은 position 속성 값이 static인 것을 제외하고 position 속성을 갖고있는 엘리먼트를 의미합니다.

 

이 속성은 쇼핑몰 같은 곳에서 사진 위에 왼쪽, 오른쪽 화살표를 오버랩 시키고 해당 화살표를 클릭할 때마다 사진을 바꿔주는 기능을 추가할 때 많이 사용하는 것 같습니다.

 

그게 어떻게 가능하냐구요? 이제부터 저와 함께 살펴보시죠.

 

우선  html 페이지를 하나 만들고 아래처럼 소스를 적어주세요.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<style>
div.relative {width: 360px;height: 200px;border: 3px solid #00f;position: relative;}
div.absolute {width: 200px;height: 100px;border: 3px solid #f00;position: absolute;}
</style>
</head>
<body>
 
<div class="relative">position: relative;
  <div class="absolute">position: absolute;</div>
</div>
 
</body>
</html>
cs

 

위 코드를 보면, relative 클래스의 div 엘리먼트 안에 absolute 클래스의 div 엘리먼트가 존재하고 있습니다.

 

absolute 포지셔닝은 상위에 positioned 엘리먼트가 필요하다는 것을 기억해주세요.

 

여기서 absolute 클래스 div 엘리먼트(12라인)의 positioned 조상 엘리먼트는 relative 클래스 div 엘리먼트(11라인)가 됩니다.

 

위 코드를 화면에 출력해보면 다음과 같습니다.

 

absolute 박스가 relative 박스 안에 있는 모양이죠.

 

이제 absolute에 right:0; 속성을 추가해보도록 하겠습니다.

 

1
2
3
4
<style>
div.relative {width: 360px;height: 200px;border: 3px solid #00f;position: relative;}
 
div.absolute {width: 200px;height: 100px;border: 3px solid #f00;position: absolute;right:0;}
</style>
cs

 

그리고 화면을 새로고침 해보면 ???

 

absolute 박스가 오른쪽으로 붙은 것을 확인할 수 있습니다.

 

 

이제 relative 클래스에 left:150px; 속성을 추가해 보죠.

 

1
2
3
4
<style>
div.relative {width:360px;height:200px;border:3px solid #00f;position:relative;left:150px;}
 
div.absolute {width:200px;height:100px;border:3px solid #f00;position:absolute;right:0;}
</style>
cs

 

어떤 모양이 나올까요? absolute 박스는 relative 박스 안에 있는데 relative 박스를 우측으로 움직였습니다.

 

relative 박스의 좌측에 150px만큼 공간을 추가했고, 그 안에있는 absolute 박스는 relative 박스 오른쪽에 꼭 붙어있는 것을 확인할 수 있습니다.

 

즉, absolute 박스는 relative 박스에 상대적인 위치에 고정된다는 것을 알 수 있죠.

 

이 속성을 응용하면 relative 박스에 이미지가 로딩 되도록 하고, absolute 박스에 화살표 이미지가 로딩되도록 해서 두 개의 이미지를 오버래핑 시킬 수도 있습니다.

 

이건 직접 한번 해보세요 ~~

 

 

 

 

자, 어떤가요? 이제 CSS를 이용한 포지셔닝을 마음대로 할 수 있을 것 같나요?

 

그렇다면 다행이네요~ ^-^ 제가 설명을 잘 해드린 것이니까요 ㅎㅎㅎ

 

이만 오늘의 포스팅을 마치겠습니다.

 

궁금한 점이 있으시면 댓글남겨주세요 아는 한 열심히 답변드리겠습니다 ^-^

 

 

안녕하세요, 케이치입니다.


이번 포스팅에서는 자바로 텍스트 파일의 내용을 읽어서 출력해보는 소스를 보여드리겠습니다.


우선 CLI 환경이든 다른 IDE 툴 환경이든 Java 프로그래밍을 실행하려면 메인 함수가 필요하죠.


그래서 클래스 파일을 만들면 public void main(String[] args) {} 함수를 생성해서 쓰죠.


이때  main메서드의 인자로 받는 저 String[] args 가 파일명을 입력받기 위한 통로입니다.


커맨드 라인 명령으로 java myapp a.txt  라고 입력을 해서 실행하면 저 args에 a.txt라는 스트링 값이 들어가게 됩니다.


즉, 파일명을 입력받게 되는 것이죠.


파일명을 입력받았으면, 우리는 그 파일을 열어서 안에 있는 내용을 출력하기만 하면 되는 겁니다.


그렇다고 무작정 파일을 열 수는 없을 겁니다. 만약 그 파일이 존재하지 않으면?? 에러 처리도 해줘야 겠죠?


자, 그럼 하나하나 차근차근 해 볼까요?


우선 클래스를 하나 생성하고 메인 함수를 만들어 봅시다.


public class ReadFileAndPrint {     public static void main(String[] args) {     } }


ReadFileAndPrint라는 이름의 자바파일을 하나 생성해서 메인함수를 정의했습니다.


자, 그럼 이제 뭘 해야 할 까요? 저 위에서 말씀드린 것처럼 파일명을 입력받아서 args로 전달해주는 것은 자바에서 알아서 자동으로 해줄테니


우리는 그 전달받은 파일명을 가지고 지지고 볶고 하면 되겠죠?


일단 입력 받은 파일명이 존재하는지를 확인해서 에러 처리를 해줘야겠습니다.



public static void main(String[] args) {
		
	// 1. 입력받은 파일이 있는지 검사
	if ( args.length == 0 ){
		System.out.println("입력받은 파일이 없습니다.");
		System.exit(0);
	}
}


위 코드는 args로 전달받은 값이 있는지 스트링 배열의 길이를 검사해서 입력받은 값이 없으면 메시지를 출력하고 프로그램을 종료하도록 해놓았습니다.


자, 그럼 실제로 입력 받은 값이 있을 때는 어떻게 해야 할 까요?


그 입력받은 값으로 파일을 읽어들여야 겠죠?


이제 아래 코드를 추가합니다.



// 2. 입력받은 파일이 존재하는지 검사
File file = new File(args[0]);

if ( file.exists() ){

}


입력받은 파일이 한개라는 가정 하에 args배열의 0번째 인덱스에 있는 값만 사용하고 있습니다.


파일을 자바소스로 읽어들일 때는 File 객체를 사용합니다. File 생성자에 파일명을 담고있는 args[0] 을 전달하여 new 키워드로 생성하면 해당 이름의 파일을 File 객체로 만들어 줍니다.


그리고 if 문을 통해서 해당 파일명을 갖는 파일이 실제로 시스템 상에 존재하는지 확인을 합니다.


if 문 안에서는 해당 파일명을 갖는 파일이 실제로 존재했을 때 해야할 일을 코드로 적어주면 되겠죠.


우리는 File  객체를 BufferedReader 를 이용해서 좀더 다루기 쉬운 형태로 바꿀 겁니다.


이제 if 문 안에 다음 소스를 추가해 볼까요?



// 3. 텍스트 파일의 내용을 읽어서 출력 BufferedReader br = new BufferedReader(new FileReader(file)); while( br.ready() ){ System.out.println(br.readLine()); } br.close();


위 코드를 보면 File 객체인 file을 FileReader로 감싼 다음 다시 BufferedReader로 감쌌습니다.


이렇게 하면 BufferedReader 의 readLine() 메서드를 이용해서 손쉽게 라인 단위로 파일을 읽어들일 수 있습니다.


내용이 존재하는지는 br.reader() 를 이용해서 검사를 하고 true가 반환될 경우에 계속해서 한줄씩 읽어내려가면서 출력하도록 로직이 구성되어있음을 확인할 수 있습니다.


자, 그럼 전체 소스를 다시 한번 보도록 하죠.



public class ReadFileAndPrint {     public static void main(String[] args) throws IOException {

// 1. 입력받은 파일이 있는지 검사 if ( args.length == 0 ){ System.out.println("입력받은 파일이 없습니다."); System.exit(0); } // 2. 입력받은 파일이 존재하는지 확인 File file = new File(args[0]); if ( file.exists() ){     // 3. 파일이 존재하면, 텍스트 파일의 내용을 읽어서 출력     BufferedReader br = new BufferedReader(new FileReader(file));     while( br.ready() ){ System.out.println(br.readLine());     }     br.close(); }

}



이제 자바 클래스 파일을 컴파일하고 실행할 때 인자로 원하는 텍스트 파일의 절대경로명을 넣어주세요. 


상대경로명을 넣을 경우 환경에 따라 파일을 못 찾을 수 있습니다.


파일의 내용이 잘 출력된다면 잘 따라오신겁니다.


어때요 ? 별로 어렵지 않죠??


이상으로 자바로 텍스트 파일 읽어서 출력하기 포스팅을 마치겠습니다.


궁금하신 점 있으면 댓글달아주세요 ^-^

스프링 웹 개발을 하다보면 상당히 많은 에러 메시지와 마주하게 됩니다.


특히 개발 환경을 구성할 때 자주 보게 되죠.


최근에 개인적으로 스프링 웹 개발 환경을 구성을 해보다가 다음과 같은 에러 메시지를 보게 되었습니다.


No converter found for return value of type: class java.util.ArrayList


스프링 메이븐 프로젝트로 구성을 했구요, 스프링 버전은 4.3.2 버전이었습니다.


pom.xml 에 jackson-core 라이브러리를 디펜던시 목록에 추가해놓은 상태여서 당연히 될 거라고 생각했었는데 에러가 발생하더군요.


스프링 컨트롤러에서 @ResponseBody 어노테이션을 사용해서 List<Dto> 를 반환하려고 할 때 발생한 에러입니다.


구글링을 해보니 해결 방법은 간단합니다.


jackson-bind 라이브러리를 추가해주는 겁니다.


그래서 아래 디펜던시 부분을 추가를 해서 해결했습니다.


<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

  <artifactId>jackson-databind</artifactId>

<version>2.5.4</version>

</dependency>



@ResponseBody 어노테이션을 사용하려면 jackson-bind 라이브러리가 필요하다는 것을 알게되었네요~


역시 누가 구성해놓은 환경에서만 개발을 하다가 혼자서 하려니 이런저런 다양한 케이스들을 많이 보게 되는 것 같습니다.


💻 Programming

윈도우에서 Docker로 PostgreSQL 띄우기

오랜만에 포스팅 하네요.


자, 오늘은 윈도우에서 도커를 이용해서 PostgreSQL 설치하고 접속해보도록 할 겁니다.


왜 도커에 설치하냐구요?


docker를 이용하면 정말 쉽고 빠르게 다양한 환경을 구성할 수 있다는 정말 정말 큰 장점이 있기 때문이죠.


가장 먼저 PC에 도커를 설치해야 겠지요.


이번 포스팅에서는 도커 설치 부분은 건너뛰는 것으로 하겠습니다. 


구글링하면 엄청 많이 나올거거든요. 그걸 참고 하시면 될 것 같습니다.


윈도우에는 윈도우용 인스톨러를 이용하면 금방 설치를 할 수 있으니까요.


일단 제 환경은 윈도우8에다가 도커를 윈도우 인스톨러를 이용해서 설치를 한 상태입니다.


사실 도커 조금 만져보신 분들한테는 엄청 쉬운 일이 되겠지만 처음 해보시는 분들한테는 조금 어려울 수도 있습니다.


다음 순서에 따라서 진행해보도록 하죠.


1. cmd 창에서 docker 가 제대로 설치되었는지 확인

2. docker를 이용해서 postgreSQL 설치 및 데몬 형태로 실행

3. DBeaver 데이터 베이스 관리 툴을 이용하여 접속 및 dml문 실행


자, 그럼 1번 부터 해볼까요?


일단 cmd창을 띄웁니다. 어떻게 띄우는지 모르시는 분들은 윈도우키를 한번 누른 뒤에 cmd 입력하고 엔터 치시면 됩니다.

(윈도우 7 이하인 경우에는 윈도우키 + c 를 눌러서 실행 창을 띄운뒤에 cmd 라고 입력해주시면 될겁니다.)



위와 같은 화면이 나왔으면 이제 docker 라고 치고 엔터를 칩니다.



위 사진처럼 옵션 목록이 주루루룩 나와주면 제대로 설치가 된겁니다.


이제 아래 명령어를 실행시켜 주세요


docker run --name myPostgreSQL -e POSTGRES_USER=GilDong -e POSTGRES_PASSWORD=1234 -d -p 5432:5432 postgres


위 명령어는 postgresql 최신버전을 다운로드 받아서 로컬 PC의 5432 포트와 postgresql 컨테이너의 5432 포트를 연결시켜주고 myPostgreSQL 이라는 이름의 컨테이너를 실행시켜줍니다.


-d 옵션은 데몬으로 띄우라는 의미입니다.

-e POSTGRES_USER=GilDong -e POSTGRES_PASSWORD=1234 옵션은 GilDong이라는 이름의 1234 비번을 갖는 새로운 사용자를 생성하라는 의미입니다.


각 옵션에 대한 설명은 docker help run 을 실행시키면 확인 하실 수 있습니다. 

docker run --help 라고 하셔도 동일한 결과를 확인 할 수 있습니다.


만약, 다운로드 받지 못하고 이상한 에러가 발생한다면 트러블슈팅을 해주셔야 합니다.


참고로, 윈도우 환경에서는 CMD창을 띄울 때마다 docker-machine 을 이용해서 환경변수 설정을 해줘야 docker의 사용이 가능하더군요.


에러메시지를 잘 읽어보고 구글링 조금만 하시면 금방 해결할 수 있을 겁니다.


자, 그럼 여기까지 잘 설치가 되신 분들은 이제 DBeaver와 같은 데이터베이스 툴을 실행을 시켜주세요.


물론 postgreSQL 드라이버를 지원하는 툴을 사용해야겠죠.


DBeaver 가 실행이 됐으면 아래 사진처럼 좌측 상단의 아이콘을 클릭해서 새로운 커넥션을 생성합니다.








여기서 Host에는 docker-machine ip 명령어를 실행해서 나오는 아이피를 적어주시면 됩니다.

만약 docker-machine 이 설치되어있지 않다면 별도로 설치를 해주셔야 합니다.

(참고로 docker-machine은 docker 프로젝트 중 하나 입니다.)


그리고 database와 user에는 위에서 실행할 때 POSTGRES_USER 옵션에 주었던 이름을 동일하게 주시면 됩니다.

패스워드 역시 마찬가지로 POSTGRES_PASSWORD 옵션에 주었던 값을 그대로 주시면 됩니다.


입력을 다 하고난 뒤 우측 하단에 있는 Test Connection ... 버튼을 클릭하여 정상적으로 접속이 되는지 확인합니다.




정상적으로 연결이 된다면 위와 같은 팝업창이 뜨게 됩니다.


별거 없죠?


이제 Next 해서 커넥션 생성하고 DML문 입력해서 실행해보시면 됩니다~


이상으로 오늘 포스팅을 마칩니다. 집에 갈 시간이 다 되어서 너무 빨리 쓰다보니 대충 쓰게 되네요.


궁금한 점이나 잘 안되시는 분들 댓글 남겨주시면 빠른 답변 남겨드릴게요~ ㅎㅎ



Legend

Excellent Good Fair Bad Horrible

자료구조별 시간복잡도 및 공간복잡도

자료구조 시간 복잡도 공간복잡도
  Average Worst Worst
  Access Search Insertion Deletion Access Search Insertion Deletion  
Array O(1) O(n) O(n) O(n) O(1) O(n) O(n) O(n) O(n)
Stack O(n) O(n) O(1) O(1) O(n) O(n) O(1) O(1) O(n)
Singly-Linked List O(n) O(n) O(1) O(1) O(n) O(n) O(1) O(1) O(n)
Doubly-Linked List O(n) O(n) O(1) O(1) O(n) O(n) O(1) O(1) O(n)
Skip List O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(n) O(n) O(n) O(n) O(n log(n))
Hash Table - O(1) O(1) O(1) - O(n) O(n) O(n) O(n)
Binary Search Tree O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(n) O(n) O(n) O(n) O(n)
Cartesian Tree - O(log(n)) O(log(n)) O(log(n)) - O(n) O(n) O(n) O(n)
B-Tree O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(n)
Red-Black Tree O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(n)
Splay Tree - O(log(n)) O(log(n)) O(log(n)) - O(log(n)) O(log(n)) O(log(n)) O(n)
AVL Tree O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(n)

정렬 알고리즘 시간복잡도 및 공간복잡도

알고리즘 시간복잡도 공간복잡도
  Best Average Worst Worst
Quicksort O(n log(n)) O(n log(n)) O(n^2) O(log(n))
Mergesort O(n log(n)) O(n log(n)) O(n log(n)) O(n)
Timsort O(n) O(n log(n)) O(n log(n)) O(n)
Heapsort O(n log(n)) O(n log(n)) O(n log(n)) O(1)
Bubble Sort O(n) O(n^2) O(n^2) O(1)
Insertion Sort O(n) O(n^2) O(n^2) O(1)
Selection Sort O(n^2) O(n^2) O(n^2) O(1)
Shell Sort O(n) O((nlog(n))^2) O((nlog(n))^2) O(1)
Bucket Sort O(n+k) O(n+k) O(n^2) O(n)
Radix Sort O(nk) O(nk) O(nk) O(n+k)

Graph Operations

Node / Edge Management Storage Add Vertex Add Edge Remove Vertex Remove Edge Query
Adjacency list O(|V|+|E|) O(1) O(1) O(|V| + |E|) O(|E|) O(|V|)
Incidence list O(|V|+|E|) O(1) O(1) O(|E|) O(|E|) O(|E|)
Adjacency matrix O(|V|^2) O(|V|^2) O(1) O(|V|^2) O(1) O(1)
Incidence matrix O(|V| ⋅ |E|) O(|V| ⋅ |E|) O(|V| ⋅ |E|) O(|V| ⋅ |E|) O(|V| ⋅ |E|) O(|E|)

Heap Operations

Type 시간복잡도
  Heapify Find Max Extract Max Increase Key Insert Delete Merge
Linked List (sorted) - O(1) O(1) O(n) O(n) O(1) O(m+n)
Linked List (unsorted) - O(n) O(n) O(1) O(1) O(1) O(1)
Binary Heap O(n) O(1) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(m+n)
Binomial Heap - O(1) O(log(n)) O(log(n)) O(1) O(log(n)) O(log(n))
Fibonacci Heap - O(1) O(log(n)) O(1) O(1) O(log(n)) O(1)

 

안녕하세요, 케이치입니다.


지난 레디스 데이타 타입 포스팅을 보셨다면 아시겠지만 레디스는 기본적으로 다섯 가지의 데이타 타입을 지원합니다.


이번 포스팅에서는 레디스 데이타 타입 별 명령어 목록 및 기능 제 1편으로 스트링 타입에 대한 설명을 하려합니다.


Redis Strings

레디스 스트링 타입은 제일 기본적인 타입입니다. Memcached의 유일한 데이터 타입인 스트링을 지원하기 때문에 레디스를 새로 배우는 사람들에게도 어렵지 않죠. 뭐 스트링은 모든 프로그래밍 언어에서 제공하는 개발자들이 가장 많이 사용하는 타입 중에 하나니까요.

레디스의 키가 바로 스트링이기 때문에 값을 스트링 타입으로 저장한다는 것은 하나의 스트링을 또다른 스트링에 매핑한다는 의미입니다. 스트링 데이타 타입은 정말 많은 용도로 사용될 수 있습니다. 예를들어 HTML 일부나 페이지 전체를 캐싱할 수도 있죠.

스트링 타입을 다루는 예제를 한번 보면서 더 알아보도록 하죠. 

레디스 프롬프트 창에서 아래처럼 입력해보세요.

> set mykey somevalue
OK
> get mykey
"somevalue"

위 예제는 SET 명령어를 이용하여 mykey라는 키에 somevalue라는 값을 매핑시킵니다.  여기서 중요한 점은 SET 명령어는  동일한 키가 이미 존재한다면 그 키에 해당하는 값을 덮어쓴다는 것입니다. 그 값이 어떤 타입이냐는 상관없이 무조건 덮어쓰니 주의하시기 바랍니다. 다시말하면 SET 명령어는 자바 프로그래밍의 = 과 같이 특정 키에 특정 값을 할당해주는 기능을 합니다.

키에 할당할 수 있는 값은 어떠한 스트링이라도 가능합니다. 바이너리 데이타 역시 가능하죠. JPEG 이미지의 바이너리 데이타도 저장이 가능합니다. 하지만 여기서 또 중요한 점이 있습니다. 어떤 값이든 그 크기가 512MB를 넘을 수는 없다는 것이죠.

SET 명령어에 옵션을 줄 수도 있습니다. 예를들어 이미 해당 키가 존재하면 실패하도록 할 수도 있고 그 반대로 이미 키가 존재할 때에만 SET하도록 할 수도 있습니다. 아래와 같이 말이죠.

> set mykey newval nx
(nil)
> set mykey newval xx
OK

스트링이 레디스의 정말 기본적인 값을 나타내는 데이터 형식이지만 여러 실용적인 기능들이 있습니다. atomic increment 가 그 한 예입니다. atomic increment가 뭔지 잘 모르신다면 atomic이 뭘 의미하는지 찾아보시면 될겁니다. 여기서 그에 대한 설명은 하지 않겠습니다.

> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152

 INCR 명령어는 스트링으로 저장된 값을 숫자형태로 변환한 뒤 1을 증가시킵니다. 그리고 그렇게 증가된 값을 다시 스트링 형태로 저장합니다. 이와 비슷하게 INCRBYDECR 그리고 DECRBY 와 같은 명령어들을 레디스는 제공하고 있습니다. 이 명령어들은 내부적으로 모두 동일하게 동작합니다. 아주 미묘한 차이만 있을 뿐이죠.

INCR 가 atomic이라는게 무슨 의미냐구요? 여러명의 사용자가 동일한 키에 대해서 INCR명령어를 요청한다고 했을 때, 요청한 사용자들이 절대로 race condition에 빠질 수 없다라는 의미입니다. 즉, 사용자 1 이 10이라는 값을 읽고, 사용자 2 역시 동일한 키에 대해서 10이라는 값을 읽은 뒤 두 사용자가 1씩 값을 증가시켰을 때 최종 결과가 11이 될 수 없다는 것이죠. 최종 결과는 항상 무조건 절대로 12 가 된다는 것을 레디스는 보장해주고 있습니다. 쉽게말하면 값을 읽고, 1 증가하고, 다시 저장하는 과정이 모두 한명의 사용자가 요청했을 때 이루어지고 그 사이에 다른 사용자가 끼어들 수 없다는 말이죠.

이 외에도 스트링을 다루기 위한 여러 명령어들이 있습니다.  GETSET 명령어는 키에 존재하는 값을 읽고 새로운 값을 세팅한뒤 읽어들인 기존값을 반환합니다. 이 명령어는 방문자수를 기록할 때 사용할 수 있겠죠. 일별로 방문자수를 기록을 해야한다면 자정이 되었을 때 GETSET 명령어로 오늘 방문자수를 읽어오고 0으로 세팅해주면 되는거죠.

여러개의 키-값 페어를 저장하거나 조회하고 싶을 때에는 MSET 와 MGET 명령어를 사용하면 됩니다.

> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30" 

MSET은 한번에 여러 키-값 페어를 저장하는 명령어이고

 MGET 은 값의 배열을 반환합니다.

Altering and querying the key space ( 키 변경, 조회, 삭제 하기 )

레디스는 키와 관련된 여러 명령어들을 제공합니다. 키 이름 변경( 이것은 위에서 보셨죠? ), 키 목록 조회 및 존재여부 파악, 또는 삭제하기와 같은 명령어들을 말이죠.

 EXISTS 명령어는 1 또는 0 을 반환합니다. 키가 존재하거나 존재하지 않을때 말이죠.

그리고 DEL 명령어는 해당 키와 그 키에 할당된 값을 삭제합니다.

> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0

위 예제를 보시면 각 명령어가 어떻게 동작하는지 쉽게 이해가 되실겁니다.

이 명령어들 외에도 무수히 많은 key space 관련 명령어들이 있습니다. 위 두 명령어와 함께 자주 사용되는 명령어 중 하나가 바로  TYPE 명령어입니다. 이 명령어는 특정 키에 저장되어있는 값의 타입을 반환합니다. 아래 예제를 보시죠.

> set mykey x
OK
> type mykey
string
> del mykey
(integer) 1
> type mykey
none

처음에 set mykey x 로 키 mykey에 값 x를 등록했습니다. 그리고 type mykey 로 mykey에 저장된 값 x의 타입을 조회해 보니 string이 나왔네요. 그 다음엔 mykey를 삭제했습니다. 그리고 다시 조회를 해보니 none 이라고 나왔네요. 해당 키에 저장된 값이 없다는 얘기겠죠.

Redis 키에 TTL (Time To Live)설정하기 

이건 단지 스트링 타입 뿐만 아니라 모든 타입에 적용되는 것이긴 합니다만 다른 타입의 데이터를 얘기하기 전에 짚고 넘어가야 할 것 같습니다. 기본적으로 키에 타임아웃 시간을 설정할 수 있습니다. 설정된 타임아웃 시간이 지나면 자동으로 키는 삭제됩니다. 이때 삭제되는것은 사용자가 직접 DEL 명령어로 삭제하는 것과 완전히 동일하게 삭제가 되는 것입니다.

 Redis expires 와 관련하여 아래 내용을 숙지하시기 바랍니다.

  • 기본적으로 밀리초 단위로 동작한다.
  • 초 단위 또는 밀리 초 단위로 만료되기 까지의 시간을 설정할 수 있다.
  • 만료일시와 관련된 정보는 디스크에 복제되어 저장되고 레디스 서버가 멈춰도 재기동 될때 현재 일시와 비교하여 삭제한다.

만료일시를 지정하는 방법은 아주 쉽습니다. 아래 예제를 한번 보시죠.

> set key some-value
OK
> expire key 5
(integer) 1
> get key (위 명령어를 입력하자마자 바로 실행)
"some-value"
> get key (5초 뒤에 실행)
(nil)

위 예제는 어떤 값을 저장한 뒤 5초로 만료시간을 지정해주고 두 번의 GET 을 호출할 때 만료시간에 따라 값이 조회가 되는지 안되는지를 확인해본 것입니다. 5초 뒤에 만료되라고 지정해주었으니 5초 뒤에는 nil이라고 뜨는 것을 확인할 수 있었습니다. 

여기서는 EXPIRE 명령어를 이용해서 만료일시를 지정해주었는데 만약에 이 만료일시를 없애기 위해서는 PERSIST 명령어를 사용할 수 있습니다. 그리고 EXPIRE명령과 동일한 기능을 SET 명령어의 옵션을 이용해서 적용할 수도 있습니다.

> set key 100 ex 10
OK
> ttl key
(integer) 9

위 예제는 키 key에 값 100을 저장하는데 10초 후에 만료되어 파기하라는 옵션을 덧붙였습니다. 그리고나서  TTL 명령어로 키 key의 만료되기 전까지의 시간(초)을 조회를 해보니 9가 나왔네요. (참고로 TTL은 Time To Live 의 약자입니다.)

초 단위가 아닌 밀리초 단위로 설정을 하려면, PEXPIRE 와 PTTL 명령어를 참고하시면 됩니다. 또는 PEXPIRE대신 SET 명령어의 옵션을 이용할 수도 있습니다.



위 내용은 레디스 공식 문서에 나와있는 내용을 스터디 할 겸 개인적으로 번역한 내용입니다. 


💻 Programming

[Redis] 레디스 키와 관련 명령어

Redis keys

Redis 에서 사용하는 key는 binary safe합니다. 

바이너리 세이프 하다는 말은 키 이름으로 어떠한 바이너리 시퀀스를 사용해도 무방하다는 말입니다. 

예를들어 JPEG파일을 저장하기 위한 키로 "foo"를 사용해도 됩니다.

또한 빈 스트링도 유효한 키로 인식합니다.


키와 관련된 또 다른 규칙은 아래와 같습니다:

  • 너무 긴 이름을 가진 키는 좋지 않습니다. 예를들어 1024 바이트 짜리 키는 매우 안좋은 것입니다. 왜냐면 메모리도 많이 잡아먹을 뿐만 아니라 키를 찾는데도 시간이 오래 걸릴 수 있기 때문입니다. 따라서 메모리 측면이나 bandwidth 측면에서 봤을 때 긴 키값이 해당 키와 관련된 값이나 서비스와 관련된 좋은 이름이라 할지라도 SHA1 등의 해시알고리즘을 통해서 길이를 줄여서 사용하는 것을 추천합니다.
  • 너무 짧은 키 역시 좋지 않습니다. "user:1000:followers"라는 키 대신에 "u1000flw" 라고 키를 정하면 나중에 키만보고 이것이 무엇에 관련된 키인지 파악하기가 쉽지 않게 됩니다. 가독성이 떨어지는 것이죠. 짧은 키가 아무리 더 적은 메모리를 사용할지라도 큰 차이가 나지 않는다면 적당히 길이를 늘려서 가독성있는 키를 만드는 것이 더 좋습니다.
  • 스키마에 집중하세요. 예를들어, "user:1000"처럼 "object-type:id" 식의 키 명명규칙은 좋은 예 입니다. 마침표(.)나 대쉬(-)를 이용하여 여러 단어를 연결하는 것도 좋습니다. "comment:1234:reply.to" 또는 "comment:1234:reply-to" 처럼 말이죠.
  • 키 하나를 저장하할 수 있는 최대 사이즈는 512 MB 입니다. 설마 키 저장하는데 이렇게 많은 메모리를 사용할 일은 없겠죠?


자 그러면 이제 Redis 에서 제공하는 키 관련 명령어의 사용법 및 종류에 대해서 간단하게 알아보겠습니다.

우선 문법은 아래와 같습니다.

Syntax (문법)

redis 127.0.0.1:6379> {명령어} {키}

{명령어} 자리에는 키 관련 명령어가 들어가면됩니다. 아래 예제를 같이 살펴볼까요?

Example (예제)

redis 127.0.0.1:6379> SET study:redis redis OK redis 127.0.0.1:6379> DEL study:redis (integer) 1

위 예제에서 DEL, SET 은 명령어 입니다. 그리고 study:redis 가 바로 key입니다. 

처음에는 SET 명령어로 study:redis라는 키에 redis라는 값을 입력을 했습니다. 

그리고나서 DEL명령어로 해당 키를 삭제를 했습니다. 키를 삭제하면 키에 매핑되어있는 값에 접근할 방법이 없어지겠죠. 

키를 삭제할 때 키가 존재하고 정상적으로 삭제가 되었다면 (integer) 1 가 출력될 것이고, 아니면 (integer) 0 가 출력될 것입니다.

Redis keys commands ( 레디스 키 명령어 )

아래 테이블은 키와 관련된 기본적인 명령어를 몇 개만 간추려 본 것입니다.:

No.

명령어 및 설명

1DEL key
이 명령어는 키가 존재한다면 삭제를 합니다
2

DUMP key 
해당 키에 저장된 값의 serialized version을 반환합니다.

3

EXISTS key 
키가 존재하는지 여부를 반환합니다.

4

EXPIRE key seconds
지정한 시간(초)가 지나면 해당 키를 삭제할 수 있도록 만료기간을 지정합니다.

5

EXPIREAT key timestamp 
특정한 시간에 키가 삭제될 수 있도록 Unix timestamp 포맷으로 특정 시각을 지정합니다.

6

PEXPIRE key milliseconds 
키의 만료기간을 밀리초 단위로 설정합니다.

7

PEXPIREAT key milliseconds-timestamp 
키의 만료 시각을 유닉스 밀리초 타임스탬프 포맷으로 지정합니다.

8

KEYS pattern 
특정 패턴과 일치하는 모든 키 목록을 조회합니다.

9

MOVE key db 
해당 키를 다른 db로 이동합니다.

10

PERSIST key 
키에 설정된 만료일시를 삭제합니다.

11

PTTL key 
해당 키가 만료되기까지의 시간을 밀리초 단위로 반환합니다.

12

TTL key 
해당 키가 만료되기 까지의 시간을 조회합니다.

13RANDOMKEY 
랜덤키 하나를 반환합니다.
14

RENAME key newkey 
키 이름을 새로운 키로 변경합니다.

15RENAMENX key newkey 
키 이름을 새로운 키로 변경합니다. (단, 새로운 키와 동일한 이름의 키가 존재하지 않을 경우에만)
16

TYPE key 
키에 저장된 데이터의 타입을 조회합니다.


이번 포스팅에서는 간략하게 레디스에 어떠한 타입이 있고 어떤 명령어로 해당 타입의 값을 추가 또는 조회하는 지 등의 예제를 보여드릴 겁니다.

레디스는 String, Hash, List, Set, Sorted Set 등의 데이터 타입을 지원합니다.


Strings (스트링)

레디스 스트링은 순차적인 바이트들입니다. 레디스에서 스트링은 binary safe한데 이것은 스트링이 알수있는 길이를 가지고 있고 어떤 특별한 종료문자에 의해 종료되지 않는 다는 것을 의미합니다. 따라서, 한 스트링당 최대 512MB 길이의 값을 저장할 수 있습니다.

Example (예제)

redis 127.0.0.1:6379> SET name "keichee" OK redis 127.0.0.1:6379> GET name "keichee"

위 예제에서 사용된 SET과 GET명령어는 데이터를 저장하거나 조회할 때 사용되는 명령어입니다.

그리고 name은 레디스에 저장할 때 키 값을 의미하며 "keichee"는 name이라는 키에 대응해서 저장될 값을 의미합니다.


Hashes (해쉬)

레디스 해쉬는 키-값 페어의 컬렉션입니다. 스트링 필드와 스트링 값을 매핑해주기 때문에 객체를 표현할 때 자주 사용됩니다.

Example

redis 127.0.0.1:6379> HMSET user:1 wow awesome keichee handsome blah handsome
OK
redis 127.0.0.1:6379> HGETALL user:1

1) "wow"
2) "awesome"
3) "keichee"
4) "handsome"
5) "blah"
6) "handsome"

위 예제에서 user:1 이라는 키에 wow, awesome, keichee, handsome, blah, handsome 이라는 값을 세팅하고 있습니다. 여기서 HMSET은 해쉬에 저장할 때, HGETALL 은 해쉬에 저장된 데이터를 조회할 때 사용되는 명령어입니다.

해쉬에 저장할 수 있는 키-값 페어의 개수는 총 (2^32) - 1 개 입니다.


Lists (리스트)

리스트는 목록형태의 데이터 타입을 말합니다. 스트링 목록을 말하며 삽입한 순서대로 정렬되어 저장됩니다. 

레디스 리스트는 제일 앞에 추가하는 것과 제일 마지막에 추가하는 명령어가 있습니다..

Example

redis 127.0.0.1:6379> lpush dblist redis
(integer) 1
redis 127.0.0.1:6379> lpush dblist mongodb
(integer) 2 redis 127.0.0.1:6379> lpush dblist rabitmq
(integer) 3 redis 127.0.0.1:6379> lrange dblist 0 10
1) "rabitmq" 2) "mongodb" 3) "redis"

리스트의 최대 길이는 (2^32) - 1 개 입니다. (총 4294967295 개).

lpush는 리스트의 제일 앞에 데이터를 추가하고자 할 때 사용하는 명령어입니다. 

lpush dblist redis 명령어는 dblist라는 이름의 리스트에 redis라는 값을 저장하라는 것을 의미합니다.

위 예제에서는 redis, mongodb, rabitmq를 dblist라는 리스트에 저장한 뒤 lrange 명령어를 이용하여 0부터 10까지의 데이터를 조회하고 있습니다.


Sets (집합)

레디스에서 집합은 정렬되지 않은 스트링 목록을 말합니다. 

세트는 추가, 삭제, 그리고 존재여부를 확인하는 명령어를 O(1) 시간복잡도로 제공합니다.

Example

redis 127.0.0.1:6379> sadd dbset redis
(integer) 1 redis 127.0.0.1:6379> sadd dbset mongodb (integer) 1 redis 127.0.0.1:6379> sadd dbset rabitmq (integer) 1 redis 127.0.0.1:6379> sadd dbset rabitmq (integer) 0 redis 127.0.0.1:6379> smembers dbset 1) "rabitmq" 2) "mongodb" 3) "redis"

NOTE: 위 예제에서 rabitmq는 두 번 추가가 되었지만 세트의 기본 속성인 유니크 속성 때문에 하나의 rabitmq만 저장됩니다.

sadd는 집합에 데이터를 추가할 때 쓰이는 명령어이며, 집합을 조회할 때에는 smembers 명령어를 사용하시면 됩니다.

조회되는 데이터는 집합에 추가된 순서와는 관계없이 랜덤하게 조회가 됩니다. 즉, 순서를 보장하지 않습니다.

set에 저장할 수 있는 데이터 개수 역시 (2^32) - 1 개입니다. 


Sorted Sets (정렬 집합)

정렬된 집합은 말그대로 집합인데 정렬된 것을 말합니다. 유니크 속성 역시 그대로 가지고 있으며. 일반 집합과의 차이점은 정렬을 위한 score 값을 가지고 있다는 것입니다.

Example

redis 127.0.0.1:6379> zadd sorted 0 redis (integer) 1 redis 127.0.0.1:6379> zadd sorted 0 mongodb (integer) 1 redis 127.0.0.1:6379> zadd sorted 0 rabitmq (integer) 1 redis 127.0.0.1:6379> zadd sorted 0 rabitmq (integer) 0 redis 127.0.0.1:6379> ZRANGEBYSCORE sorted 0 1000 1) "redis" 2) "mongodb" 3) "rabitmq"

위 예제에서 처럼 정렬집합에 데이터를 넣을때는 zadd 명령어를 사용하며 집합명 다음에 score(점수)를 명시해주는데,


이 점수는 각 데이터 마다 중복하여 사용될 수 있으며 중복된 점수를 갖는 데이터는 조회 시 순서가 보장되지 않습니다.


ZRANGEBYSCORE 명령어는 점수를 기준으로 집합을 조회 하라는 의미입니다.




이 외에도 스트링 기반의 비트맵(Bitmap)이나 하이퍼로그로그스(HyperLogLogs)와 같은 데이터 타입도 지원하고 있습니다. 


이러한 타입에 대해서 레디스 공식 문서에는 아래와 같이 설명을 하고 있습니다.

  • Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits: you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
스트링 값을 비트의 배열로 다루기 위한 특별한 명령어를 제공한다고 합니다. 각각의 비트를 세팅하거나 1로 세팅된 모든 비트의 개수를 구하거나 하는 등등의 기능을 제공합니다.
  • HyperLogLogs: this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don't be scared, it is simpler than it seems... See later in the HyperLogLog section of this tutorial.

통계학적 데이타 구조로 집합의 카디날리티를 측정하기 위해서 사용된다고 합니다. 생각보다 쉬우니까 사용하기를 두려워하지 말라고 하네요 ㅋㅋ


자세한 내용은 각각의 데이터 타입에 대한 개별 포스팅에서 확인하시기 바랍니다. ^-^


이상 케이치였습니다.


즐겁고 행복한 하루 되세요~



💻 Programming

[Redis] 레디스 시작하기 (설치하기)

Redis 는 오픈소스이며 메모리를 이용한 키-값 페어로 저장하는 데이터베이스입니다. 따라서 일반 디스크에 저장하는 데이터베이스보다 속도가 월등히 뛰어나며 웹어플리케이션에서 유용하게 사용될 수 있습니다.

Redis 는 다른 경쟁 데이터베이스와 비교했을 때 다음과 같은 차이점이 있습니다.

  • Redis 는 데이터베이스를 완전히 메모리에 저장하며 영속성을 위해서만 디스크를 사용합니다.

  • Redis 는 다른 키-값 저장형태를 사용하는 데이터베이스들과 비교했을 때 상대적으로 다양한 데이터 타입을 지원합니다.

  • Redis 는 무제한의 슬레이브 복제를 가능하게 합니다.

Redis Advantages (레디스의 장점)

  • Exceptionally Fast (매우 빠른 속도) : Redis 는 매우 빠른 속도를 자랑합니다. 초당 110000 건의 업데이트가 가능하며, 약 81000 건의 조회가 가능합니다.

  • Supports Rich data types (다양한 데이터 타입 지원) : Redis 는 기본적으로 개발자들이 알고있는 대부분의 데이터 타입을 지원합니다. 리스트(list), 집합(set), 정렬된 셋(sorted set), 해쉬(hashes) 등을 모두 지원합니다. 따라서 왠만한 문제를 해결하기 위해서 특별히 공을 들여 특정 데이터 타입에 맞도록 설계할 필요가 없어집니다.

  • Operations are atomic (동작의 원자성) : 모든 레디스 오퍼레이션은 원자성을 띄기 때문에, 두 명 이상의 사용자가 동시에 레디스 서버에 접속할 경우 업데이트된 값을 조회할 수 있습니다.

  • MultiUtility Tool () : Redis 는 멀티 유틸리티 툴이고 다양한 케이스에 사용될 수 있습니다. 예를들면 캐싱(caching), 메시지 큐(messaging-queues), 또는 세션 정보나 웹페이지 방문자수와 같은 짧은 기간동안에만 필요한 데이터들을 저장하기 위해 사용될 수 있죠.

레디스가 다른 키 - 값 저장 DB에 비해 다른 점?

  • Redis 는 더 복잡한 데이터 타입을 값으로 저장할 수 있다는 점과 그런 데이터 타입을 저장하거나 조회하는데 atomic 접근을 보장하기 때문에 여타의 키-값 저장 DB와는 다른 길로 진화를 했다고 볼 수 있습니다.

  • Redis 는 in-memory 기반이지만 디스크에 영속적인 데이터베이스이기 때문에 메모리에 저장될 수 없을만큼 많은 양의 데이터를 저장할 수는 없습니다. in-memory databases의 장점은 복잡한 데이터 구조를 다루는 것이 디스크에 저장된 동일한 데이터 구조를 다루는 것보다 쉽다는 것입니다. 이게 무슨 말인지는 아직 잘 이해가 되질 않네요.


Install Redis on Ubuntu (우분투에 레디스 설치하기)

우분투에 레디스를 설치하기 위해서는 터미널 창에서 아래와 같은 명령어를 입력하면 됩니다.

$sudo apt-get update
$sudo apt-get install redis-server


Start Redis (레디스 시작하기)

$redis-server

아주 간단하죠?

Check if redis is working? (레디스가 동작하는지 확인하기)

$redis-cli

이 명령어는 아래와 같은 레디스 프롬프트를 띄웁니다.

redis 127.0.0.1:6379>


이제 ping을 날려봅시다.

redis 127.0.0.1:6379> ping
PONG

그러면 위 처럼 PONG라는 단어가 출력이 되야 합니다. 여기까지 됐다면 레디스가 정상적으로 설치된 것입니다.



자바 프로그램을 짜다보면 현재 프로젝트의 root 경로를 필요로 할 때가 있다.


그럴 경우 아래처럼 


public static void main(String[] args) {
       
      System.out.println("Current Working Directory = " + System.getProperty("user.dir"));
}

System.getProperty 메서드에 user.dir 을 인자로 넣어주면 현재 프로젝트의 root 경로(absolute path)를 String 으로 반환합니다.


Use 'System.getProperty("user.dir")' method for getting current working directory as absolute path. The method returns a string value of the root path of the java project as an absolute path.