파이프에서 쉘 변수로 값 읽기


205

파이프에 들어가는 stdin의 데이터를 bash에 처리하려고하지만 운이 없습니다. 의미하는 바는 다음과 같은 일이 아닙니다.

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

출력을 원합니다 test=hello world. ""따옴표를 넣어도 "$test"작동하지 않습니다.


1
예 .. echo "hello world"| 읽기 시험; echo test = $ test가 잘 작동했습니다. 결과 : test = hello world; 어떤 환경에서 이것을 실행하고 있습니까? bash 4.2를 사용하고 있습니다.
alex.pilon

한 번의 읽기로 여러 줄을 원하십니까? 귀하의 예는 한 줄만 표시하지만 문제 설명은 명확하지 않습니다.
Charles Duffy

2
@ alex.pilon, Bash 버전 4.2.25를 실행 중이며 그의 예제도 나에게 적합하지 않습니다. Bash 런타임 옵션 또는 환경 변수의 문제 일 수 있습니까? 나는 예제가 Sh와 함께 작동하지 않으므로 Bash가 Sh와 호환되도록 시도 할 수 있습니까?
Hibou57

2
@ Hibou57-bash 4.3.25에서 다시 시도했지만 더 이상 작동하지 않습니다. 내 기억은 이것에 대해 희미하고 그것이 작동하기 위해 무엇을했는지 모르겠습니다.
alex.pilon

2
@ Hibou57 @ 파이프의 마지막 cmd를 영향을해야 alex.pilon는 bash4에 바르> = 4.2 shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
IMZ - 이반 Zakharyaschev

답변:


162

사용하다

IFS= read var << EOF
$(foo)
EOF

다음 과 같이 파이프에서 수락하도록 속일 수 있습니다read .

echo "hello world" | { read test; echo test=$test; }

또는 다음과 같은 함수를 작성하십시오.

read_from_pipe() { read "$@" <&0; }

그러나 아무 의미가 없습니다-변수 할당이 지속되지 않을 수도 있습니다! 파이프 라인은 환경이 참조가 아닌 값으로 상속되는 서브 쉘을 생성 할 수 있습니다. 이것이 read파이프의 입력을 방해하지 않는 이유입니다 -정의되지 않았습니다.

참고로, http://www.etalabs.net/sh_tricks.html 은 bourne shells의 이상과 비 호환성과 싸우는 데 필요한 cruft 모음입니다.


대신 다음을 수행하여 과제를 마지막에 지정할 수 있습니다.`test =``echo "hello world"| {읽기 테스트; 에코 $ 테스트; }```
Compholio

2
다시 시도해 봅시다 (이 마크 업에서 백틱을 피하는 것이 재미 있습니다) :test=`echo "hello world" | { read test; echo $test; }`
Compholio

두 명령을 그룹화하는 {}대신 왜 사용했는지 물어볼 수 ()있습니까?
Jürgen Paul

4
트릭은 read파이프에서 입력을받는 것이 아니라을 실행하는 동일한 쉘에서 변수를 사용하는 것 read입니다.
chepner

1
있어 bash permission denied이 솔루션을 사용하려고 할 때. 내 경우는 상당히 다르지만 어디에서나 답을 찾을 수 없었습니다. 나에게 도움이 된 것은 (다른 예제이지만 비슷한 사용법) pip install -U echo $(ls -t *.py | head -1)입니다. 경우에 따라 누군가 비슷한 문제가 발생하여 나와 같은 대답을 우연히 발견합니다.
ivan_bilan

111

많은 데이터를 읽고 각 줄에서 별도로 작업하려면 다음과 같이 사용할 수 있습니다.

cat myFile | while read x ; do echo $x ; done

줄을 여러 단어로 나누려면 x 대신 다음과 같이 여러 변수를 사용할 수 있습니다.

cat myFile | while read x y ; do echo $y $x ; done

대안 적으로 :

while read x y ; do echo $y $x ; done < myFile

그러나 이런 종류의 일로 정말 영리한 일을 시작하기 시작하면 펄과 같은 스크립트 언어를 사용하는 것이 좋습니다.

perl -ane 'print "$F[0]\n"' < myFile

