리눅스의 특수 권한(setuid, setgid, sticky) 외에 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

실행권한

리눅스에서 파일을 다루는 방법은 세가지가 있습니다. 파일을 읽고(read), 쓰고(write), 실행(execute)하는 것이 그 세가지입니다. 리눅스는 서버용 멀티유저 운영체제이기 때문에 권한이 매우 중요합니다. 어떤 관리자는 특정 파일에 대해서 읽고 쓸 수 있는 권한이 있을 수 있고, 다른 관리자는 수정이 불가한 파일이 있을 수가 있겠죠. 이렇게 파일을 다룰 수 있게 리눅스에서는 파일의 속성을 줄 수가 있습니다. 

ls -l 명령으로 exam.txt파일의 실행권한을 보도록 하겠습니다.

노란색으로 표시한 부분이 이 파일의 권한을 의미합니다. 이 파일을 만든 소유자는 ubuntu이고 ubuntu라는 그룹이라는 것도 알 수 있습니다.

-rw-rw-r--

- r w - r w - r - -
파일 종류
-는 일반 정규 파일
소유자의 read 권한 소유자의 write권한 소유자의 실행권한 X 소유자 그룹의 read권한 소유자 그룹의 write권한 소유자 그룹의 실행권한X 다른 사용자의 read권한 다른 사용자의 write권한X 다른 사용자의 실행권한X

 

맨앞의 파일의 종류를 나타내는 '-'를 제외하고 권한의 '-'는 그 파일에 대한 해당 권한이 없다는 것을 의미합니다. 권한은 아까 세종류가 있다고 했는데 각각 이렇습니다.

r read로 파일을 읽을 수 있는 권한입니다.
w write로 파일을 수정할 수 있는 권한입니다.
x execute로 파일을 실행할 수 있는 권한입니다. 파일에는 단순 기록하는 파일외에도 실행파일이 있기 때문에 이러한 권한이 필요합니다.

 

chmod 명령어

chmod는 파일의 권한을 바꿀 수 있는 명령어입니다. 명령어 형식은 이렇습니다.

chmod [파일에 추가거나 뺄 권한] [파일 이름]

만일 다른 유저들의 쓰기 권한을 추가하고 싶다면 아래의 명령으로 권한을 추가할 수 있습니다. 

 

u+w의 앞 u는 사용자를 의미합니다. 여기서 +는 더한다는것을 알 수 있겠죠? 반대로 뺄때는 -를 씁니다. 마지막 글자 w는 어떤 권한인지를 말합니다. 읽기 권한을 추가하려면 r를 사용하면 되겠네요. 맨 처음 글자는 아래와 같습니다. 

u user의 앞글자로 소유자를 의미합니다.
g group의 앞글자로 소유자의 그룹을 의미합니다.
o other의 앞글자로 다른 유저들을 의미합니다.

 

여러 권한을 설정할때는 쉼표로 나열해주면 됩니다. 아래는 그룹과 다른 유저들에게 r,w를 더해주는 명령어의 예입니다.

chmod g+rw,o+rw file.txt

그리고 숫자로 권한을 일괄적으로 바꾸는 방법도 있습니다.

파일의 권한을 설명할때 rwxrwxrwx로 해도되지만 보통은 정수를 사용하여 권한을 이야기합니다. 앞에 rw-rw-r--는 숫자 664로 대응이 되는데, 왜 이렇게 되는걸까요? 세개를 묶어서 세비트로 표현하기 때문입니다.

r w - r w - r - -
1 1 0 1 1 0 1 0 0
4 2 0 4 2 0 4 0 0

두번째 줄은 이진수, 세번째 줄은 10진수로 표현했습니다. 그래서 rw-는 결국 이진수 110으로 되어 6이 됩니다. 그렇다면 rwx는 111이 되어서 7이겠네요.

아래는 chmod로 유저는 모든 권한을, 그룹 사용자는 읽기, 쓰기 권한을, 그리고 다른 사용자는 읽기 권한만 추가하는 명령어의 예입니다.

- chmod 764 a.out

그리고 setuid와 setgid, sticky의 비트를 사용하여 네자리로 표현할 수도 있습니다. 각각 setuid는 4, setgid는 2, sticky는 1로 대응이됩니다.

- chmod 4764 a.out

위 명령은 setuid를 설정하는 명령입니다. setuid와 setgid, sticky는 아래에 설명하도록 하겠습니다.

 

setuid

setuid를 설명하기에 앞서 리눅스에서는 유저를 id로 구별합니다. 두 종류가 있는데 아래와 같습니다.

UID (REAL UID) : 이는 실제 사용자 본인의 아이디를 표현한다고 해서 real id라고도 하면 uid라고 합니다. 

EUID (EFFECTIVE UID) : 유효 사용자 아이디라고해서 프로그램이 실행될때 갖는 아이디를 말합니다. 즉, 실행시에 이 프로그램을 만든 사용자의 ID로 실행이 된다는 겁니다. 

setuid의 예1)

그렇다면 setuid의 예를 하나 들어보도록 할까요? 아래와 같이 root가 파일 하나를 만들고 본인만 읽을 수 있게 만들어 놓았습니다.

 

그리고 root 사용자는 아래와 같은 코드로 이 파일을 읽는 코드를 짜서 실행파일을 만들었습니다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(){
        int fd=open("root_read_only.txt",O_RDONLY);
        int n;
        char buf[128];
        printf("uid:%d\n",getuid());    //실제 사용자 ID
        printf("euid:%d\n",geteuid());  //이 프로그램을 실행할때만 갖는 ID
        n=read(fd,buf,sizeof(buf));
        if(n<0){
                printf("파일을읽을 수 없습니다. erorr code:%d\n",n);
                exit(1);
        }
        printf("file content:%s\n",buf);
}

 

root 사용자가 이 실행파일의 주인이고 실행파일의 권한은 모든 사용자가 실행할 수 있게 만들었습니다. 

 

이제 다른 사용자인 ubuntu가 이 read.out이라는 실행파일을 실행하게 되면 아래의 내용과 같이 나옵니다. 지금 현재 uid는 1000이고 euid도 1000이라서 root_read_only.txt를 볼 수 없습니다. 왜냐면 root_read_only.txt는 오직 root만 읽을 수 있게 설정했거든요. 

 

이때 이 실행파일이 실행될때만 루트의 권한을 갖도록 하여 파일을 읽을 수 있게 하는 방법은 euid를 root로 설정하는 setuid를 주면 될텐데요. root 계정으로 그 권한을 줘보도록 하겠습니다. chmod u+s read.out으로 setuid를 줄 수 있고, 이때 rwsrwxrwx로 바뀌게 된것을 알 수 있습니다. 소유자의 실행권한인 s로 바뀐 것은 setuid가 설정되어 있는 파일이며 실행시에 파일의 소유자의 권한으로 실행된다는 것을 의미합니다.

 

이제 ubuntu라는 유저는 이 root_read_only.txt라는 파일을 읽을 수 있을까요? 그럴 수 있는지 read.out을 실행해보도록 합시다. 

 

euid가 0으로 바뀐것을 확인할 수 있으면서 file의 내용을 읽어볼 수 있습니다.

setuid의 예2)  passwd

setuid를 설명하기 위해서 임의로 제가 만든 하나의 예입니다. 리눅스에서 가장 대표적으로 setuid를 사용하는 실행파일은 /bin/passwd파일입니다. 이 파일은 사용자의 비밀번호를 바꾸는데, 필요한 명령어로 /etc/passwd를 수정해야합니다. 하지만 /etc/passwd는 절대 root만 수정할 수 있으므로 다른 사용자는 변경할 수가 없죠. 그렇지만 다른 사용자들이 비밀번호를 바꿀때 /etc/passwd를 수정해야하므로 수정 프로그램이 필요하고 그 파일이 바로 /bin/passwd파일입니다. /bin/passwd는 실행시에 루트권한으로 실행이되어 /etc/passwd파일을 수정할 수 있게 됩니다.

 

 

