우리는 최근에 이것을 사용한 몇 가지 게시물을 보았습니다.
var=$(</dev/stdin)
쉘의 표준 입력을 변수로 읽어보십시오.
그러나 적어도 Linux 기반 시스템과 Cygwin에서는 이것이 올바른 접근 방식이 아닙니다.
왜? 올바른 방법은 무엇입니까?
답변1
(아니, 이번에는 그것과 관련이 있어주변에 따옴표가 없습니다.$(...)
1).
$(<file)
운영자
Korn 쉘 연산자( zsh
및 에서도 지원됨 bash
)는 다음에 자세히 설명되어 있습니다.Bash의 파일 읽기 명령 대체 이해.
간단히 말해서, 이는 $(cat < file)
파일 읽기가 cat
실행이 필요하지 않고 셸에서 내부적으로 수행된다는 점과 를 제외 bash
하거나 추가 프로세스²가 필요하다는 점을 제외하면 기능적으로 동일합니다².
에서는 실제로 내장²과 동일 bash
합니다 .$(cat < file)
cat
cat
bash
다른 제한 사항도 있습니다. 이는 stdin 입력 파일 리디렉션에만 적용되며 $(<&3)
또는 와 같은 다른 형태의 리디렉션에는 적용되지 않습니다 $(<<<foo)
.
/dev/표준 입력
/dev/stdin
, 프로세스의 파일 설명자를 이름으로 참조할 수 있도록 1980년대 다양한 Unices에 추가된 특수 파일입니다 ./dev/stdout
/dev/stderr
/dev/fd/x
이러한 Unices에서 /dev/stdin
(문자 장치 파일)을 열면 stdin(fd 0)과 중복되는 파일 설명자가 생성되므로 3를 실행하는 것과 동일합니다 dup(0)
.
1990년대 Linux에 유사한 기능이 추가되었을 때 구현 방식은 상당히 달랐고 호환되지 않았습니다.
Linux에서 이러한 파일 /dev/std...
은 /dev/fd/x
특수 문자 장치 파일이 아니라 에 대한 기호 링크입니다 /proc/self/fd/x
.마법의 심볼릭 링크fd에서 열린 파일에엑스.
그래서 /dev/stdin
다른 것으로 열어보세요. dup(0)
그렇게 할 수 있는 권한이 있다고 가정하면 처음부터 시작하여(현재 stdin이 가리키는 파일 내의 오프셋이 아님) 요청된 모드로 원본 파일을 다시 엽니다. 이는 또한 fd 0과 독립적인 fd에서 읽기/쓰기/탐색을 수행하는 경우 파일의 stdin 오프셋이 업데이트되지 않음을 의미합니다.
Cygwin은 Linux의 접근 방식을 복사하여 2000년대에 유사한 기능을 추가했습니다. 전부는 아니지만 대부분의 다른 Unices는 원시 방식으로 실행됩니다( /dev/fd/x
완전히 지원하는 경우).
그렇다면 왜 잘못됩니까?
Linux와 Cygwin에서는 stdin에서 직접 읽는 것이 아니라 결과 파일 설명자에서 읽기 위해 $(</dev/stdin)
열고 /dev/stdin
읽는 것이 동일하지 않기 때문에 쉽게 올바른 내용을 읽지 못하거나 전혀 읽지 못하고 내용을 알려주지 못할 수 있습니다. 표준 입력을 읽은 나머지 스크립트.
다음 예를 고려하십시오.
$ cat wrong
#! /bin/bash -
var=$(</dev/stdin)
printf 'I got: "%s"\n' "$var"
printf "This is how many bytes are left to read on stdin: "
wc -c
$ cat right
#! /bin/bash -
var=$(cat)
printf 'I got: "%s"\n' "$var"
printf "This is how many bytes are left to read on stdin: "
wc -c
$ cat file
1
2
3
4
5
$
$ ./wrong < file
I got: "1
2
3
4
5"
This is how many bytes are left to read on stdin: 10
$ ./right < file
I got: "1
2
3
4
5"
This is how many bytes are left to read on stdin: 0
이 경우에도 wrong
모든 stdin 라인을 읽는 것처럼 보이지만 실제로는 그것을 소비하는 것처럼 보이지는 않습니다. wc -c
여전히 10바이트를 읽을 수 있습니다.
$ { read var; ./wrong; } < file
I got: "1
2
3
4
5"
This is how many bytes are left to read on stdin: 8
$ { read var; ./right } < file
I got: "2
3
4
5"
This is how many bytes are left to read on stdin: 0
스크립트의 표준 입력이 첫 번째 줄을 초과할 때 호출되더라도 wrong
첫 번째 줄을 가져오는 방법을 알아보세요 .file
$ socat -u file:file exec:./wrong
./wrong: line 2: /dev/stdin: No such device or address
I got: ""
This is how many bytes are left to read on stdin: 10
$ socat -u file:file exec:./right
I got: "1
2
3
4
5"
This is how many bytes are left to read on stdin: 0
wrong
/dev/stdin
소켓이기 때문에 열 수 없으며 열 수 없습니다.열려 있는()콘센트.
$ chmod 600 file
$ sudo -u other_user ./wrong < file
./wrong: line 2: /dev/stdin: Permission denied
I got: ""
This is how many bytes are left to read on stdin: 10
$ sudo -u other_user ./right < file
I got: "1
2
3
4
5"
This is how many bytes are left to read on stdin: 0
right
내가 열었던 fd 0에서 읽는 중이지만 다음 과 같이 wrong
다시 열려고 합니다 .file
다른 사용자누가 이런 일을 할 권리가 없습니다.
Linux/Cygwin에서는 파이프 및 tty와 같은 일부 문자 장치와 같이 열려 있고(소켓이 아니며 읽기 권한이 있는) 검색할 수 없는 파일을 열 $(</dev/stdin)
때와 같은 몇 가지 간단한 경우에만 작동합니다. /dev/stdin
열 수 있는 권한이 있는 검색 가능한 파일의 시작 부분에서 stdin을 여는 것과 같은 일부 다른 경우에는 다음이 발생할 수 있습니다.나타나다작동하지만 입력을 사용할 수 없습니다.
올바른 방법
위에 표시된 대로:
var=$(cat)
올바른 방법입니다⁴. cat
fd 0(stdin)에서 읽고 파이프인 fd 1에 씁니다. 그리고 쉘은 다른 쪽 끝의 출력을 읽어서 채웁니다 $var
.
cat
이 작업을 수행하는 유일한 명령은 아니지만 가장 간단하고 옵션이 전달되지 않으면 입력을 텍스트로 해석하지도 수정하지도 않습니다.
ksh93 또는 zsh에서는 var=$(<&0)
이 작업을 수행할 수 있지만( <&0
no-op로, 하나 이상의 리디렉션이 필요함) ksh93에서는 zsh
기본적으로 이 작업을 수행하므로 최적화가 아닙니다 var=$($NULLCMD <&0)
.$NULLCMD
cat
텍스트 입력(텍스트에 NUL 문자가 포함되지 않음)의 경우 zsh
또는 를 사용하여 bash
다음을 수행할 수 있습니다.
{ ! IFS= read -rd '' var; } < file
read
첫 번째 NUL 구분 기호를 읽고 구분 기호가 발견되면 성공을 반환합니다. 여기서는 구분 기호를 찾는 것을 원하지 않으므로 종료 상태를 무효화합니다. file
이는 열 수 있지만 읽을 수 없는 경우 올바른 종료 상태를 얻을 수 없음 을 의미합니다 .
추가 고려 사항
명령어 대체( $(cat)
) 및 $(<file)
연산자 삭제모두입력의 후행 개행 문자입니다. 따라서 기술적으로, 이후에는 var=$(cat)
전체 $var
입력이 포함되지 않고 전체 입력에서 후행 개행 문자를 뺀 값이 포함됩니다.
전체 입력에 대해 다음을 수행할 수 있습니다.
var=$(cat; ret=$?; echo . && exit "$ret")
ret=$? var=${var%.}
(종료 상태는 cat
그대로 유지됩니다 $ret
).
단 zsh
, 입력에 NUL 바이트가 있는 경우 $var
다른 쉘이 이러한 바이트를 변수에 저장하는 것을 지원하지 않기 때문에 보존되지 않습니다.
$ printf 'a\0b' | ksh -c 'var=$(cat); printf "Got: <%s>\n" "$var"' | sed -n l
Got: <a>$
$ printf 'a\0b' | mksh -c 'var=$(cat); printf "Got: <%s>\n" "$var"' | sed -n l
Got: <ab>$
$ printf 'a\0b' | bash -c 'var=$(cat); printf "Got: <%s>\n" "$var"' | sed -n l
bash: line 1: warning: command substitution: ignored null byte in input
Got: <ab>$
$ printf 'a\0b' | dash -c 'var=$(cat); printf "Got: <%s>\n" "$var"' | sed -n l
Got: <ab>$
$ printf 'a\0b' | zsh -c 'var=$(cat); printf "Got: <%s>\n" "$var"' | sed -n l
Got: <a\000b>$
여기서 1은 $(...)
목록 컨텍스트가 아닌 스칼라(배열 아님) 변수 할당에 사용되므로 확장 시 분할+glob이 발생하지 않습니다. 따라서 문제가 되지는 않지만 주위에 따옴표를 추가해도 $(<...)
아무런 차이가 없습니다.
² 또 다른 차이점은 최신 버전의 zsh를 제외한 모든 버전에서는 읽기 오류가 자동으로 무시된다는 것입니다. var=$(</); echo "$? <$var>"
예를 들어 오류는 보고되지 않지만 bash(ksh93 또는 mksh와 반대)는 0이 아닌 종료 상태를 반환합니다.
³ 최소한 fd가 열린 모드와 호환되는 모드에서 파일이 열리는 한. exec >/dev/stdin
예를 들어 stdin(fd 0)은 읽기 전용 모드로 열면 일반적으로 작동하지 않습니다.
⁴ 표준이지만 $(<file)
ksh/zsh/bash에서만 발견되며 /dev/stdin
모든 Unices에서는 발견되지 않습니다.