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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

재지정(Redirection)

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

순서대로 파일 디스크립터(fild descriptor)는 0, 1, 2입니다.

 

리다이렉션(Redirection)이라고 하는 것은 이러한 서술자 중 하나 이상을 파일로 다시 지정하는 수단이라고 볼 수 있는데요. 어떻게 리다이렉션을 사용할까요?

 

1) program > file_name

>는 이해하기 쉽습니다. program에서 출력하는 것을 file_name이라는 파일에 기록하겠다는 것입니다. 더 잘 이해하기 위해서 저의 리눅스를 활용해보도록 하지요.

제 리눅스의 /tmp 디렉터리에는 다음과 같은 파일들이 있습니다.

무슨 파일들인지는 잘 모르겠네요.


[root@localhost tmp]# ls
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp

ls 명령을 치는 순간 화면에 보이는 파일들의 목록이 있죠? 이것은 표준 출력으로 표시한 것입니다. 쉽게 이해하기 위해서 모니터가 표준 출력이라고 보세요.

 

이제는 이것을 파일로 저장해보도록 합시다. 위의 꺽쇠를 이용하면 됩니다.


[root@localhost tmp]# ls > list_stdout
[root@localhost tmp]# cat list_stdout
list_stdout
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp
tracker-extract-files.0

그러면 list_stdout이 생겨날텐데 이것을 cat명령어로 실행해보면 파일의 내용을 볼 수 있습니다. 위의 ls 명령어의 결과와 같죠?

 

그렇다면 다음의 명령은 어떻게 출력이 될까요? 분명 위의 ls에는 aaa.txt라는 파일은 없었습니다. 과연 파일로 저장될까요? 


[root@localhost tmp]# ls -l aaa.txt > list_aaa
ls: cannot access 'aaa.txt': 그런 파일이나 디렉터리가 없습니다
[root@localhost tmp]# cat list_aaa

list_aaa가 생겨나지 않았고 메시지가 출력이 되네요. 여기서 위의 메시지는 stderr로 출력된 에러 메시지입니다. stderr로 출력된 에러 메시지는 list_aaa의 파일로 저장이 되지 않네요. 

 

그 이유는 기본적으로 ls -l aaa.txt > list_aaa는 ls -l aaa.txt 1> list_aaa와 같이 동작합니다. 여기서 숫자 1은 표줄 출력(stdout)을 의미하게 됩니다. 1이 파일 디스크립터라면 우리는 표준 에러는 다음과 같이 저장할 수 있겠군요. 

 

아래의 결과를 보면 2(표준에러)가 리다이렉션되어 파일의 내용으로 전달되는 것을 알 수 있습니다.

 


[root@localhost tmp]# ls -l aaa.txt 2> list_aaa
[root@localhost tmp]# cat list_aaa
ls: cannot access 'aaa.txt': 그런 파일이나 디렉터리가 없습니다

 

2) program >> file_name

꺽쇠가 두개 연달아 있는 것은 파일의 끝에 내용을 덧붙이라는 것으로 여러분이 직접 실험해보시기 바랍니다.

 

 

 

 

3) program < file_name

여기서부터는 조금 헷갈릴 수 있으니 잘 들으세요. 아니, 보세요. file_name의 파일 내용을 program의 표준 입력으로 사용하겠다 라는 의미가 됩니다. 

 

ls는 표준입력을 받지 않는다.

자, 프로그램에는 표준 입력으로 사용자의 입력을 받는 것이 있고 그렇지 않는 프로그램이 있습니다. 여기서 ls는 사용자의 입력을 표준입력으로 받을까요? 결론은 아닌데요. 

아래의 내용으로 확인해보겠습니다.

우선 아까 없던 aaa.txt를 만들어볼텐데 aaa.txt에는 aaa라는 문자를 기록하도록 하지요. 그리고 다시 echo를 사용해 bbb라는 내용을 갖는 aaa파일을 만들어봅시다.


[root@localhost tmp]# echo aaa > aaa.txt
[root@localhost tmp]# ls aaa.txt
aaa.txt
[root@localhost tmp]# echo bbb > aaa
[root@localhost tmp]# ls aaa
aaa

(echo는 그 다음의 입력을 표준 출력으로 출력하는데 아까 배운 >을 통해서 파일로 저장했습니다. )

 

이제 ls에 aaa.txt의 내용을 표준입력으로 받아보도록 하겠습니다. 


[root@localhost tmp]# ls < aaa.txt
aaa
aaa.txt
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp
tracker-extract-files.0