setgid

setgid 역시 비슷합니다. 실행시에 그룹의 권한을 갖는 다는 것인데요. 앞서 설명한 setuid와 개념은 비슷하며 그룹 권한에 s로 표시가 됩니다. 

 

sticky비트

sticky비트는 다른 사용자가 자유롭게 디렉토리를 사용할 수 있도록 만드는 권한입니다. 원래 디렉토리도 디렉토리를 만든 사람만이 읽기, 쓰기가 가능합니다. 하지만 sticky를 쓰면 모든 사용자가 자유롭게 읽기, 쓰기가 가능합니다. 마치 공유폴더와 아주 비슷한 개념입니다. 이 권한은 디렉토리에만 해당되는 권한입니다. 예를 들어설명해볼까요?

아래와 같이 root는 공유폴더를 만들 목적으로 디렉토리를 하나 만들었습니다. 하지만 권한을 주지는 않았죠. 

 

그래서 ubuntu라는 유저는 이 디렉토리에 파일을 기록하려고 했으나 아래와 같이 권한이 없다는 메시지를 받게 됩니다.

 

아래와 같이 root는 다른 유저에 대해서 sticky비트를 설정합니다.

 

이렇게 되면 아래와 같이 ubuntu는 /shared 디렉토리를 자유롭게 이용할 수 있습니다.

 

여기까지 리눅스 파일의 실행권한과 setuid, setgid, sticky에 대한 개념을 알아보았습니다. 최대한 쉽게 예를 들어 설명하려고 했는데, 이해가 가셨는지 모르겠네요. 

반응형
블로그 이미지

REAKWON

와나진짜

,

리눅스 프로그래밍과 관현한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

grep

grep 명령어는 각 파일에서 패턴(Pattern)을 검색할때 사용하는 명령어로 리눅스에서 매우 자주 사용하는 명령어입니다. 주로 사용하는 방법은 아래와 같이 나뉘어집니다.

grep [OPTION...] PATTERNS [FILE...]
grep [OPTION...] -e PATTERNS ... [FILE...]
grep [OPTION...] -f PATTERN_FILE ... [FILE...]

 

1. 첫번째 방식 : 옵션과 함께 PATTERNS를 주어진 파일들에서 찾습니다.

2. 두번째 방식 : 옵션과 함께 정규표현식으로 PATTERNS을 정의하고 주어진 파일들에서 찾습니다.

3. 세번째 방식 : 옵션과 함께 파일에 정의된 PATTERN을 주어진 파일들에서 찾습니다.

 

FILE을 지정하는 방법에 따른 검색

FILE은 찾을 대상의 파일을 의미합니다. 찾을 파일들을 명시적으로 지정하게 되면 그 파일만들 탐색합니다. 만약 -(대쉬, dash)이면 stdin에서 찾습니다. 즉, 키보드로 입력이 되면 패턴을 찾는다는 방식입니다. 만약 파일이 주어지지 않았다면 -r 옵션에 따라 다릅니다. -r 옵션이 주어지면 현재 디렉토리에 있는 파일들을 대상으로 하위 디렉토리까지 패턴을 찾습니다. 그렇지 않는다면 표준 입력(stdin)으로 입력받습니다.

이러한 예를 들어보도록 하겠습니다.

ex ) FILE을 명시 

현재 디렉토리 /etc이며 여기서 shadow, passwd 파일에서 root 패턴을 포함하는 라인을 출력합니다.

# pwd
/etc
# grep "root" shadow passwd
shadow:root:$6$Mg4AK6u6xv/EZk93$vwpv6ALfAv1jH0.3Ub0hIjVgDXJSa9Mwk625jfzNzYzR48aypIFflbhUL4VAxECs3PiFr6NTA0ghnAfpAj2Bz1:18693:0:99999:7:::
passwd:root:x:0:0:root:/root:/bin/bash
passwd:nm-openvpn:x:118:124:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin

 

ex) FILE을 "-"로 지정

이렇게 파일대신 대시를 사용하게 되면 표준입력으로 입력된 것에서 패턴을 검색합니다. Apple과 일치하게 되면 색이 달라지는 것을 볼 수 있습니다.

# grep "Apple" -
Apple
Apple
goo
foo
boo
Apple
Apple
apple
^C

 

ex) FILE 지정 생략

FILE 생략시 역시 키보드로 입력받는 데이터 중에서 검색을 합니다. 파일을 '-' 준것과 동일함을 알 수가 있네요.

# grep "Apple"
Apple
Apple
Banana
App
Application
Apple
Apple
^C

 

ex) -r 옵션과 FILE 지정 생략

현재 디렉토리부터 하위 디렉토리에 있는 파일까지 전부 검색합니다. 여기서는 "root"라는 패턴이 포함되면 그 라인을 전부 출력하게 됩니다.

# pwd
/etc
# grep "root" -r
ca-certificates.conf:mozilla/Comodo_AAA_Services_root.crt
shadow:root:$6$Mg4AK6u6xv/EZk93$vwpv6ALfAv1jH0.3Ub0hIjVgDXJSa9Mwk625jfzNzYzR48aypIFflbhUL4VAxECs3PiFr6NTA0ghnAfpAj2Bz1:18693:0:99999:7:::
passwd_bak:root:x:0:0:root:/root:/bin/bash
passwd_bak:nm-openvpn:x:118:124:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
xattr.conf:xfsroot.*                    skip            # xfs specific; obsolete
nanorc:## In root's .nanorc you might want to use:
...//생략//...

 

지원하는 옵션들의 예제

grep은 많이 쓰이는 명령어인 만큼, 매우 많은 옵션을 지원하고 있습니다. 전부 외우고 숙지하는 것은 무리이니까 자주 사용하고 유용한 옵션들만 알아보도록 하겠습니다.

옵션 설명
-r recursive의 약자로 현재 디렉토리부터 하위 디렉토리까지 전부 탐색합니다.
-i ignore case의 약자로 패턴의 대,소문자를 구분하지 않고 검색합니다.
-c 파일에서 지정한 패턴과 얼마나 일치하는지에 대한 수(count)가 출력됩니다.
-n 파일의 어느 라인에서 패턴이 일치하는지 라인 넘버(number)도 같이 출력해줍니다.
-l 패턴과 일치하는 파일의 이름만을 출력합니다.
-f 파일에 있는 패턴으로 검색합니다.
-e 정규 표현식으로 패턴을 검색합니다.
-v 패턴과 일치하는 라인을 삭제합니다. 불필요한 라인을 삭제할때 유용합니다.

 

 

grep 사용 예제

1) grep -irn "<stdio.h>" : stdio.h 헤더 파일을 사용하는 파일을 현재 디렉토리부터 검색하여 line도 같이 출력

# grep -irn "<stdio.h>"
test.c:1:#include <stdio.h>

 

2) 명령어의 결과를 grep으로 검색

grep은 기본적으로 파일을 지정하지 않는다면 stdin으로 입력을 받는다고 위에서 설명을 했었습니다. 리눅스에서 파이프는 어떤 명령어의 출력(정확히 이야기하면 표준 출력, stdout)을 다음 명령어의 stdin으로 넘겨줍니다. 그래서 이런 명령어 형식을 자주쓰게 됩니다.

명령어| grep "찾을 패턴"

 

예를 들면 현재 실행되는 프로세스 중에 ssh와 관련된 프로세스를 알아보고 싶다면 아래와 같은 조합으로 사용할 수 있습니다. 

