파이프와 서브셸의 리디렉션을 이해하는 데 어려움을 겪고 있음: 코드 설명을 높이 평가하겠습니다.

파이프와 서브셸의 리디렉션을 이해하는 데 어려움을 겪고 있음: 코드 설명을 높이 평가하겠습니다.

터미널 세션(Debian Buster, Bash 5.0)에서 다음 로그를 고려하세요.

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test; } | cat > result; }
root@cerberus ~/scripts # cat result
test
root@cerberus ~/scripts #

여기에는 특별한 것이 없습니다. 이는 예상되는 동작이며 이해합니다.

그러나 다음 경우의 동작을 이해하지 못합니다.

root@cerberus ~/scripts # rm -f result
root@cerberus ~/scripts # { { echo test >&3; } | cat > result; } 3>&1
test
root@cerberus ~/scripts # cat result
root@cerberus ~/scripts #

정확히 말하면 두 번째 줄을 실행할 때 "test"가 출력되는 이유는 이해하지만 결과 파일에 아무것도 없는 이유를 이해하지 못합니다. 무슨 일이 일어나고 있는지에 대한 나의 이해는 다음과 같습니다.

  1. 먼저, fd 3은 의 복사본으로 설정됩니다 stdout. 파이프라인이 실행되기 전에 이런 일이 발생한다고 확신합니다. 그렇지 않으면 파이프라인의 어떤 명령도 fd 3에 액세스할 수 없어 "잘못된 설명자" 오류 메시지가 발생하기 때문입니다.

  2. 파이프는 간단한 명령이 아니므로 이를 실행하려면 하위 쉘을 생성해야 합니다. 하위 쉘은 파일 설명자 및 리디렉션을 포함하여 상위 쉘의 실행 환경을 상속합니다.[1]

  3. 파이프라인의 각 명령은 자체 하위 셸에서도 실행됩니다.[2], 실행 환경과 파일 설명자를 다시 상속합니다. 의 출력 echo은 fd 3으로 리디렉션되고 fd 3은 이전에서 복사됩니다 stdout. 즉, 출력은 fd 3으로 이동한 다음 fd 1, 즉 stdout로 이동합니다.echostdout

  4. echo그러나 출력이 결과 파일에 포함되지 않는 이유를 이해할 수 없습니다 . ~에서배쉬 매뉴얼(강조):

파이프라인의 각 명령 출력은 다음 명령의 입력으로 파이프됩니다. 즉, 각 명령은 이전 명령의 출력을 읽습니다.이 연결은 명령으로 지정된 리디렉션 전에 수행됩니다.

내 이해는 echo출력이 cat입력 에 연결되어야한다는 것입니다앞으로>&3리디렉션을 개별적으로 설정하거나 적용합니다. 그러나 이것이 사실이라면 명령을 실행한 후에 결과 파일이 존재하고 "테스트"를 포함하게 됩니다. 그래서 내 이해는 분명히 잘못되었습니다.

누군가 내가 놓친 것을 설명해 줄 수 있습니까?

아래 AB 및 Gilles의 탁월한 답변을 바탕으로 추가 설명과 함께 업데이트

내 우려는 위의 #3에 쓴 내용에서 비롯됩니다.그러나 그것은 진실이 아니다. Giles의 답변도 참조하십시오.

AB가 가장 먼저 답변을 제공했습니다(아래 참조). 그러나 그것을 이해하는 데는 시간이 좀 걸렸습니다. 그래서 이해하기 쉽도록 몇 가지 구절을 설명하겠습니다.

  1. 줄의 마지막 부분: 3>&1먼저 완료: 터미널 출력을 가리키는 fd 1이 fd 3에 복사됩니다. 이는 fd 1과 fd 3이 모두 터미널 출력을 가리킨다는 것을 의미합니다. 그것들은 동일하며 서로 바꿔서 사용할 수 있습니다.

  2. 분기하기 전에 시스템 호출은 일반적으로 다음 사용 가능한 fd( pipe(2)fd 4 및 fd 5)에 파이프를 생성하는 데 사용됩니다. 그런 다음 준비 프로세스는 future echo 및 future cat으로 분기되어 다음 단계를 수행합니다.

    a) 준비 프로세스 echo는 다음과 같이 작동합니다.

    fd 5가 fd 1에 복사됩니다(fd 1: 터미널 출력이 가리키는 위치를 덮어씁니다). 이는 fd 1이 이제 fd 5와 동일하며 서로 바꿔서 사용할 수 있음을 의미합니다. 특히, fd 1은 더 이상 터미널 출력을 가리키지 않고 대신 파이프의 쓰기 끝을 가리킵니다. 이 단계(아래 참조)에서는 fd 1에 대한 쓰기가 해당 쓰기 끝을 가리키 므로

    출력이 echo파이프의 쓰기 끝으로 이동합니다 . 동일한 작업에 대해 두 개의 파일 설명자를 사용할 필요가 없고 어쨌든 fd 1이 기록되므로 이제 fd 5가 닫힙니다. 그런 다음 나중에 언급되는 추가 리디렉션을 설정한 후에 실행됩니다(3. 참조). b) 마찬가지로, fd 4에서 fd 0으로 준비 프로세스를 복사한다는 것은 fd 0이 더 이상 터미널 입력을 가리키지 않고 파이프의 수신 끝을 가리킨다는 것을 의미합니다. 이 단계에서는 fd 0을 읽고 fd 0이 해당 수신단에 연결되어 있으므로 입력은 파이프의 수신단에서 나옵니다 . 동일한 것을 나타내기 위해 두 개의 파일 설명자가 필요하지 않고 어쨌든 fd 0에서 읽기 때문에 fd 4는 이제 닫힙니다. 그런 다음 실행되었습니다. 이런 일이 발생하면 fd 3이 모든 곳에서 상속됩니다.echo

    echo

    echo

    catcatcatcatcat

  3. >&3글머리 기호 1의 반대: fd 3을 fd 1에 복사합니다. fd 3은 터미널 출력을 가리키도록 생성되며 파이프를 실행하는 하위 쉘과 개별 파이프 명령을 실행하는 다른 하위 쉘에 의해 상속됩니다.

    2a) 단계에서 fd 1은 파이프의 쓰기 쪽을 가리켰습니다. 그러나 이제 리디렉션은 >&3다시 fd 1을 덮어쓰고 fd 3과 동일하게 만들고, 이는 다시 (여전히) 터미널 출력을 가리킵니다. 이는 fd 1이 더 이상 파이프의 쓰기 끝을 가리키지 않고 터미널 출력을 가리킨다는 것을 의미합니다. 이것이 파이프를 실행할 때 터미널에 "test"가 나타나는 이유입니다( echofd 1이 가리키는 위치에 상관없이 항상 fd 1이 기록된다는 점을 기억하십시오).

    또한 fd 1이 리디렉션에 의해 "덮어쓰기"되면 이전 버전이 닫힙니다(기본 시스템 호출이 dup2(2)이를 수행하기 때문입니다). 이전 버전은 파이프의 쓰기 끝을 가리키므로 이제 해당 쓰기 끝이 닫힙니다.

    따라서 수신측에서는 cat어떠한 데이터도 수신하지 않습니다. 즉시 EOF 알림을 받습니다. 이것이 바로 cat아무것도 수신되지 않고 결과 파일이 비어 있거나 잘린 상태로 남아 있는 이유입니다. [참고: 위에서 언급한 것처럼 fd 1이 기록되고 fd 3에 대한 정보가 전혀 알려져 있지 않기 때문에

    리디렉션 후에 fd 3을 닫아야 합니다(즉, 을 작성해야 합니다 >&3 3>&-) . 하지만 제 예에서는 그 부분이 빠져있어서 실제 문제를 방해하지 않도록 그대로 두고 싶었습니다.) ]>&3echo