뭐 얼핏보면 뭔가 출력이 됐는데 우리가 원하는 결과는 aaa.txt의 내용은 bbb가 ls의 입력으로 들어가서 ls aaa라는 명령어가 수행되어야하는 것입니다. 근데 제가 전달하는 입력을 씹고 현재 디렉터리의 파일 내용을 출력하고 있죠. 이것은 ls의 기본 동작입니다. 

ls는 이 처럼 표준 입력을 받지 않습니다. 대신 argv라고하는 argument vector를 통해서 사용자 입력을 받고 있는 셈입니다.

 

표준입력을 받는 명령어는 무엇이 있을까요? 

대표적으로 cat, grep 등이 있습니다. 

 

cat의 기본 동작

cat의 기본동작(아무런 옵션을 주지 않을때의 동작)은 입력받은 거 그대로 출력하는 기능입니다. 그 입력은 표준입력으로 받습니다. 

표준 입력으로 받으니 이것을 파일의 내용을 입력으로 줘보겠습니다.


[root@localhost tmp]# echo Hello > hello.txt
[root@localhost tmp]# cat < hello.txt
Hello

 

cat명령어는 argv로 파일 이름을 인자로 받을 수 있도록 프로그램이 되어있는데, 결과는 위와 같습니다.

 

 

 

 

grep의 기본 동작

grep은 기본적으로 바로 뒤에 인자가 포함된 문자열이 입력이 되면 그 문자열을 다시 출력해줍니다. 아래의 결과를 보면 알 수 있는데 이 grep은 표준 입력으로 입력을 받습니다.


[root@localhost tmp]# grep reak
AAA
BBB
rea
rreak
rreak
rreakk
rreakk
mmsmsms

자, 여기서 눈 여겨 봐야할 것은 grep은 굵은 글씨인 rreak, rreakk만 출력하였습니다. 왜냐면 인자로 받은 reak라는 문자열이 포함되있기 때문이죠.

 

표준 입력으로 받으니 우리는 파일을 입력으로 리다이렉션을 쓸 수 있겠군요. 위의 입력했던 내용을 그대로 vi 편집기로 저장합니다. 


[root@localhost tmp]# vi grep_test
[root@localhost tmp]# grep reak < grep_test
rreak
rreakk


vi 편집기 내용


AAA 
BBB 
rea 
rreak
rreakk
mmsmsms 

 

표준 입력으로 받은 결과와 동일함을 알 수 있네요.

 

리다이렉션을 통한 Copy

리다이렉션을 통해서 파일을 복사할 수도 있는데요. 아래를 보도록 합시다.  echo로 file을 생성한 후에 cat의 입력으로 file의 내용을 받습니다. 그 후에 cat은 표준 출력으로 file의 내용을 출력하는데 다시 >로 표준 출력의 내용을 file_copied라는 파일에 저장합니다. 결국은 복사를 수행한 것과 같게 됩니다.


[root@localhost tmp]# echo hello > file
[root@localhost tmp]# cat < file > file_copied
[root@localhost tmp]# cat file_copied
hello

 

 

 

 

 

파이프( | )

파이프라는 것은 program1의 표준 출력을 program2의 표준입력으로 입력받습니다. 명령어의 결과를 다시 어떤 명령어의 입력으로 받아야할때 사용되며 리눅스에서 매우 흔히 쓰입니다. 형식은 아래와 같습니다.

program1 | program2

파이프라는 것은 program1의 표준 출력을 program2의 표준입력으로 입력받습니다. 리눅스에서 매우 흔히 쓰이는데요. 가장 빈번하게 사용하는 grep과 연관시켜서 사용해봅시다. 

우선 제 리눅스에서 /bin으로 이동했습니다. 그리고 ls -l 명령을 보면 무수히 많은 파일들이 보이는데 그 중에서 zip이라는 문자열을 포함한 것을 확인해보고 싶습니다. 

그렇다면 ls -l로 우선 표준 출력을 하고 이 표준 출력된 내용을 zip이라는 인자를 전달받는 grep에게 표준 입력으로  전달시키면 되겠죠?

답은 간단합니다. 아래의 명령을 실행시켜보세요.


