본문 바로가기

Linux/쉘스크립트

I/O 재지향

쉘은 항상 기본적으로 표준입력(stdin, 키보드), 표준출력(stdout, 스크린), 표준에러(stderr, 스크린에 뿌려질 에러 메세지) "파일들"을 열어 놓습니다. 이 파일들을 포함해서 열려 있는 어떤 파일이라도 재지향 될 수 있습니다. 재지향이란 간단히 말해서 파일, 명령어, 프로그램, 스크립트, 심지어는 스크립트 속의 코드 블럭의 출력을 낚아 채서 다른 파일, 명령어, 프로그램, 스크립트의 입력으로 보내는 것입니다.

열려 있는 파일 각각은 파일 디스크립터(file descriptor)를 할당 받습니다. [1] 표준입력, 표준출력, 표준에러에 해당하는 파일 디스크립터는 각각 0, 1, 2 입니다. 추가적으로 열리는 파일을 위해서 3부터 9까지의 파일 디스크립터가 남겨져 있습니다. 종종, 이 추가적인 파일 디스크립터들중의 하나를 표준입력, 표준출력, 표준에러로 할당해서 임시적인 중복된 링크로 쓰는 것이 유용할 때가 있습니다. 이런 방법을 쓰면 아주 복잡한 재지향이나 파일 디스크립터를 뒤죽 박죽 사용했을 때, 아주 간단하게 원래대로 복구시켜 줍니다

> # 표준출력을 파일로 재지향. # 파일이 없으면 새로 만들고, 있다면 덮어 씁니다. > /etc/shadow # 이런거 하면 큰일남. 로그인 못함

ls -lR > dir-tree.list # 디렉토리 트리 목록을 파일로 저장해 줍니다. : > filename # > 는 "filename"의 길이가 0 이 되도록 잘라줍니다. # : 는 아무 출력도 안 하는 더미 플레이스홀더(placeholder)로 동작합니다. >> # 표준출력을 파일로 재지향. # 파일이 없으면 새로 만들고, 있으면 파일 끝에 덧붙입니다. 2>&1 # 표준에러를 표준출력으로 재지향. # 에러 메세지는 표준 출력의 자격으로 스크린에 보내집니다. i>&j # i번 파일 디스크립터를 j번 파일디스크립터로 재지향. # i가 가르키는 파일의 모든 출력은 j가 가르키는 파일로 보내집니다. >&j # 기본적으로 1번 파일 디스크립터(표준출력)를 j번 파일 디스크립터로 재지향. # 모든 표준출력은 j가 가르키는 파일로 보내집니다. 0< < # 파일에서 입력을 받도록 해줍니다. # ">"와 짝을 이루는 명령어로, 종종 같이 쓰입니다. # # grep search-word <filename [j]<>filename # "filename"을 읽고 쓰기용으로 열고 "j"번 파일 디스크립터를 할당합니다. # "filename"이 없다면 새로 만듭니다. # "filename"이 주어지지 않으면 기본적으로 표준입력인 0번이 할당됩니다. # # 이를 응용하면 파일의 특정한 위치에 쓰기를 할 수 있습니다. echo 1234567890 > File # "File"에 문자열을 씁니다. exec 3<> File # "File"을 열고 3번 파일 디스크립터를 할당합니다. read -n 4 <&3 # 문자 4개만 읽은 다음, echo -n . >&3 # 소수점을 쓰고, exec 3>&- # 3번 파일 디스크립터를 닫습니다. cat File # ==> 1234.67890 # 어라, 랜덤 억세스네. | # 파이프. # 프로세스와 명령어를 엮어 주는 일반적인 목적의 툴. # ">"와 비슷하지만, 실제로는 좀 더 일반적으로 쓰입니다. # 명령어, 스크립트, 파일, 프로그램들을 함께 묶는데 유용하게 쓰입니다. cat *.txt | sort | uniq > result-file # 모든 *.txt 파일의 출력을 정렬한 다음, 중복되는 줄을 제거하고 # 마지막으로 그 결과를 "result-file"에 저장.

