read
read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
read 명령은 stdin 로부터 라인을 읽어들여서 IFS 에 따라 값을 분리한 다음 지정한 [name ...]
에 할당합니다.
awk 의 용어를 빌려보면 라인은 record 에 해당하고 분리된 값은 field 에 해당하며 -d delim
옵션으로 지정하는 값은 RS (record seperator) , IFS 값은 FS (field seperator) 와 같은 의미가 됩니다.
name 값을 주지 않으면 읽어들인 라인은 REPLY 변수에 할당됩니다.
[name ...] 에 값을 할당하는 방법
# name 이 하나면 단어 전체를 할당
$ read v1 <<< "1 2 3 4 5"
$ echo $v1
1 2 3 4 5
-------------------------------
# name 이 단어 개수보다 적을경우 마지막 name 에 나머지를 할당
$ read v1 v2 v3 <<< "1 2 3 4 5"
$ echo $v1
1
$ echo $v2
2
$ echo $v3
3 4 5
----------------------------------
$ read _ v2 _ v4 _ <<< "1 2 3 4 5"
$ echo $v2
2
$ echo $v4
4
read 명령은 IFS 값에 따라 단어를 분리 합니다.
$ IFS=':|@' read -r c1 c2 c3 c4 <<< "red:green|blue@white"
$ printf "c1: %s, c2: %s, c3: %s, c4: %s\n" "$c1" "$c2" "$c3" "$c4"
c1: red, c2: green, c3: blue, c4: white
---------------------------------------------------------------------------
datetime="2008:07:04 00:34:45"
IFS=': ' read -r year month day hour minute second <<< "$datetime"
파이프에 연결된 명령은 subshell 에서 실행되므로 다음과 같이 할 수 없습니다.
$ echo 1 2 3 4 5 | read v1 v2 v3
$ echo $v1 $v2 $v3
$
................................
# 명령 group 을 이용하면 값을 표시할 수 있습니다.
$ echo 1 2 3 4 5 | { read v1 v2 v3; echo $v1 $v2 $v3 ;}
1 2 3 4 5
-----------------------------------------------------------
# here document 를 이용하는 방법
$ read v1 v2 v3 <<END
$( echo 1 2 3 4 5 )
END
$ echo $v1 $v2 $v3
1 2 3 4 5
파일에서 라인을 읽어 들일때 라인의 앞,뒤 공백을 유지하려면 IFS 값을 NUL 로 설정합니다.
$ cat test.txt # 라인 앞,뒤에 공백이 있다.
empty space
empty space
$ while read -r line; do # 라인 앞,뒤 공백이 없어진다.
echo "X${line}X"
done < test.txt
Xempty spaceX
Xempty spaceX
# IFS 값을 NUL 로 설정해야 라인 앞,뒤 공백이 유지된다.
$ while IFS= read -r line; do
echo "X${line}X"
done < test.txt
Xempty space X
X empty spaceX
Options
- -r : 읽어 들이는 데이터에서
\
문자를 이용한 escape 을 disable 합니다 (raw read).
$ read v # -r 옵션 미사용
xxx\t\nyyy # 입력
$ echo "$v"
xxxtnyyy # 출력
---------------------------------------
$ read -r v # -r 옵션 사용
xxx\t\nyyy # 입력
$ echo "$v"
xxx\t\nyyy # 출력( 입력된 그대로 출력 )
$ echo -e "$v"
xxx
yyy
- -d delim : 라인 구분자를 의미하며 기본적으로 newline 입니다.
# -d 값을 null 로 설정하면 파일 전체 라인을 읽어 들입니다.
$ read -r -d '' whole < datafile
$ echo "$whole"
20081010 1123 xxx
20081011 1234 def
20081012 0933 xyz
...
------------------------------------------------
# find 명령에서 -print0 을 이용해 출력했으므로 -d 값을 null 로 설정
find * -print0 | while read -r -d '' file; do
echo "$file"
done
- -a array : 단어를 분리해서 array 에 입력합니다.
$ IFS=, read -r -a arr <<< "1,2,3,4,5"
$ echo ${#arr[@]}
5
$ echo ${arr[1]}
2
# IFS 가 데이타 마지막에 올 경우 마지막 필드는 항목에서 제외됩니다.
$ IFS=, read -a arr <<< "1,2,3,4,"
$ echo ${#arr[@]}
4
-p prompt : 사용자에게 값을 입력받을때 prompt 를 설정할 수 있습니다.
( 프롬프트가 표시될 때는 stderr 로 출력됩니다. )-e : 사용자에게 값을 입력받을때 readline 을 사용합니다.
-i text :
-e
옵션과 같이 사용하며, 초기 입력값을 설정할 수 있습니다.
$ read -p "Enter the path to the file: " -ei "/usr/local/" reply
Enter the path to the file: /usr/local/bin
$ echo "$reply"
/usr/local/bin
-s : 사용자에게 값을 입력받을때 타입 한 값을 화면에 표시하지 않습니다.
-n nchars : nchars 만큼 문자를 읽어 들입니다. 중간에 라인 구분자를 만나면 중단합니다.
-N nchars : 라인 구분자를 상관하지 않고 무조건 nchars 만큼 읽어 들입니다.
$ read -n 8 v1 <<END
12345
6789
END
# 중간에 라인구분자 newline 을 만나므로 5 까지만 표시됩니다.
$ echo "$v1"
12345
--------------------
$ read -N 8 v1 <<END
12345
6789
END
# newline 이 포함되므로 7 까지 표시됩니다.
$ echo "$v1"
12345
67
-----------------------------------------
asksure() {
echo -n "Are you sure (Y/N)? "
while read -n 1 answer; do
echo
case $answer in
[Yy]) return 0 ;;
[Nn]) return 1 ;;
esac
done
}
-----------------------------------------
pause() {
read -s -n 1 -p "Press any key to continue..."
}
- -t timeout : 사용자에게 입력을 받을 때 timeout 값을 설정할 수 있습니다.
이 외에도 FD 를 named pipe 에 연결해 사용할 때 유용한 기능입니다. FD 를 파일에 연결해 사용할 경우 읽어들일 라인이 없으면 바로 리턴하고 오류 값이 반환되지만 named pipe 같은 경우는 더 이상 진행하지 못하고 block 됩니다. 이때 timeout 값을 설정하면 block 상태를 벗어날 수 있습니다.
timeout 값을 0 으로 설정하면 실제 라인을 읽어들이지 않습니다. 그러나 읽어들일 라인이 있을 경우는 0 을, 그 외는 오류를 반환하므로 읽어들일 라인이 있는지 없는지 테스트하는데 사용할 수 있습니다.
timeout 값은 소수로 입력할 수 있습니다.
$ mkfifo pipe
$ exec 3<> pipe
$ echo -e "111\n222" > pipe
$ read -r v <&3; echo status: $?, value: $v
status: 0, value: 111
$ read -r v <&3; echo status: $?, value: $v
status: 0, value: 222
# 읽어들일 라인이 없으므로 block 된다.
$ read -r v <&3; echo status: $?, value: $v
^C
--------------------------------------------
# 다음은 timeout 값을 0.1 초로 설정합니다.
$ echo -e "111\n222" > pipe
$ read -r -t.1 v <&3; echo status: $?, value: $v
status: 0, value: 111
$ read -r -t.1 v <&3; echo status: $?, value: $v
status: 0, value: 222
# 읽어들일 라인이 없을경우 0.1 초 후에 block 상태에서 리턴합니다.
$ read -r -t.1 v <&3; echo status: $?, value: $v
status: 142, value:
# timeout 값을 0 으로 설정하면 읽어들일 라인이 있는지 테스트할 수 있습니다.
$ read -r -t0 v <&3; echo status: $?
status: 1
sh
에서는 -t 옵션을 사용할 수 없으므로 다음과 같이 timeout 명령을 이용합니다.
# 10 초 동안 사용자로부터 입력을 받음
sh$ AA=$( timeout --foreground 10 sh -c 'read -r v; echo "$v"' )
hello # 사용자 입력
sh$ echo "$AA"
hello
---------------------------------------------------------------
sh$ exec 3<> pipe
sh$ echo "111\n222" > pipe
sh$ AA=$( timeout --foreground .1 sh -c 'read -r v <&3; echo "$v"' ); echo status: $?, value: "$AA"
status: 0, value: 111
sh$ AA=$( timeout --foreground .1 sh -c 'read -r v <&3; echo "$v"' ); echo status: $?, value: "$AA"
status: 0, value: 222
sh$ AA=$( timeout --foreground .1 sh -c 'read -r v <&3; echo "$v"' ); echo status: $?, value: "$AA"
status: 124, value:
- -u fd : stdin 대신에 file descriptor 로 부터 데이터를 읽어 들입니다.
exec 3< infile
while read -r -u3 line; do
echo "$line"
done
exec 3>&-
............................
$ while read -r -u3 line; do echo "$line"; done 3<<END
> 111
> 222
> END
111
222
# sh 에서는 다음과 같이 하면 됩니다.
read -r line <&3
종료 상태 값
다음의 경우는 오류에 해당하고 0 이 아닌값을 리턴합니다.
- end-of-file 상태를 만났을때
$ cat infile
111
222
$ exec 3< infile
$ read -r v <&3; echo status: $?, value: $v
status: 0, value: 111
$ read -r v <&3; echo status: $?, value: $v
status: 0, value: 222
# 읽어들일 라인이 없을 경우 종료상태값 1 을 리턴
$ read -r v <&3; echo status: $?, value: $v
status: 1, value:
- read times out (이때는 128 이상의 값을 리턴합니다.)
- 변수에 값을 할당할때 오류발생
-u
옵션에 사용된 유효하지 않은 FD (file descriptor)
여기서 첫 번째 경우 end-of-file 상태를 만났을 때를 살펴볼 필요가 있는데요.
파일 마지막 라인에 newline 이 없으면 value 값은 정상적으로 설정되는데도
종료 상태 값은 1 이 될 수 있습니다.
이것은 -d
옵션 값으로 null 을 사용할 때도 해당됩니다.
$ echo -en "111\n222" > file
$ od -a file
0000000 1 1 1 nl 2 2 2 # 마지막 라인에 newline 이 없다.
0000007
$ exec 3<> file
$ read -r v <&3; echo status: $?, value: "$v"
status: 0, value: 111
# value 값은 정상적으로 설정되지만 종료 상태 값은 1 이 된다.
$ read -r v <&3; echo status: $?, value: "$v"
status: 1, value: 222
Quiz
파일을 while 문으로 읽어들일 때 파일 끝에 newline 이 없으면 마지막 라인이 오류로 인식이 되어 value 값은 설정되지만 프린트가 되지 않습니다. 또한 기본적으로 각 라인의 앞, 뒤에 있는 공백이 제거되는데요. 어떻게 하면 파일을 원본 그대로 출력할 수 있을까요?
# 1. IFS='' 로 설정하여 라인의 앞, 뒤에 존재하는 공백을 유지합니다.
# 2. -r 옵션을 설정하여 '\' 문자의 escape 이 처리되지 않게 합니다.
# 3. || [ -n "$line" ] 을 추가하여 파일 끝에 newline 이 존재하지 않아
# 오류가 발생할시 처리될수 있게 합니다.
while IFS= read -r line || [ -n "$line" ]
do
echo "$line"
done < file.txt