# ps -ef | grep "ssh"
ubuntu      2032    1955  0  8월24 ?      00:00:03 /usr/bin/ssh-agent /usr/bin/im-launch env GNOME_SHELL_SESSION_MODE=ubuntu /usr/bin/gnome-session --systemd --session=ubuntu
root      108345       1  0  8월25 ?      00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root      275674  108345  0 20:45 ?        00:00:00 sshd: ubuntu [priv]

 

ps 명령어는 결과를 stdout으로 출력하기 때문에 이 출력 결과를 grep으로 넘겨줍니다. 전달받은 표준 출력은 grep의 표준 입력으로 전해져 "ssh"의 패턴과 일치하면 출력하게 됩니다. 이해가 되시나요?

만약 pipe와 관련한 설명이 부족하다면 아래의 링크를 통해서 더 자세히 알아보세요.

https://reakwon.tistory.com/115

 

[리눅스] 재지정, 리다이렉션(redirection: >, <)과 파이프(|) 개념과 쉬운 설명

재지정(Redirection) 리눅스에서 프로그램은 보통 세 개의 파일 서술사를 열게 됩니다. 바로 표준 입력(standard input, STDIN), 표준 출력(standard output, STDOUT), 그리고 표준 에러(standard error, STDERR)..

reakwon.tistory.com

 

3) # grep -v "root" /etc/passwd : 패턴 root이 없는 라인만 검색

# grep -v "root" /etc/passwd
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

 

여기까지 grep에 대한 기본적인 원리와 자주 사용하는 사용법을 알아보았습니다. 정규표현식을 아시는 분은 더욱 유용하게 사용할 수 있겠지만 저는 거기까지 필요는 없이 잘 사용하고 있습니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

정적(static)

정적 멤버(혹은 변수)와 메소드라는 것은 무엇일까요? 우선 아래와 같은 상황이 있다고 가정해봅시다. Counter라는 클래스가 있으며 Counter는 멤버로 cnt를 갖고 있습니다. 그리고 count라는 메소드가 있는데, 이 count 메소드는 단순히 cnt를 하나 증가하는 역할을 합니다.

그리고 메인에서는 Counter의 두 객체 counter1, counter2를 생성하고 둘은 counter() 메소드를 통해서 cnt를 증가시키고 있습니다. 여기서 저의 의도는 두 객체가 cnt를 같이 증가시키고 싶습니다.

class Counter{
	public int cnt;
	public void count() {
		cnt++;
	}
	
}

public class TestMain {
	
    public static void main(String[] args){
    	Counter counter1=new Counter();
    	Counter counter2=new Counter();
    	counter1.count();
    	counter2.count();
    	System.out.println("counter1:"+counter1.cnt+", counter2:"+counter2.cnt);
    	
    }
}

 

만약 프로그램이 저의 의도대로 동작한다면 counter2까지 왔을때 cnt는 2가 되어야합니다. (물론 그렇게 동작하지 않습니다.) 결과를 보면 아래와 같네요.

counter1:1, counter2:1

 

정적 변수(Static variable)

여기서 저의 희망은 cnt라는 맴버를 공통으로 사용하는 것입니다. 이때 static하나만 써주면 저의 목적이 달성됩니다. Counter 클래스에서 cnt를 선언할때 static만 붙여봅시다.

class Counter{
	public static int cnt;
	public void count() {
		cnt++;
	}
}

public class TestMain {
	
    public static void main(String[] args){
    	Counter counter1=new Counter();
    	Counter counter2=new Counter();
    	counter1.count();
    	counter2.count();
    	System.out.println("counter1:"+counter1.cnt+", counter2:"+counter2.cnt);
    	
    }
}

그리고 결과를 봐야겠죠.

counter1:2, counter2:2

 

제가 말한대로 동작하는 것을 알 수 있습니다.

어떻게 이런것이 가능할까요? static 변수나 메소드는 static 메모리 구역에 따로 고정적(정적)으로 할당되어 관리됩니다. 그래서 이 static 변수와 메소드는 모든 Counter 클래스가 공통적으로 사용할 수 있습니다. 이러한 특징 때문에 클래스 변수, 메소드라고 합니다.

 

static에 대해서 가볍게 아시려면 "공용 변수, 메소드" 라고 쉽게 기억하시면 됩니다.

 

그렇지만 static에 대해서 좀 더 자세히 알려면 여러분은 우선 프로그램이 실행될때의 메모리 구조에 대해서 아셔야합니다. 우리가 항상쓰던 new로 객체를 생성하는 것은 heap 영역에 저장되어 프로그램이 실행하는 중간에 메모리를 할당합니다. 그래서 메소드가 끝나서 쓸일이 없어질때 자바의 Garbage Collector에 의해서 메모리에서 수거가 됩니다.

하지만 static은 프로그램이 실행 전 먼저 메모리에 잡히게 됩니다. 이때 실행 전이라고 하는 것은 프로그램이 메모리에 적재되고 명령어를 수행하기 전을 말합니다. 그리고 프로그램이 종료될때 해제가 되지요. 즉, heap의 영역보다 나중에 정리된다는 뜻입니다.

메모리 구조

 

가장 먼저 메모리에 적재되어 정적(static)으로 존재하기 때문에 static 멤버나 메소드는 클래스의 객체 생성없이 클래스의 이름만가지고도 사용할 수 있습니다.

public static void main(String[] args){
	Counter.cnt++;
    Counter.cnt++;
    System.out.println("cnt:"+Counter.cnt);
}
cnt:2

 

정적 메소드(static method)

이제 정적 변수는 알았는데, 정적 메소드는 그렇다면 무엇일까요? 역시 변수와 개념은 다르지 않습니다. 메모리에 메소드하나를 고정적으로 두어서 사용이 가능합니다. Counter 클래스를 변형시켜서 보도록 합시다. 

class Counter{
	public static int count(int cnt) {
		return cnt+1;
	}
}

public class TestMain {
	
    public static void main(String[] args){
    	Counter counter=new Counter();
    	System.out.println("count 1:"+Counter.count(1));
    	System.out.println("count 2:"+Counter.count(2));
    	System.out.println("counter's count 5:"+counter.count(5));
    }
}

 

그러면 메모리에는 이런식으로 잡히게 됩니다. 마치 counter라는 메소드를 공용으로 사용하듯 말이죠.

 

실행을 시켜보면 아래와 같은 결과를 갖게 됩니다.

count 1:2
count 2:3
counter's count 5:6

 

이제 메모리에 정적변수나 메소드가 다른 동적으로 메모리를 할당(new 키워드로 할당하거나 일반 메소드)하는 시점보다 먼저 메모리에 잡히게 되었다는 것을 알았다면 다음과 같은 규칙을 이해하게 될 것입니다.

 - 정적 메소드에서는 정적 메소드나 정적 변수만 사용할 수 있다. 멤버 변수는 사용할 수 없다.

이러한 이유는 정적 메소드에서는 자신보다 나중에 메모리가 할당되는 멤버 변수나 메소드는 언제 할당되는지 알수가 없습니다. 만약 아래의 코드처럼 count가 먼저 호출되는 상황에서 cnt가 메모리에 할당되지 않았다면요. 그래서 이처럼 빨간줄이 쳐지면서 static 만이 올수가 있다고 메시지를 띄워줍니다.

 

main 메소드에서 static 메소드만 호출 가능했던 이유

main 메소드 역시 static 메소드이기 때문에 여러분들이 클래스를 배우기 전 메소드를 배울때 묻지도 따지지도 않고 우선 static을 붙여서 메소드를 구현해보셨을 것입니다. main 메소드는 프로그램 실행 전 먼저 메모리에 할당되어야하기 때문이고, 메인이 static이기 때문에 호출되는 메소드 역시 static이어야하기 때문입니다.

 