여러개의 입출력 재지향과 파이프를 하나의 명령어 줄에서 같이 쓸 수 있습니다.

command < input-file > output-file
command1 | command2 | command3 > output-file

예 12-23 와 예 A-10 를 참고.

여러개의 출력 스트림이 한 파일로 재지향 될 수도 있습니다.

ls -yz >> command.log 2>&1
# "ls"의 잘못된 옵션인 "yz"의 결과를 "command.log"로 저장합니다.
# 표준에러가 파일로 재지향 됐기 때문에 어떤 에러 메세지라도 그 파일에 저장됩니다.

파일 디스크립터 닫기

n<&-

n번 입력 파일 디스크립터를 닫아 줍니다.

0<&-<&-

표준입력을 닫아 줍니다.

n>&-

n번 출력 파일 디스크립터를 닫아 줍니다.

1>&->&-

표준출력을 닫아 줍니다.

자식 프로세스는 열려 있는 파일 디스크립터를 상속 받는데 이것 때문에 파이프가 동작합니다. 파일 디스크립터가 상속되길 바라지 않는다면 그 파일 디스크립터를 닫으면 됩니다.

# 파이프로 표준에러만 재지향 하기.

exec 3>&1                              # 표준출력의 현재 "값"을 저장.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # 'ls'와 'grep'을 위해 3번 파일 디스크립터를 닫고,
exec 3>&-                              # 이제, 스크립트 나머지 부분을 위해 닫습니다.

# Thanks, S.C.


I/O 재지향
====================================================================
[root@fw ~]# ls -l /proc/12896/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2
[root@fw ~]# tty
/dev/pts/2
[root@fw ~]# ps
  PID TTY          TIME CMD
12896 pts/2    00:00:00 bash
13048 pts/2    00:00:00 ps

-- filetest.c --
#include <stdio.h>   // fopen, fclose, fprintf 
#include <unistd.h>  // sleep

int main()
{
  FILE *fp;
  fp = fopen("/tmp/filetest.txt", "a");
  fprintf(fp, "Hello World \n");
  sleep(100);
  fclose(fp);

  return 0;
}
-- filetest.c --

-- 실행결과 --
# ./filetest&
# ls -l /proc/13122/fd
total 0
lrwx------ 1 root root 64 May  9 11:40 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:40 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:40 2 -> /dev/pts/2
l-wx------ 1 root root 64 May  9 11:40 3 -> /tmp/filetest.txt
-- 실행결과 --


>
표준출력을 파일로 재지향.
파일이 없으면 새로 만들고, 있다면 덮어 쓴다.
덮어쓴다는 얘기는 기존의 내용을 삭제하고 새로운 내용이 들어간다.


# ls -ld /etc/passwd
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd
# ls -ld /etc/passwd > passwd.txt
# cat passwd.txt 
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd


 >>
표준출력을 파일로 재지향.
파일이 없으면 새로 만들고, 있으면 파일 끝에 덧붙인다.

# ls -ld /etc/passwd >> passwd.txt
# cat passwd.txt 
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd

# ls -ld /etc/passwd >> passwd.txt
# cat passwd.txt 
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd

# ls -ld /etc/passwd > passwd.txt
# cat passwd.txt 
-rw-r--r-- 1 root root 2073 May  8 21:44 /etc/passwd


- 정상출력(1) 으로 재지향
# echo 12345 1> a.txt
# cat a.txt 
12345
# echo 1234567890 2> a.txt
1234567890
# cat a.txt 
# 
# 
# echo 1234567890
1234567890
# echo $?
0

- 비정상출력(2) 으로 재지향
# afjaksdf
-bash: afjaksdf: command not found
# eco $?
-bash: eco: command not found
# afjaksdf
-bash: afjaksdf: command not found
# echo $?
127
# afjaksdf 1> a.txt
-bash: afjaksdf: command not found
# cat a.txt 
# afjaksdf 2> a.txt
# cat a.txt 
-bash: afjaksdf: command not found


