쉘 리디렉션을 사용하여 동일한 파일 설명자 읽기/쓰기

쉘 리디렉션을 사용하여 동일한 파일 설명자 읽기/쓰기

쉘 리디렉션의 맥락에서 파일 설명자를 이해하려고 합니다.

catSTDOUT에 작성된 FD3을 읽을 수 없는 이유는 무엇입니까 ?ls

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1;

이것을 시도할 때 cat여전히 키보드에서 읽고 싶습니다.

이것이 불가능하다면, 왜 안 됩니까?


차이점: 이 질문은 질문을 사용하여 동일한 파일 설명자를 읽고 쓰는 것에 관한 것입니다.임시 파일 없이 STDERR 및 STDOUT을 다른 변수로 리디렉션 예를 들어.

답변1

존재하다

{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1

fd 1을 { ... } 3>&1fd 3으로 복제합니다. 이는 fd 3이 이제 동일한 리소스(동일한 리소스)를 가리킨다는 의미입니다.파일 설명 열기) fd 1에서 지적한 바와 같습니다. 터미널에서 실행하는 경우 아마도 터미널 장치에 대해 읽기+쓰기 모드로 열린 fd일 것입니다.

그 후 exec 0<&3fds 0, 1, 3은 모두 동일한 것을 가리킵니다.파일 설명 열기(터미널 에뮬레이터가 쉘을 실행하기 전에 생성된 의사 터미널 쌍의 슬레이브 끝을 열 때 생성됩니다(위의 터미널 사례에서 명령을 실행하는 경우)).

그런 다음 변경을 수행하는 프로세스에서 out=$(cat)fd 1은 파이프의 쓰기 끝이고 0은 여전히 ​​tty 장치입니다. 따라서 터미널 장치에서 읽혀지므로 키보드에 입력하는 내용(터미널 장치가 아닌 경우 fd가 쓰기 전용 모드로 열릴 수 있으므로 오류가 발생할 수 있습니다).cat$(...)cat

stdout에 기록된 내용을 cat읽으 려면 IPC 메커니즘(예: 파이프, 소켓 쌍 또는 의사 터미널 쌍)의 두 끝인 stdout 및 stdin이 필요합니다. 예를 들어, stdout은 파이프의 쓰기 끝이고 stdin은 파이프의 읽기 끝입니다.lslscatlscat

그러나 이는 IPC(프로세스 간 통신) 메커니즘이기 때문에 차례로 실행하는 것이 아니라 동시에 ls실행 해야 합니다 .cat

파이프가 가능하기 때문에잡다일부 데이터(현재 Linux 버전에서는 기본적으로 64KiB), 두 번째 파이프를 생성하면 짧은 출력을 얻을 수 있지만 더 큰 출력의 경우 교착 상태가 발생하고 ls파이프가 완전히 시작될 때 정지되며, 뭔가가 파이프를 지우지 cat만 돌아올 때만 가능합니다 ls.

또한 yash원시 인터페이스는 하나만 있으며 stdout에서 읽을 두 번째 파이프(구성으로 생성된 stderr에 대한 또 다른 파이프 )를 pipe()만들어야 합니다 .ls$(...)

yash에서는 다음을 수행합니다.

{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4

(yash 특정 함수) 는 3>>|4fd 3에 쓰기 끝이 있고 fd 4에 읽기 끝이 있는 두 번째 파이프를 생성합니다.

그러나 stderr 출력이 파이프 크기보다 크면 중단됩니다. 우리는 파이프가 아닌 메모리 내 임시 파일로 파이프를 효과적으로 사용하고 있습니다.

실제로 파이프를 사용하려면 먼저 lsstdout을 한 파이프의 쓰기 끝으로 사용하고, stderr을 다른 파이프의 쓰기 끝으로 사용해야 합니다. 그런 다음 쉘은 동시에 이 파이프의 다른 끝(하나의 파이프가 아님)을 읽어야 합니다. 데이터가 온다. 다른 경우에는 교착 상태가 발생합니다) 두 개의 변수에 저장합니다.

데이터가 들어올 때 두 fd를 모두 읽을 수 있으려면 select()/를 poll()지원하는 쉘이 필요합니다. zsh그런 껍질인데, 그런 껍질이 없어요 yash.파이프 리디렉션기능 1, 따라서 명명된 파이프를 사용하고(따라서 생성, 권한 및 정리 관리) zselect/ 와 함께 복잡한 루프를 사용해야 합니다 sysread.

/proc/self/fd/x¹ Linux를 사용하는 경우 파이프의 동작이 명명된 파이프와 유사하다는 사실을 활용할 수 있으므로 다음을 수행할 수 있습니다.

#! /bin/zsh -
zmodload zsh/zselect
zmodload zsh/system

(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)
(){exec {we}>$1 {re}<$1} <(:)

ls -d / /x >&$wo 2>&$we &
exec {wo}>&- {we}>&-
out= err=
o_done=0 e_done=0

while ((! (o_done && e_done))) && zselect -A ready $ro $re; do
  if ((${#ready[$ro]})); then
    sysread -i $ro && out+=$REPLY || o_done=1
  fi
  if ((${#ready[$re]})); then
    sysread -i $re && err+=$REPLY || e_done=1
  fi
done

답변2

이는 변수 할당 구문의 근본적인 한계이자 쉘 생성 하위 쉘의 부작용인 것 같습니다. stderr 또는 stdout을 캡처할 수 있지만 둘 다 캡처할 수는 없습니다. 다른 스트림은 파일(아마도 FIFO)로 리디렉션되어야 합니다.

# a function for testing
your_command() { sh -c 'echo "this is stdout"; echo "this is stderr" >&2'; }

errfile=$(mktemp)
out=$( your_command 2>|"$errfile" )
err=$(< "$errfile")
rm "$errfile"

echo "out: $out"
echo "err: $err"

관련 정보