이상으로 자바에서 static 개념에 대한 설명이었습니다. 무엇보다 메모리가 할당되는 시점만 이해한다면 왜 이런 결과가 나오는지 이해하기가 쉬울 것입니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

SwipeRefreshLayout

요즘 어플리케이션들을 보면 아래로 당겨서 화면을 새로 로딩하는 기능이 많죠. 이런 기능을 담당하는 것이 SwipeRefreshLayout입니다. 그래서 아래로 당기면 아래처럼 로딩하는 화면(빙글 빙글 돌아가는 로딩화면)이 나온 이후에 로딩이 완료되면 어플리케이션의 정보를 갱신해줍니다.

 

 

SwipreRefreshLayout을 사용하기 위해서는 build.gradle 파일에 아래처럼 종속 항목을 명시해줍니다. 

build.gradle

 

build.gradle

dependencies {
	//...
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
    //...
}

 

현재 안정화버전은 1.1.0버전이기 때문에 1.1.0버전을 사용하도록 하겠습니다. 추후에 안정화버전이 변경될 수 있으니 안드로이드 개발 문서를 참고해주시기 바랍니다.

https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout?hl=ko#1.1.0 

 

Swiperefreshlayout  |  Android 개발자  |  Android Developers

Swiperefreshlayout 스와이프하여 새로고침 UI 패턴을 구현합니다. 최근 업데이트 현재 안정화 버전 다음 버전 후보 베타 버전 알파 버전 2020년 7월 22일 1.1.0 - - 1.2.0-alpha01 종속 항목 선언 SwipeRefreshLayout

developer.android.com

 

이제 sync를 하고 나면 벌써 사용할 준비는 끝이 났습니다. 이제 메인화면을 구성할 activity_main.xml을 만들어줍시다.

activity_main.xml

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id='@+id/refresh_layout'>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:id="@+id/text_view"
            android:text="아래로 당겨보세요."/>
    </RelativeLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

 

SwipeRefreshLayout에 새로고침이 될 레이아웃을 추가하였습니다. 저는 간단히 RelativeLayout안의 TextView의 텍스트를 새로운 데이터로 고쳐보도록 하겠습니다. 이것을 프로그램한것이 MainActivity에 존재합니다.

MainActivity.java 

import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private SwipeRefreshLayout swipeRefreshLayout;
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        swipeRefreshLayout=(SwipeRefreshLayout)findViewById(R.id.refresh_layout);
        textView=(TextView)findViewById(R.id.text_view);

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                textView.setText("새로 고침 완료");
                swipeRefreshLayout.setRefreshing(false);
            }
        });
    }
}

 

누가봐도 매우 간단한 코드네요. SwipeRefreshLayout에 OnRefreshListener를 추가해주면 새로고침이 시작되었을때 onRefresh 메소드가 호출이 됩니다. 이때 다시 로드하는 코드를 추가하여 주고 setRefreshing에 false를 전달하여 로딩중인 화면을 없애주면 됩니다.

 

실행하면 화면이 로딩된 이후 TextView에서 로딩이 완료되었다는 메시지와 함께 Refreshing을 중지합니다.

 

만약 쓰레드를 이용하면 onRefresh에 쓰레드를 실행하여 Handler를 통해 완료 메시지를 받으면 여기서 setRefreshing을 false로 전달해주면 됩니다. 

반응형
블로그 이미지

REAKWON

와나진짜

,

람다 표현식(Lambda Expression)

람다 표현식은 JAVA SE 8부터 추가된 기능입니다. 람다 표현식은 함수를 하나의 식으로 표현한 것인데요. 메소드의 이름을 갖다버린 익명 함수(Anonymous Function)로 볼 수 있습니다.

아주 간단히 이야기하자면 귀찮은 익명 메소드(자바에서는 익명 클래스, Anonymous Class)를 아주 간단하게 줄인 표현식이라고 생각하면 됩니다.

 

기존 방식

예를 들어 안드로이드에서 클릭 이벤트 발생시에 아래와 같이 View.OnClickListener를 정의해주고 onClick 메소드를 채워줘야 클릭 이벤트를 받을 수 있습니다.

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG,"클릭 발생");
            }
        });

 

구현해야할 메소드는 onClick 메소드 하나 뿐인것을 알 수 있습니다. onClick 메소드는 너무나 간단하게 로그를 찍는 코드밖에 없습니다. 하지만 꽤나 복잡한 과정을 거치게 되죠. 분명 로그만 찍는데 부가적으로 기록해야하는 코드가 길다는 것이죠. 

혹은 우리가 자주 사용하는 Thread를 Runnable을 통해서 객체 생성과 동시에 동작시키려면 이렇게 코드를 짜야합니다.

new Thread(new Runnable() {
    		@Override
    		public void run() {
    			System.out.println("Thread run");
    		}
    	}).start();

 

 

람다를 사용한 방식

만약 위의 코드들에 람다표현식을 적용한다면 이렇게 바뀝니다.

button.setOnClickListener(v->Log.e(TAG,"클릭 발생"));

 

그리고 Thread를 동작시키는 코드는 아래와 같게됩니다.

new Thread(()->System.out.println("Thread run")).start();

 

몇줄이 단지 한줄로 바뀌게 되지요. 이렇게 람다를 적용하면 이렇게 간단한 코드로 바뀌게 됩니다. 즉, 가독성이 정말 높아지게 됩니다. 여기서 우리는 단지 하나의 익명 클래스를 구현했지만, 그것이 여러개가 된다면 가독성이 매우 떨어지게 됩니다.

 

반면 익명 함수이기 때문에 재사용이 불가능하고, 디버깅이 어려운 단점이 있습니다. 

 

람다 표현식 문법

이제는 람다를 사용하는 방법에는 어떤 방법이 있는지 알아보도록 합시다. 이때 람다를 사용할 수 있는 경우는 Overriding해야할 메소드가 하나여야합니다. 두개 이상의 메소드를 구현해야할때에는 사용할 수가 없습니다.

1. 구현부가 한줄인 경우

() -> System.out.println(x)

 

2. 인자가 있는 경우

() -> {System.out.println("lambda")};

 

3. 인자가 있는 경우

인자가 2개 이상인 경우에는 괄호로 묶어야합니다. 그리고 타입을 명시적으로 지정해줄 수도 있습니다.

x -> {System.out.println(x)};
(x) -> {System.out.println(x)};
(x,y) -> {System.out.println(x+","+y)};
(int x, int y) -> {System.out.println(x+","+y)};

 

4. 반환값이 있을 경우

(int x) -> { return x*x };

 

 

함수형 프로그래밍

람다를 사용하는 가장 주된 이유는 함수형 프로그래밍을 해줄 수 있게 만들어주기 때문입니다. 여기서 함수형 프로그래밍이라는 것은 순수 함수, 메소드로 문제를 해결하는 프로그래밍 페러다임입니다. 자바 8 이전에는 어떻게(how) 문제를 해결하는 것에 초점을 두지만, 8 이후에는 무엇(what)을 할 것인가에 대해서도 지원하게 됩니다.

예를 들어 for을 우리가 구현하지 않고 미리 정의된 forEach를 사용하는 방식입니다. 이렇게 되면 코드를 짧게 유지하여 유지 보수하기가 매우 쉬워집니다. 

 

함수형 프로그래밍을 도와주는 몇몇 기법에는 지금 소개한 람다 표현식과 StreamAPI 등이 있습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

특정 시간에 Notification 발생 시키기

 

특정 시간이 되면 알림이 발생되는 코드를 android에서 구현해보도록 하겠습니다. 특정 시간을 설정해서 그 시간이 되면 알려주는 무엇인가가 있어야하는데 이를 가능하게 만들어주는 것이 AlarmManager입니다. 코드를 통해서 알아보도록 합시다.

 