답변1

1. 먼저, fd 3은 stdout의 복사본으로 설정됩니다.

위에서 말씀드린 대로 맞는 말인데, 좀 이상하네요. 이 말의 뜻을 잘못 이해하신 것 같습니다. 이는 fd 3에 쓰는 것이 해당 리디렉션이 적용되는 동안 stdout에 쓰는 것과 동일하다는 의미는 아닙니다. 이는 fd 3이 리디렉션을 설정할 때 연결된 stdout에 연결된다는 의미입니다. 이 코드를 터미널에서 실행하는 경우 3>&1파일 설명자 3을 터미널에 연결합니다. 그래서…

3. echo(…)의 출력은 fd 3으로 리디렉션되고 fd 3은 이전에 stdout에서 복사되었습니다. 즉, 이로 인해 echo 출력이 stdout에 표시됩니다(출력은 fd 3으로 이동하고 fd 3은 fd 1로 이동). , 즉 표준 출력).

FD 3이 터미널입니다. 어느 시점에서 다른 프로세스에도 fd 1이 발생했다는 사실은 관련이 없는 역사적 세부 사항입니다.

답변2

OP의 4번째 때문에 이렇게 작동합니다.FD프로세스에 따른 다양한 생성/실행 상속. 나는 fork/exec가 일어나는 모든 장소를 쓰지 않았습니다. 나는 확실히 이 중 일부를 단순화하고 있습니다(내장 명령을 사용하여...). Linux용 문서 링크가 제공되지만 POSIX 또는 POSIX 유사 시스템에서도 동일한 동작이 발생해야 합니다.

  1. 줄의 마지막 부분: 3>&1먼저 완료:FD터미널을 가리키는 1은 다음과 같이 복사됩니다.FD3 (보통 사용dup2(2)시스템 호출).
  2. 포크하기 전에 일반적으로 다음을 사용하여 파이프라인이 생성됩니다.pipe(2)시스템 호출, 다음 번 가능FDs: 4와 5를 가정해 봅시다. 그런 다음 준비 프로세스는 future echo와 future 로 구분됩니다 cat. proto-echo는 5를 1로 복사하고(그것이 가리키는 위치인 터미널을 "덮어씁니다"), 5를 닫고 execs하고 echo, proto-cat dups2()는 4를 0으로 닫고, 4를 닫고 execs합니다 cat.FD3 모든 곳에서 상속됩니다.
  3. >&3포인트 1과 반대로: 반복됩니다.FD3(터미널을 가리킴)FD1. 따라서 파이프의 쓰기 쪽이 교체되어 이제 닫혀 있습니다(dup2(2)말한다: "파일 설명자가새로운 FD이전에 열렸으면 재사용할 때까지 자동으로 닫힙니다. "). 파이프에는 아무 것도 기록되지 않습니다. 터미널은 이를 수신 test하여 표시합니다.
  4. cat대상 파일을 병렬로 열고 자르고 result파이프에서 읽기를 시작합니다. 그러면 EOF가 실행됩니다.pipe(7)쓰기 측이 닫혀 있기 때문에 cat명령이 종료되었습니다.
  5. 기본 셸 프로세스에 남은 하위 프로세스가 없습니다. 실행이 종료됩니다.

결과: test터미널에 result파일이 비어 있습니다.

관련 정보