[root@localhost bin]# ls -l | grep zip
lrwxrwxrwx. 1 root root           5  5월 10  2019 bunzip2 -> bzip2
lrwxrwxrwx. 1 root root           5  5월 10  2019 bzcat -> bzip2
-rwxr-xr-x. 1 root root       38472  5월 10  2019 bzip2
-rwxr-xr-x. 1 root root       17560  5월 10  2019 bzip2recover
-rwxr-xr-x. 1 root root       40256  5월 10  2019 funzip
-rwxr-xr-x. 1 root root        3447  5월 10  2019 gpg-zip
-rwxr-xr-x. 1 root root        2345 11월  8  2019 gunzip
-rwxr-xr-x. 1 root root      170544 11월  8  2019 gzip
lrwxrwxrwx. 1 root root           6  5월 11  2019 mzip -> mtools
-rwxr-xr-x. 1 root root        5656  5월 13  2019 preunzip
-rwxr-xr-x. 1 root root        5656  5월 13  2019 prezip
-rwxr-xr-x. 1 root root       13352  5월 13  2019 prezip-bin
-rwxr-xr-x. 2 root root      210408  5월 10  2019 unzip
-rwxr-xr-x. 1 root root      105064  5월 10  2019 unzipsfx
-rwxr-xr-x. 1 root root      234496  5월 11  2019 zip
-rwxr-xr-x. 1 root root      105376  5월 11  2019 zipcloak
-rwxr-xr-x. 1 root root        2953 10월 10  2008 zipgrep
-rwxr-xr-x. 2 root root      210408  5월 10  2019 zipinfo
-rwxr-xr-x. 1 root root      100104  5월 11  2019 zipnote
-rwxr-xr-x. 1 root root      100112  5월 11  2019 zipsplit

굳이 그림으로 표현하면 아래와 같이 실행됩니다.

 

이렇게 리다이렉션과 파이프에 대해서 알아보았습니다.

궁금하신 점은 댓글로 달아주세요.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

파이프와 더불어 더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

파이프(Pipe)

파이프(Pipe)란 프로세스간 통신을 할때 사용하는 커뮤니케이션의 한 방법입니다. 가장 오래된 UNIX 시스템의 IPC로 모든 유닉스 시스템이 제공합니다. 하지만 두가지 정도의 한계점이 있습니다.

 

첫번째 한계점으로 파이프는 기본적으로 반이중 방식입니다. 물론 전이중 방식을 지원하는 시스템이 있긴 하나, 최대의 이식성을 위해서는 파이프는 반이중 방식이라는 생각을 해야합니다. 이것은 FIFO라는 명명된 파이프로 극복할 수 있습니다.

두번째 한계점으로는 부모, 자식 관계에서의 프로세스들만 사용할 수 있습니다. 부모프로세스가 파이프를 생성하고, 이후 자식 프로세스와 부모프로세스가 파이프를 이용하여 통신합니다.

 

이러한 한계점이 잇긴 하지만 여전히 쓸모있는 IPC기법입니다.

 

파이프는 unistd.h 헤더파일이 존재합니다.

 

#include <unistd.h>
int pipe(int fd[2]);

pipe함수가 성공적으로 호출되었다면 0, 실패했을 경우 -1을 반환합니다.

인자 fd는 2개의 원소가 있는 배열이라는 점을 주목합시다. 2개의 원소를 쓰는 이유가 있습니다. 아래의 그림을 보면서 이해합시다.

 

 

파이프는 커널영역에 생성되어 파이프를 생성한 프로세스는 파일 디스크립터만 갖고 있게 됩니다. 여기서 파일디스크립터 fd[1]은 쓰기용 파이프, fd[0]은 읽기용 파이프입니다. 그러니 우리가 만약 데이터를 fd[1]에 쓰게 되면 fd[0]으로 그 데이터를 읽을 수 있는 것입니다.

 

그렇다면 자식 프로세스를 하나 더 두어서 자식과 부모가 통신할 수 있게 하려면 어떻게 해야할까요? 우선 자식 프로세스를 fork하면 파일 디스크립터는 부모의 파일디스크립터를 자식이 그대로 사용할 수 있는 것을 활용합니다. (파일디스크립터가 그대로 자식프로세스에 복제됩니다.)

부모프로세스는 파이프에 데이터를 쓰는 프로세스, 자식 프로세스는 그 파이프에서 데이터를 읽는 프로세스로 설계합시다.

 

 

우선 부모 프로세스에서 파이프를 생성하면 파이프에 데이터를 쓸것이기 때문에 읽기 파이프는 닫습니다. fd[0]이죠? 그런 후 fd[1]에 데이터를 씁니다.

자식 프로세스는 쓰기 파이프는 쓰지 않으므로 fd[1]을 닫고, 읽기 파이프로 데이터를 읽습니다.

 

다음은 그런 기능을 하는 코드입니다.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
        int fd[2];
        pid_t pid;
        char buf[MAX_BUF];

        if(pipe(fd) < 0){
                printf("pipe error\n");
                exit(1);
        }
        if((pid=fork())<0){
                printf("fork error\n");
                exit(1);
        }

        printf("\n");
        if(pid>0){ //parent process
                close(fd[READ]);
                strcpy(buf,"message from parent\n");
                write(fd[WRITE],buf,strlen(buf));
        }else{  //child process
                close(fd[WRITE]);
                read(fd[READ],buf,MAX_BUF);
                printf("child got message : %s\n",buf);
        }
        exit(0);
}

 