먼저 레이아웃은 간단하게 아래처럼 구성되어있습니다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TimePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:timePickerMode="spinner"
        android:id="@+id/time_picker" />


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="저장"
        android:id="@+id/save"/>

</LinearLayout>

 

이제 time_picker에서 시간을 설정하고 save 버튼을 누르게 되면 그 시간에 Notification이 발생하게 되는 코드를 설명합니다.

 

MainActivity

MainActivity의 onCreate 코드는 아래와 같습니다. 

package com.reak.alarmtest;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.TimePicker;
import android.widget.Toast;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private Button save;
    private TimePicker timePicker;

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        timePicker=(TimePicker)findViewById(R.id.time_picker);
        save=(Button)findViewById(R.id.save);

        save.setOnClickListener(v->{

            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis());
            int hour=timePicker.getHour();
            int minute=timePicker.getMinute();
            calendar.set(Calendar.HOUR_OF_DAY,hour);
            calendar.set(Calendar.MINUTE,minute);

            if (calendar.before(Calendar.getInstance())) {
                calendar.add(Calendar.DATE, 1);
            }

            AlarmManager alarmManager=(AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
            if (alarmManager != null) {
                Intent intent = new Intent(this, AlarmReceiver.class);
                PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                        AlarmManager.INTERVAL_DAY, alarmIntent);

                Toast.makeText(MainActivity.this,"알람이 저장되었습니다.",Toast.LENGTH_LONG).show();
            }
        });
    }
}

 

여기서 Calendar 객체를 현재시간으로 미리 설정해두고, set 메소드로 timePicker에서 설정된 시간과 분으로 설정시키는 것입니다. 이때 시간과 분을 TimePicker에서 얻어오려면 @RequiresApi(api = Build.VERSION_CODES.M) 를 메소드위에 추가시켜야합니다.

이제 AlarmManager를 가져옵니다. Intent는 수신자 클래스를 전달하게 됩니다. 여기서는 AlarmReceiver라는 수신자이며 밑에서 이 Receiver를 구현하게 될겁니다. 그리고 PendingIntent를 얻어와서 setRepeating 메소드로 정확한 시간에 알람을 설정시켜줍니다.

 

여기서 setRepeating메소드는 알람을 반복시키는 메소드입니다. 여기서는 AlarmManager.INTERVAL_DAY를 사용하였고, 이것은 매일 알람이 울릴것을 명시해준것입니다. 

 

굳이 정확한 시간에 알람을 울리지 않을 경우에는 setInexactRepeating 메소드를 사용할 수도 있습니다. 실제로 안드로이드 개발문서에는 이 메소드를 사용하라고 권고하고 있네요.

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.reak.alarmtest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AlarmTest">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".AlarmReceiver"/>

    </application>

</manifest>

 

수신자를 사용하려면 위에서처럼 메니페스트에 수신자를 명시해주어야합니다. 

 

AlarmReceiver

package com.reak.alarmtest;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import androidx.core.app.NotificationCompat;

import java.util.Calendar;
import java.util.StringTokenizer;

import static android.app.Notification.EXTRA_NOTIFICATION_ID;

public class AlarmReceiver extends BroadcastReceiver {