펄에는 상당히 가파른 학습 곡선이 있지만 (또는이 언어들 중 하나를 추측합니다) 가장 간단한 스크립트 이외의 것을하고 싶다면 장기적으로 훨씬 더 쉽다는 것을 알 수 있습니다. Perl Cookbook과 Larry Wall et al.의 The Perl Programming Language를 추천합니다.


8
"대안 적으로"는 올바른 방법입니다. UUoC 및 서브 쉘이 없습니다. BashFAQ / 024를 참조하십시오 .
추후 공지가있을 때까지 일시 중지되었습니다.

43

read파이프에서 읽지 않습니다 (또는 파이프가 서브 쉘을 작성하기 때문에 결과가 손실 될 수 있음). 그러나 Bash에서 here 문자열을 사용할 수 있습니다.

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

그러나에 대한 정보는 @chepner의 답변을 참조하십시오 lastpipe.


멋지고 간단한 원 라이너, 이해하기 쉽습니다. 이 답변에는 더 많은 투표가 필요합니다.
David Parks 19

42

이것은 또 다른 옵션입니다

$ read test < <(echo hello world)

$ echo $test
hello world

24
중요한 장점 <(..)이상 가지고는 $(..)<(..)수익률이 실행하는 명령이 사용할 수 있도록 자마자 호출자에게 각 라인. $(..)그러나 호출자가 출력을 사용할 수있게하려면 명령이 완료 될 때까지 기다렸다가 모든 출력을 생성하십시오.
Derek Mahar

37

Bash의 전문가는 아니지만 이것이 왜 제안되지 않았는지 궁금합니다.

stdin=$(cat)

echo "$stdin"

그것이 나를 위해 일한다는 하나의 라이너 증거 :

$ fortune | eval 'stdin=$(cat); echo "$stdin"'

4
"read"는 bash 명령이고 cat은 하위 프로세스에서 시작되는 별도의 바이너리이므로 효율성이 떨어집니다.
dj_segfault

10
때로는 단순성과 선명도 효율성이 뛰어남 :)
Rondo

5
가장 간단한 답변
drwatsoncode

내가 이것에 부딪친 문제는 파이프를 사용하지 않으면 스크립트가 중단된다는 것입니다.
데일 앤더슨

물론 @DaleA. 표준 입력에서 읽으려고하는 프로그램은 아무것도 입력되지 않으면 "중지"됩니다.
djanowski

27

bash4.2 lastpipe에는 서브 쉘이 아닌 현재 쉘의 파이프 라인에서 마지막 명령을 실행하여 코드가 작성된대로 작동 할 수 있는 옵션이 도입되었습니다 .

shopt -s lastpipe
echo "hello world" | read test; echo test=$test

2
아! 너무 좋아요 대화식 쉘에서 테스트하는 경우 : "set + m"(. sh 스크립트에는 필요하지 않음)
XXL

14

쉘 명령에서 bash 변수로의 암시 적 파이프의 구문은 다음과 같습니다.

var=$(command)

또는

var=`command`

귀하의 예에서는 입력을 기대하지 않는 할당 문으로 데이터를 파이프합니다.