결과는 아래와 같습니다.

 

child got message : message from parent

 

자식 프로세스에서 부모 프로세스가 pipe에 쓴 데이터를 읽었습니다. 

또는 자식프로세스가 데이터를 쓰고, 부모프로세스가 데이터를 읽는 설계도 가능하겠죠.

 

 

그렇다면 부모 프로세스와 자식 프로세스가 읽기, 쓰기가 가능하게 구현하려면 어떻게 해야할까요? 파이프를 한개만 사용한다고 해봅시다.

 

그리고 이런 상황을 가정해보지요.

1. 먼저 부모프로세스가 파이프에 fd[1]로 데이터를 보냅니다. 

2. 그 이후 자식 프로세스가 부모 프로세스가 쓴 데이터를 fd[0]으로 읽습니다.

3. 자식 프로세스는 바로 fd[1]로 파이프에 응답값을 보냅니다.

4. 부모 프로세스는 fd[0]으로 자식 프로세스가 보낸 응답값을 읽습니다.

 

결론을 말씀드리면 항상 위의 상황은 발생하지 않습니다. 그 이유는 누가 먼저 파이프를 읽느냐에 따라서 결과가 달라지는데, 만일 부모프로세스가 파이프에 쓰고, 자식 프로세스가 그 데이터를 읽기도 전에 부모프로세스가 먼저 데이터를 읽는다면 파이프에 데이터는 없겠죠. 허나 자식 프로세스는 없는 데이터를 계속 읽기만 기다리고 있기 때문에 프로그램이 망하게 되는 겁니다.

 

이때는 파이프를 2개 사용해야합니다.

fdA와 fdB 2개 사용합니다. 부모프로세스는 자식에게 쓰기용으로 fdA[1], 자식프로세스로부터 읽기용으로 fdB[0]만 있으면 됩니다. 필요없는 fdA[0], fdB[1]은 닫아줍니다.

그리고 자식프로세스는 부모프로세스로부터 읽기용으로 fdA[0], 쓰기용으로 fdB[1]만 있으면 되지요. 역시 필요없는 fdA[1], fdB[0]은 닫아줍니다.

이제 이런 개념으로 코드를 구현합시다.

 

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
        int fdA[2],fdB[2];
        pid_t pid;
        char buf[MAX_BUF];
        int count=0;

        if(pipe(fdA) < 0){
                printf("pipe error\n");
                exit(1);
        }

        if(pipe(fdB) < 0){
                printf("pipe error\n");
                exit(1);
        }

        if((pid=fork())<0){
                printf("fork error\n");
                exit(1);
        }

        printf("\n");
        if(pid>0){ //parent process
                close(fdA[READ]);
                close(fdB[WRITE]);
                while(1){
                        sprintf(buf,"parent %d",count++);
                        write(fdA[WRITE],buf,MAX_BUF);
                        memset(buf,0,sizeof(buf));
                        read(fdB[READ],buf,MAX_BUF);
                        printf("parent got message : %s\n",buf);
                        sleep(1);
                }
        }else{  //child process
                close(fdA[WRITE]);
                close(fdB[READ]);
                count=100000;
                while(1){
                        sprintf(buf,"child %d",count++);
                        write(fdB[WRITE],buf,MAX_BUF);
                        memset(buf,0,sizeof(buf));
                        read(fdA[READ],buf,MAX_BUF);
                        printf("\tchild got message : %s\n",buf);
                        sleep(1);
                }
        }
        exit(0);
}

 

 

부모 프로세스는 0부터 1초마다 증가한 값을 파이프에 쓰고, 자식 프로세스로부터 파이프로 읽습니다. 자식 프로세스는 100000부터 증가한 값을 1초마다 쓰고, 읽습니다. 그 결과는 아래와 같습니다.

        child got message : parent 0
parent got message : child 100000
        child got message : parent 1
parent got message : child 100001
        child got message : parent 2
parent got message : child 100002
        child got message : parent 3
parent got message : child 100003
        child got message : parent 4
parent got message : child 100004
        child got message : parent 5
parent got message : child 100005

       

부모 자식 관계의 프로세스가 아닌 별개의 프로세스가 통신할때는 아까 위에서 말씀드린 것 처럼 FIFO를 사용해야합니다. 

 

파이프를 이용한 IPC구현, 이제 어렵지 않겠죠?

 

반응형
블로그 이미지

REAKWON

와나진짜

,