    private Context context;
    private String channelId="alarm_channel";
    @Override
    public void onReceive(Context context, Intent intent) {
        this.context = context;


        Intent busRouteIntent = new Intent(context, MainActivity.class);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
        stackBuilder.addNextIntentWithParentStack(busRouteIntent);
        PendingIntent busRoutePendingIntent =
                stackBuilder.getPendingIntent(1, PendingIntent.FLAG_UPDATE_CURRENT);

        final NotificationCompat.Builder notificationBuilder=new NotificationCompat.Builder(context,channelId)
                .setSmallIcon(R.mipmap.ic_launcher).setDefaults(Notification.DEFAULT_ALL)
                .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                .setAutoCancel(true)
                .setContentTitle("알람")
                .setContentText("울림")
                .setContentIntent(busRoutePendingIntent);


        final NotificationManager notificationManager=(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);

        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel channel=new NotificationChannel(channelId,"Channel human readable title",NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        int id=(int)System.currentTimeMillis();

        notificationManager.notify(id,notificationBuilder.build());

    }
}

 

이제 알람이 발생되면 AlarmReceiver의 onReceive 메소드가 호출되게 됩니다. 이때 아까 우리가 MainActivity에서 넘긴 PendingIntent를 requestCode를 통해서 얻어올 수가 있습니다. 이 requestCode는 알람을 설정했던 것과 같은 코드를 사용해야합니다. 여기서는 1입니다.

 

오레오(Oreo)버전 이상부터는 NotificationChannel을 명시해주어야합니다. 그 코드도 적용되어 있습니다.

 

대충 완성하게 되면 아래와 같은 모습입니다. 앱 디자인은 개나 줘버린 모습이지만 구현이 제대로 되었나 확인만 하는 용도입니다.

alarm test 앱화면

 

그리고 원하는 시간에 알람을 저장시켜줍니다. 그래서 9시 15분에 알람을 설정합니다. 그리고 그 시간에 울리는 지 잠깐 기다려주겠습니다. 이때 9시 14분이었고, 1분만 기다리면 됩니다.

alarm 저장

 

다음은 알람이 발생되어 Notification이 발생된 화면입니다.

 

분명 9시 15분이었는데, 9시 16분에 알람이 발생되었군요. 정확한 시간에 알람이 딱 울리지는 않는 것 같습니다. 제 생각에는 setRepeating이 setInexactRepeating메소드보다 더 정확한 시간에 발생시켜주는 것으로 확인이 됩니다.

알람 취소

그리고 알람을 취소하려면 아래와 같이 코드를 추가하면 되는데요.  위의 코드는 없지만 만약 알람 취소버튼을 추가하고 그 버튼이 눌리면 alarmManager의 cancel 메소드를 이용하면 됩니다.

PendingIntent cancelIntent=PendingIntent.getBroadcast(MainActivity.this, 1, intent,PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(cancelIntent);

 

 

여기까지 Alarm과 Notification, Receiver를 조합하여 앱을 구현해보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

프로세스와 관현한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

ps(process status)

여러분들이 컴퓨터를 사용할때 가장 많이 사용하는 명령이 있습니다. 그 중 한가지 경우가 프로세스에 대한 정보를 알고 싶을 경우인데요. 예를 들어 어떤 프로세스가 수행중인데, 이 프로세스가 CPU를 굉장히 많이 소모시킨다고 합니다. kill 명령으로 그 프로세스를 종료시키고 싶지만 pid를 알아야하겠죠. 이때 pid등의 정보를 볼 수 있는 명령이 있습니다. 그 process의 상태를 알고 싶을때 매우 많이 사용하는 명령이 바로 ps입니다.

 

ps는 프로세스에 대한 많은 정보를 담고 있고 매우 많이 사용되는 명령으로 옵션이 매우 다양합니다. ps는 옵션에는 세가지 종류가 있습니다.

1. Unix Option : 앞에 '-' (dash)가 붙는 옵션 표기방법입니다. 

2. BSD Option : '-' 를 붙이지 않습니다.

3. GNU Option : 명령어 앞에 '--' (double dash)를 붙입니다.

물론 Unix, BSD, GNU 옵션들을 모두 우리가 알 수도 없고, 외울 수도 없지만 그래도 유용한 옵션정도는 알고 있어야겠죠? 

 

기본 ps 명령어 구성

ps 명령어를 치면 아래와 같이 나오게 됩니다.  별로 몇개 나오지 않는 다는 것을 알 수 있는데, 이것은 ps가 기본적으로 같은 EUID(Effective User ID)의 프로세스이며 같은 터미널의 프로세스만을 골라서 보여주기 때문입니다. 

보여주는 정보는 프로세스 ID(PID), 터미널(TTY), CPU 점유 시간(TIME), 그리고 프로세스가 수행된 명령어(CMD)입니다. 여기서 a.out은 제가 임의로 무한 루프를 돌린 프로세스를 실행시켜서 그렇고 시간은 TIME이 00:04:31인것을 볼 수 있네요.

 

ps -e, -A : 모든 프로세스를 보여줍니다. 

ps -a : 세션 리더와 터미널과 연관된 프로세스들을 제외한 모든 프로세스를 보여줍니다.

ps -d : 세션 리더를 제외한 모든 프로세스를 보여줍니다.

ps -f : full format으로 세션의 정보를 표시합니다. 

ps -ef : -e와 -f의 옵션 조합인데, 모든 프로세스를 full format으로 보여줍니다. 아래는 그 결과를 보여줍니다. UID, PID, PPID, C, STIME, TTY, TIME, CMD의 정보를 볼 수 있네요. TTY(연결 터미널)가 없으면 대부분 데몬 혹은 커널 프로세스입니다. 

ps -ef 결과1
ps -ef 결과 2

 

 

ps -u userlist : EUID 혹은 유저 이름으로 프로세스를 고릅니다. 이때 여러 uid를 줄수 있는데 ','(comma)로 구분하여 명시해줍니다. euid는 프로세스가 수행할때 갖는 유저 권한을 말합니다.

ps -U userlist : -u 옵션과는 동일하나 RUID가 갖는 프로세스만을 찾아냅니다. ruid는 real user id라는 것으로 실제 프로그램을 실행한 uid를 의미합니다. 이때도 쉼표로 여러 uid를 지정할 수 있습니다.

ps -p pidlist : 프로세스 id가 일치하는 프로세스를 출력합니다. 여러 pid들을 뽑아내고 싶다면 마찬가지로 ','(comma)로 pid를 구분하여 명시해줄 수 있습니다. 이 명령은 ps --pid pidlist와 같습니다.

ps --ppid pidlist : 부모 프로세스 id와 일치하는 프로세스를 출력합니다. 역시 여러 ppid를 ','(comma)로 구분가능합니다.

ps -t ttylist : tty와 일치하는 프로세스들을 출력해줍니다. 이 명령은 t 혹은 --tty 옵션과 같습니다. 

ps -o format : 사용자가 지정한 format대로 출력합니다. format에 대해서는 설명이 길지만 간략하게 원하는 column만 보여준다고 기억하시면 됩니다. 예를 들어 사용자가 임의로 pid, ppid, cmd, uid 등을 표시할 수 있습니다.

다음의 표는 format에 대해 정리한 표입니다.

CODE NORMAL HEADER
%C pcpu %CPU
%G group GROUP
%P ppid PPID
%U user USER
%a args COMMAND
%c comm COMMAND
%g rgroup RGROUP
%n nice NI
%p pid PID
%r pgid PGID
%t etime ELAPSED
%u ruser RUSER
%x time TIME
%y tty TTY
%z vsz VSZ

 

아래의 예는 uid가 0, 1000인 프로세스를 출력하는데, uid, ruid, euid, guid, pid, ppid, cmd를 출력해줍니다.

ps -u userlist -o format

 

ps aux : BSD 문법으로 실행중인 모든 프로세스를 나타냅니다. ps -aux와는 다른 옵션입니다. 

 

ps aux

 

위의 보이는 것 중에 프로세스의 상태를 나타내는 STAT 혹은 S는 아래의 코드로 구성됩니다. 다른건 필요없고 Z나 <defunct>로 표시된 프로세스는 좀비 프로세스로 자원을 점유하므로 시스템 관리가 필요합니다. 반드시 없애야합니다.

Code Desc
D Uninterruptible sleep
Idle Idle kernel thread
R Running or runnable
S Interruptible sleep
T stopped by job control signal
t stopped by debugger during tracing
W paging
X dead
Z defuct (zombie) process

 

BSD format에서는 추가 문자가 쓰일 수 있습니다.

Character Desc
< high-priority(not nice to other users)
N low-priority(nice to other users)
L has pages locked into memory
s is a session leader
l is multi-threaded
+ is in the foreground process group

 

ps | grep - 원하는 프로세스만 추출

대개 ps명령은 많은 결과를 출력하는데 이때 grep을 이용하여 원하는 프로세스를 찾을 수 있습니다. 예를 들면 sshd와 관련된 프로세스를 찾기를 원하면 이렇게 사용할 수 있습니다.

ps -ef | grep sshd

 

ps -ejH

프로세스를 트리 형태로 조금 보기 좋게 표시하고 싶다면 -ejH옵션을 사용하면 됩니다. 자식 트리면 CMD가 한칸 띄어져서 출력이 됩니다.

ps -ejH 1
ps -ejH 2

 

사실 트리모양으로 보기 좋게 출력하고 싶다면 아래의 pstree 명령이 더 보기 좋습니다.

pstree

여러분이 트리 형식으로 실행중인 프로세스를 보고 싶으시면 pstree 명령을 사용하시면 됩니다. 이 트리는 기본적으로 init 혹은 systemd 프로세스가 루트인데, 만약 pid를 명시한다면 그 pid가 루트가 됩니다. 

pstree

 

이상으로 ps명령어와 옵션에 대해서 포스팅을 했습니다. 사실 모든 옵션을 사용할 일은 없습니다. 저는 ps -ef | grep 만 사용하게 되던데, 저도 나중에 참고할 겸 포스팅을 했습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

 

1. 커서 이동

$ : 커서를 줄 맨끝으로 이동합니다.

0 : 숫자 0을 누르면 그 줄의 맨 앞으로 커서가 이동합니다. 빈칸을 포함하여 맨 앞으로 이동합니다.

^ : 0과 같이 줄 맨 앞에 커서를 위치하지만 글자앞으로 이동합니다.

ex)