4
$ ()는 쉽게 중첩 될 수 있기 때문입니다. JAVA_DIR = $ (dirname $ (readlink -f $ (which java)))를 생각하고`로 시도하십시오. 당신은 세 번 탈출해야합니다!
albfan

8

첫 번째 시도는 꽤 가까웠다. 이 변형은 다음과 같이 작동합니다.

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

출력은 다음과 같습니다.

test = hello world

테스트 후 할당과 에코를 묶으려면 파이프 뒤에 중괄호가 필요합니다.

중괄호가 없으면 테스트 후 할당 (파이프 후)이 하나의 쉘에 있고 에코 "test = $ test"는 해당 할당에 대해 모르는 별도의 쉘에 있습니다. 그렇기 때문에 "test = hello world"대신 "test ="가 출력됩니다.


8

내 눈에는 bash에서 stdin을 읽는 가장 좋은 방법은 다음 방법으로 입력이 끝나기 전에 줄에서 작업 할 수 있습니다.

while read LINE; do
    echo $LINE
done < /dev/stdin

1
나는 이것을 찾기 전에 거의 미쳤다. 공유해 주셔서 감사합니다!
Samy Dindane

6

넘어지기 때문에 메모를 남기고 싶습니다. POSIX와 호환되도록 이전 sh 스크립트를 다시 작성해야하므로이 스레드를 찾았습니다. 이것은 기본적으로 다음과 같은 코드를 다시 작성하여 POSIX에 의해 도입 된 파이프 / 서브 쉘 문제를 피하는 것을 의미합니다.

some_command | read a b c

으로:

read a b c << EOF
$(some_command)
EOF

그리고 이와 같은 코드 :

some_command |
while read a b c; do
    # something
done

으로:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

그러나 후자는 빈 입력에서 동일하게 작동하지 않습니다. 이전 표기법으로 while 루프는 빈 입력에 입력되지 않지만 POSIX 표기법에서는! EOF 이전의 줄 바꿈으로 인해 생략 될 수 있다고 생각합니다. 이전 표기법과 비슷하게 동작하는 POSIX 코드는 다음과 같습니다.

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

대부분의 경우이 정도면 충분합니다. 그러나 불행히도 some_command가 빈 줄을 인쇄하면 여전히 이전 표기법과 정확히 동일하게 작동하지 않습니다. 오래된 표기법에서 while 본문이 실행되고 POSIX 표기법에서 본문 앞에서 끊어집니다.

이 문제를 해결하는 방법은 다음과 같습니다.

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF

5

PIPE 및 명령 줄 인수에서 데이터를 읽을 수있는 스마트 스크립트 :

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

산출:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

설명 : 스크립트가 파이프를 통해 데이터를 수신하면 stdin / proc / self / fd / 0은 파이프에 대한 심볼릭 링크가됩니다.

/proc/self/fd/0 -> pipe:[155938]

그렇지 않으면 현재 터미널을 가리 킵니다.

/proc/self/fd/0 -> /dev/pts/5

배쉬 [[ -p 옵션은 파이프인지 여부를 확인할 수 있습니다.

cat - 에서 읽습니다 stdin .

우리 cat -가 없을 때 사용 하면 stdin영원히 기다릴 것입니다. 그래서 우리는 그것을 if조건 안에 넣습니다 .


또한 /dev/stdin다음 링크를 사용할 수도 있습니다/proc/self/fd/0
Elie G.

3

과제와 관련된 표현에 무언가를 들여다 보는 것은 그렇게 행동하지 않습니다.

대신 다음을 시도하십시오.

test=$(echo "hello world"); echo test=$test

2

다음 코드 :

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

작동하지만 파이프 다음에 또 다른 새로운 하위 쉘이 열립니다.

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

습관.


chepnars '방법 을 사용하기 위해 작업 제어를 비활성화해야했습니다 (터미널 에서이 명령을 실행 중이었습니다).

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

배쉬 매뉴얼은 말한다 :

마지막 파이프

설정되고 작업 제어가 활성화되지 않은 경우, 쉘은 현재 쉘 환경에서 백그라운드에서 실행되지 않은 파이프 라인의 마지막 명령을 실행합니다.

참고 : 비 대화식 쉘에서는 작업 제어가 기본적으로 해제되어 있으므로 set +m스크립트 내부가 필요하지 않습니다 .


1

stdin에서 입력을받을 수있는 쉘 스크립트를 작성하려고했다고 생각합니다. 그러나 인라인으로 시도하는 동안 test = 변수를 만들려고하지 못했습니다. 인라인으로하는 것이별로 의미가 없다고 생각하므로 예상대로 작동하지 않습니다.

나는 줄이려고 노력했다

$( ... | head -n $X | tail -n 1 )

다양한 입력에서 특정 라인을 가져옵니다. 그래서 입력 할 수 있습니다 ...

cat program_file.c | line 34

stdin에서 읽을 수있는 작은 쉘 프로그램이 필요합니다. 당신처럼.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

당신은 간다.


-1

이건 어때요:

echo "hello world" | echo test=$(cat)

기본적으로 아래 답변에서 속인 것입니다.
djanowski

어쩌면, 나는 내 것이 약간 깨끗하고 원래 질문에 게시 된 코드에 더 가깝다고 주장 할 것입니다.
bingles
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.