- IO 재지향을 이용한 파일쓰기
# echo 0123456789 > File
# cat File 
0123456789

# exec 3<>File
# ls -l /proc/$$/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:58 3 -> /root/File
# read -n 4 <&3
# cat File 
0123456789

# echo -n . >&3
# cat File 
0123.56789

# exec 3>&-
# ls -l /proc/$$/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2

# cat File 
0123.56789


 test.c 
/*----- fname : test.c -----*/

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

int main()
{
  sleep(100);  
  return 0;
}

[root@fw ~]# ./test > /dev/null 2>&1 &
[1] 13214
[root@fw ~]# ls -l /proc/13214/fd
total 0
lrwx------ 1 root root 64 May 14 11:18 0 -> /dev/pts/4
l-wx------ 1 root root 64 May 14 11:18 1 -> /dev/null
l-wx------ 1 root root 64 May 14 11:18 2 -> /dev/null
[root@fw ~]# 
[1]+  Done                    ./test > /dev/null 2>&1
[root@fw ~]# 
[root@fw ~]# 
[root@fw ~]# 
[root@fw ~]# ./test 2> /dev/null >&2 &
[1] 13216
[root@fw ~]# ls -l /proc/13216/fd
total 0
lrwx------ 1 root root 64 May 14 11:21 0 -> /dev/pts/4
l-wx------ 1 root root 64 May 14 11:21 1 -> /dev/null
l-wx------ 1 root root 64 May 14 11:21 2 -> /dev/null
[root@fw ~]# ./test 2> /dev/null 1>&2 &
[2] 13218
[root@fw ~]# ls -l /proc/13218/fd
total 0
lrwx------ 1 root root 64 May 14 11:21 0 -> /dev/pts/4
l-wx------ 1 root root 64 May 14 11:21 1 -> /dev/null
l-wx------ 1 root root 64 May 14 11:21 2 -> /dev/null


- 파일디스크립터 닫기

pts/0# ps
  PID TTY          TIME CMD
13232 pts/4    00:00:00 bash
13311 pts/4    00:00:00 ps

pts/0# ls -l /proc/$$/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2

pts/1# ls -l /proc/13232/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 1 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2

pts/0# exec 1<&-

pts/1# ls -l /proc/13232/fd
total 0
lrwx------ 1 root root 64 May  9 10:14 0 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 2 -> /dev/pts/2
lrwx------ 1 root root 64 May  9 11:27 255 -> /dev/pts/2

pts/0# ps 
pts/0# ls


pts/0# vi .bash_logout 
-- .bash_logout --
sleep 50
clear
-- .bash_logout --
pts/0# ps
  PID TTY          TIME CMD
13323 pts/2    00:00:00 bash
13355 pts/2    00:00:00 ps
pts/0# exec 0<&-

pts/1# ls -l /proc/13323/fd
total 0
lrwx------ 1 root root 64 May 14 11:30 0 -> /dev/pts/2
lrwx------ 1 root root 64 May 14 11:31 1 -> /dev/pts/2
lrwx------ 1 root root 64 May 14 11:31 2 -> /dev/pts/2
lrwx------ 1 root root 64 May 14 11:31 255 -> /dev/pts/2

pts/1# ls -l /proc/13323/fd
total 0
lrwx------ 1 root root 64 May 14 11:31 1 -> /dev/pts/2
lrwx------ 1 root root 64 May 14 11:31 2 -> /dev/pts/2
lrwx------ 1 root root 64 May 14 11:31 255 -> /dev/pts/2

====================================================================


'Linux > 쉘스크립트' 카테고리의 다른 글

변수 타입 지정: declare 나 typeset  (0) 2015.05.14
매개변수 치환(Parameter Substitution)  (0) 2015.05.14
문자열 조작  (0) 2015.05.13
내부 변수(Internal Variables)  (0) 2015.05.12
until과 위치 매개변수 사용  (0) 2015.05.11