                               reakwon.tistory.com
ㄴ 0을 누를때 커서             ㄴ^를 누를때 커서

 

+ : 커서를 다음줄의 첫 글자로 이동합니다.

- : 커서를 이전 줄의 첫 글자로 이동합니다.

gg : 커서를 문서의 맨 처음 줄로 위치합니다.

G : 커서를 문서의 맨 마지막 줄으로 위치합니다.

w : 다음 단어의 첫 글자로 이동시킵니다. 특수문자 발견시 멈춥니다.

W : 다음 단어의 첫 글자로 이동시킵니다. 단어를 공백으로 구분합니다. 따라서 공백 이후를 다음 단어로 취급합니다.

b : 단어의  첫글자로 이동시킵니다. 특수문자 발견시 멈춥니다.

B : 단어의 첫글자로 이동시킵니다. 단어의 구분을 공백을 기준으로 합니다.

% : 짝이 되는 괄호의 위치까지 이동합니다. 만약 여는 괄호 '('에서 %을 누르면 짝맞는 ')'까지 커서가 이동합니다. 만약 선택이 된 상태에서 %를 누르면 그 괄호내용 모두를 선택할 수 있습니다. 이 기능을 폴딩할때 이용이 될 겁니다.

 

2. 입력

명령에서 대문자 명령을 소문자 명령의 반대의 기능을 하게 됩니다.

a : 현재 커서의 다음에 글자를 입력할 수 있습니다.

A : 현재 줄의 맨 끝에 글자를 입력할 수 있습니다.

i : 현재 커서에서 글자를 입력할 수 있습니다.

I : 현재 줄의 맨 앞에 글자를 입력할 수 있습니다.

o : 커서의 아랫줄에 입력할 수 있습니다.

O : 커서의 윗줄에 입력할 수 있습니다.

 

3. 화면 이동

Ctrl + f : 한 화면 다음(아래)으로 이동합니다. 보다 빠른 이동을 할때 좋습니다.

Ctrl + b : 한 화면 이전(위)으로 이동합니다.

 

4. 선택

v : 기본적으로 v를 누르고 방향키로 텍스트를 선택합니다.

 

 

 

 

4. 삭제, 되돌리기

d : 선택 영역을 삭제합니다. 이떄 선택은 위에 등장한 v로 선택합니다.

dw : 현재 커서부터 다음 단어(공백 포함, 특수문자 제외)까지 모두 지웁니다. 이것을 응용하면 bdw가 왜 한 단어를 삭제하는지 알 수 있습니다.

bdw : 현재 단어 삭제 (b - 단어 앞으로 이동, dw : 다음 단어까지 삭제)

dd : 한줄 전체를 삭제합니다.

ndd : n개의 줄을 한번에 삭제시킵니다.

u : 만약 잘못 삭제시켰다면 u를 눌러서 되돌릴 수 있습니다. 삭제 뿐만 아니라 모든 명령에서 실수했다 싶으면 u로 되돌리시면 됩니다.

 

5. 복사 & 붙여넣기 

y : 선택한 영역을 복사합니다. 선택은 v로 선택할 수 있습니다.

yw : 현재부터 한 단어의 끝까지 복사할 수 있습니다.

byw : 현재 커서가 위치한 이 단어를 복사합니다.

yy : 한줄 복사를 할 수  있습니다.

nyy : n줄 복사

p : 현재 커서 아랫줄에 복사한 텍스트 붙여넣기

 

6. 단어 찾기

/찾을단어 

n : 다음 단어 찾기

N : 이전 단어 찾기

이 방법은 아주 단순하긴한데 정확히 그 문자와 일치하는 단어만 검색합니다. 즉, 대소문자를 구분해서 검색하는 방법입니다. 대소문자 구분없이 검색하기를 원한다면 끝에 \c를 붙여주면 됩니다. 이렇게요

/찾을단어\c

이때는 대소문자 구분없이 찾을 수 있습니다.

 

 

 

 

7. : 기본 명령어 - ESC를 누르고 ':'를 누른 상태

: set nu - 줄 표시를 합니다. 코드에서 라인을 확인하고 싶다면 아래의 명령으로 볼 수 있습니다. 혹은 set number를 full로 쳐주어도 됩니다.

: set nonu - 줄 표시를 해제합니다. 더 이상 라인을 보고싶지 않으면 이 명령으로 해제할 수 있습니다.

: 숫자 - 한번에 라인을 이동하고 싶으면 숫자를 치고 이동할 수 있습니다.

: w - 지금 수정된 사항을 write한다는 것으로 저장을 의미합니다. 하지만 vi 프로그램을 닫지는 않습니다.

: wq - 수정된 사항을 저장하고 나가겠다(quit)는 명령입니다. vi 프로그램을 저장함과 동시에 닫는 명령입니다.

: q! - 어떨때는 vi 편집기가 수정했는데 저장하지 않는다는 이유로 놓아주지 않는 경우가 있는데 이떄 저장하기 싫다면 q!를 사용하면 됩니다.

: set ts=n - 탭의 간격을 조절하는 명령어로 n은 숫자입니다. 예를 들어 set ts=4인 명령을 내린다면 탭의 간격은 공백 4개와 같은 간격을 갖습니다.

 

창분할 - 2가지가 존재합니다.

: sp - 현재 창을 평행 분할합니다.  다음 창은 Ctrl+w,w (Ctrl 누른 상태에서 w를 두번 누름)로 이동할 수 있습니다.

: sp [filename] : 파일 이름을 지정해주면 그 파일이 열립니다.

: vs - 현재 창을 수직 분할 합니다. 마찬 가지로 다음 창은 Ctrl+w,w로 이동할 수 있습니다.

: vs [filename] : 마찬가지로 파일 이름을 지정해서 현재 파일이 아닌 다른 파일을 열 수 있습니다.

 

8. 단어 변경 

:s/현재단어/바꿀단어

현재 단어와 일치하는 하나의 단어만을 변경합니다.

만약 문서 전체의 단어를 변경하고 싶으시면 앞에 %를 붙여주면 됩니다.

:%s/현재단어/바꿀단어

:%s/현재단어/바꿀단어/g

전체 문서에서 현재단어를 바꿉니다.

 

:시작 줄 번호, 끝 줄 번호s/현재단어/바꿀단어

혹은 줄번호를 주어 제약을 걸어서 시작 줄, 끝 줄에 있는 단어를 바꿀 수도 있습니다.

 

:%s/현재단어/바꿀단어/i

현재 단어의 대소문자를 무시하고 일치하면 단어를 치환합니다. i는 ignore를 의미하지요.

 

간혹가다가 위도우즈의 파일을 리눅스로 가져오면 ^M문자가 섞여 들어올때가 있습니다. 이때 ^M문자를 없애려면 아래와 같은 명령을 통해 깔끔하게 없앨 수 있습니다.

%s/^M$//g

 

 

 

 

 

9. 탭 추가

: tabnew [filename] - 현재 vi 창에 새로운 tab을 생성합니다. 기존의 파일을 열 수도 있고 아니면 새로운 파일을 열어서 편집할 수도 있습니다. 예를 들어 list.txt이라는 파일을 탭으로 생성하려면 아래의 명령을 통해서 가능합니다.

 

원래 열었던 파일(test)과 새로운 파일인 list.txt가 같이 열려있다는 것을 알 수 있고, 편집도 가능한 것을 알 수 있습니다.

 

gt : 다음 탭으로 이동합니다. 위의 경우에는 다음 탭이 없으므로 test 탭으로 이동합니다.

gT : 이전 탭으로 이동합니다. 위의 경우에서 이전 탭은 test이므로 test 탭으로 이동하지요.

탭 안에서 편집은 이제 vi 편집기의 명령어를 가지고 똑같이 편집하고 저장할 수가 있습니다.

 

10. 코딩에 도움이 되는 몇가지 명령어

헤더파일 열어 확인하기

헤더파일에 ctrl+wf를 누르면 헤더파일의 내용을 창분할로 볼 수 있습니다. 아래와 같이 stdio.h의 내용을 보고 싶으면 커서를 헤더파일 이름에 위치시키고 ctrl 키와 w,f 를 차례대로 누르면 됩니다.

 

 

폴딩하기

코드가 너무 길어 보기좋게 내용을 접고싶다면 폴딩기능을 사용할 수 있습니다. 아래처럼 v로 함수 모두를 선택해봅시다. v를 누르고 화살표로 선택할 수 있지만 함수가 긴 경우에는 다음과 같이 한번에 선택할 수 있습니다.

우선 함수 가장 앞에 'v'를 눌러 선택 모드로 지정하시구요. '$'를 눌러 가장 맨 끝 줄로 이동합니다. 가장 맨 끝 줄에는 함수를 여는 중괄호가 있습니다. 이때 '%'를 누르면 함수 전체를 선택하게 됩니다.  

 

 

 

함수 선택

zf : 폴딩하려면 함수가 모두 선택된 상황에서 zf를 누르면 됩니다.

 

zi : 폴딩된 상태에서 함수를 보고 싶어서 피거나, 아니면 핀것을 다시 접으려면 zi를 입력하면 됩니다. 이 명령어는 토글방식으로 접었다 폈다할 수 있습니다.

 

한번에 들여쓰기하기

여러분이 코드를 복사하다가 보면 어쩌다가 들여쓰기가 안된 상태로 복사가 될 때도 있습니다. 이때 노가다로 탭을 눌러서 늘리면 시간이 꽤나 걸리겠죠. 이때 한번에 들여쓰기하는 방법이 "gg=G"입니다.

커서가 어디에 있건 상관없습니다. gg는 문서의 맨앞을 의미, G는 문서의 끝을 의미합니다. =은 tab의 간격으로 들여쓰기 하라는 명령이죠. 즉, 문서의 처음부터 끝까지 들여쓰기하는 명령어입니다.

문서 전체 들여쓰기

 

자동 완성

다른 IDE를 사용할때 자동완성 기능이 있죠? "vi 편집기는 왜 그런게 없는거야" 라고 하시지 마시고 구글에서 그런 기능을 하는 명령어가 무엇인지 찾아보세요. 답은 ctrl + p , ctrl + n입니다. 둘 다 자동완성 기능을 하지만 어느쪽으로 펼쳐지느냐만 다를 뿐입니다. 문자를 쓰는 편집 모드에서 ctrl + p, ctrl + n을 사용해야합니다. esc 누르고 사용하는게 아닙니다.

vi 자동완성 기능

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

수행 시간 구하기(clock 함수)

 

여러분이 짠 프로그램의 얼마나 효율적으로 동작하는지 궁금하다거나 회사에서 솔루션을 개발하다가 본인의 함수가 얼마나 빠르게 동작하는 지 확인해볼 필요가 있습니다. 그럴때 프로그램이 얼마나 실행됐는지 궁금하시다면 clock()함수로 구할 수가 있습니다. clock함수는 아래와 같이 정의가 되어있습니다. time.h 헤더파일을 include해야함을 알 수가 있네요.

 

#include <time.h>
clock_t clock(void);

 

이 함수에 대해서 프로그램 수행시간은 아래에서 실제 구현하긴 할 것인데, 우선 이 함수에 대해서 설명부터 하도록 하겠습니다.

clock함수는 clock_t라는 값을 반환하는데 이 값은 CPU가 사용된 값을 나타냅니다. 정확히는 시간이 아닌 clock수를 반환하지요. 초 단위로 구하려면 초당 클록수를 알아야하는데 아래의 매크로를 사용하면 됩니다.

CLOCKS_PER_SEC

이처럼 초 단위로 바꾸고 싶다면 CLOCKS_PER_SEC라는 매크로로 나누어서 초단위로 구할 수 있습니다. clock수를 어떤 이유로 얻어올 수 없다면 (clock_t) -1 이 반환됩니다.

또한 이 함수는 Thread-Safety한 특징을 갖습니다.

이제 정말 프로그램의 수행시간을 알아내는 코드를 보도록 합시다. 아래의 코드는 for루프를 수행한 수행시간을 구합니다. 1초 이내의 끝이 날 것이기 때문에 보다 밀리-세컨 단위의 더 정확한 시간을 측정하기 위해서 clock_t 자료형에서 double로 형변환을 한 것입니다.

 

#include <time.h>
#include <stdio.h>

int main(void)
{
    int  i;
    double start, end;

    //for 루프 시작 시간
    start = (double)clock() / CLOCKS_PER_SEC;    

    //for루프 100000000번 돌아보기
    int sum = 0;
    for (i = 0; i < 100000000; i++) {
        sum++;
    }

    //for 루프 끝난 시간
    end = (((double)clock()) / CLOCKS_PER_SEC);
    printf("프로그램 수행 시간 :%lf\n", (end-start));
}

 

결과 

 

수행 결과

 

제 컴퓨터가 좋은 편이 아니라 0.436초 걸리고 난 후에 프로그램이 종료되었음을 알 수 있네요. 프로그램 수행시간을 측정하는 방법은 이처럼 clock() 함수를 이용해서 구해낼 수 있습니다. 이렇게 우리가 만든 알고리즘이 얼마나 걸리는지도 확인해볼 수 있겠습니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,

문자열을 숫자로 변환하는 함수

문자열을 숫자 자료형으로 변환하려면 어떤 방법으로 변환할 수 있을까요? 만약 입력이 숫자의 문자열이라고 가정한다면 아래의 코딩으로 숫자로 변환할 수 있습니다.

#include <stdio.h>

int my_atoi(const char* str) {
	int ret = 0;
	int i;
	for (i = 0; str[i] != '\0'; i++)	//NULL문자만나면 for문 종료
		ret = ret * 10 + (str[i] - '0');	//숫자 문자에 '0'을 빼면 레알 숫자가 구해짐
	return ret;
}
int main() {
	int str = "12345";
	printf("%d\n", my_atoi(str));

}

 

문자 하나에서 '0'을 빼면 이것이 곧 숫자 0이 된다는 점을 이용한 코드이지요. 사실 이렇게 간단하게 짤 수도 있겠지만 문자열에 숫자말고도 알파벳같은 글자가 섞여 들어오면 처리가 필요하게 됩니다. 코드짤때 에러 처리하는 것은 여간 귀찮은 것이 아니지요. 위 처럼 접근하는 것은 좋긴하지만 급할때 삽질하지 말라고 아래의 함수들을 C언어 라이브러리에서 제공해줍니다.

 

지금 소개해드릴 함수들이 그런 함수들입니다. 아래의 함수들을 사용하기 위해서는 stdlib.h 헤더파일을 우선 include시켜줘야한다는 점 기억하시구요.

 

atoi(Ascii To Integer) - int로 변환

#include <stdlib.h>
int atoi(const char* str);

atoi함수는 문자열을 정수형으로 변환시켜주는 함수입니다. 함수명의 앞글자 a는 Ascii를 의미합니다. 이후의 모든 함수의 a는 ascii의 a라는 점은 알아두시면 나중에 배운척 할 수 있습니다. 이 함수가 받는 str 문자열에서 숫자가 아닌 글자들은 모두 무시됩니다.

변환할 수 없는 값이라면 0을 반환합니다.

 

atol(Ascii To Long) - long으로 변환

atoll(Ascii To Long Long) - long long int로 변환

#include <stdlib.h>
long int atol(const char *str);
long long int atoll(const char *str);

long 형태로 입력을 받고 싶다면 atol 함수를 이용하면 됩니다. 이것도 모자라 long int의 범위를 넘어서는 큰 수를 입력받고 싶을때가 있습니다. 보다 큰 범위의 수를 변환하기를 원한다면 atoll 함수를 이용하여 변환할 수 있습니다. 

반환할 수 없으면 0L을 반환합니다.

 

atof(Ascii To Float) - 부동소수점 값으로 변환

#include <stdlib.h>
double atof(const char *str);

정수형말고도 소숫점으로 변환할 수 있는 함수도 있습니다. atof라는 함수이지요. 전달받은 문자열을 부동 소수점 값으로 변환해주는 함수입니다. 여기서 float자료형을 반환하는 게 아닌 double 자료형으로 return 한다는 것을 유의하세요. 

만약 함수를 통해서 반환할 수 없다면 반환값은 0이 됩니다.

 

단순한 설명보다는 무엇보다 어떻게 사용하는지 코드를 보면서 이해를 하는게 좋습니다. 

아래의 코드는 기본적인 함수들의 사용법을 보여주는 예제입니다.

#include <stdio.h>
#include <stdlib.h>
int main() {
	char* ll_str = "987654321123456789";
	char* i_str = "1234";
	char* f_str = "1234.567";
	//long long 자료형 출력
	printf("%lld\n", atoll(ll_str));

	//정수형 10진수로 출력
	printf("%d\n", atoi(i_str));

	//부동 소수점으로 출력
	printf("%.3f\n", atof(f_str));

	i_str = "1234에 문자열 꼽사리";
	printf("%d\n", atoi(i_str));

}

 

결과 화면

 

위 결과에서도 확인할 수 있듯이 "1234"에 다른 문자열 "에 문자열 꼽사리"를 섞어도 숫자만 추출해내는 것을 알 수 있습니다. 

이처럼 C언어에서는 문자열을 숫자로 변환시켜주는 함수를 제공하니 알맞게, 필요할때 사용하시면 되겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,