셸은 어떤 순서로 명령과 스트림 리디렉션을 실행합니까?


32

나는 모두를 재려고 stdout하고 stderr파일에 오늘, 나는이 건너 온 :

<command> > file.txt 2>&1

이것은 분명히 리디렉션 stderrstdout먼저 한 다음 결과는 stdout로 리디렉션됩니다 file.txt.

그러나 왜 주문이 <command> 2>&1 > file.txt아닌가? 자연스럽게 이것을 (왼쪽에서 오른쪽으로 실행한다고 가정) 처음 실행되는 명령 stderr으로 리디렉션 된 stdout다음 결과 stdout가로 쓰여집니다 file.txt. 그러나 위의 stderr내용은 화면으로 만 리디렉션 됩니다.

쉘은 두 명령을 어떻게 해석합니까?


7
TLCL은 "먼저 표준 출력을 파일로 리디렉션 한 다음 파일 설명자 2 (표준 오류)를 파일 설명자 1 (표준 출력)로 리디렉션합니다"및 "리디렉션 순서가 중요하다는 것을 알 수 있습니다. 표준 오류의 리디렉션 표준 출력을 리디렉션 한 후에 항상 발생해야합니다. 그렇지 않으면 작동하지 않습니다 "
Zanna

@ 잔나 : 그렇습니다. 제 질문은 TLCL에서 정확하게 읽었습니다! :) 왜 작동하지 않는지, 즉 쉘이 일반적으로 명령을 해석하는 방법을 알고 싶습니다.
Train Heartnet

글쎄, 당신은 당신의 반대라고 말합니다. "이것은 stderr을 stdout으로 먼저 리디렉션합니다 ..."-TLCL에서 이해하는 것은 쉘이 stdout을 파일로 보낸 다음 stderr를 stdout (즉, 파일)로 보낸다는 것 입니다. 내 해석은 stderr을 stdout으로 보내면 터미널에 표시되고 stdout의 후속 리디렉션에는 stderr이 포함되지 않습니다 (즉 stdout의 리디렉션이 발생하기 전에 stderr의 화면으로의 리디렉션이 완료됩니까?)
Zanna

7
나는 이것이 구식이라고 알고 있지만 , 쉘에는 이러한 것들을 설명하는 매뉴얼이 있습니다 - 매뉴얼의 리디렉션bash . 그건 그렇고, 리디렉션은 명령이 아닙니다.
Reinier Post

경로 재 지정이 설정되기 전에 명령 실행할 수 없습니다 . execv-family syscall을 호출하여 실제로 서브 프로세스를 시작중인 명령으로 넘기면 쉘은 루프에서 벗어납니다 (더 이상 해당 프로세스에서 코드를 실행하지 않습니다) ) 해당 시점부터 발생하는 상황을 제어 할 수있는 방법이 없습니다. 따라서 셸은 명령을 실행 하는 프로세스에서 셸 자체의 복사본이 실행되는 동안 리디렉션을 모두 수행 해야합니다fork() .
Charles Duffy

답변:


41

<command> 2>&1 > file.txtstderr 을 실행하면 2>&1stdout 이 현재가는 곳으로 리디렉션됩니다 . 그 후 stdout은로 파일로 리디렉션 >되지만 stderr는 리디렉션되지 않으므로 터미널 출력으로 유지됩니다.

<command> > file.txt 2>&1stdout을 사용하면 먼저로 파일 >2>&1리디렉션 한 다음 stderr을 stdout이 진행되는 위치 ( 파일 )로 리디렉션합니다.

처음에는 반 직관적 인 것처럼 보일 수 있지만 이러한 방식으로 리디렉션을 생각할 때 왼쪽에서 오른쪽으로 처리되는 것이 훨씬 더 의미가 있음을 기억하십시오.


파일 설명자와 "dup / fdreopen"호출을 왼쪽에서 오른쪽으로 순서대로 생각하면 이치에 맞습니다
Mark K Cowan

20

추적하면 이해가 될 것입니다.

처음에는 stderr과 stdout이 같은 것으로 이동합니다 (보통 터미널이라고 부릅니다 pts).

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

여기 에서는 파일 설명자 번호 로 stdin, stdout 및 stderr을 참조합니다 . 각각 파일 설명자 0, 1 및 2입니다.

이제, 리디렉션의 첫 번째 세트에, 우리가 > file.txt하고 2>&1.

그래서:

  1. > file.txt: fd/1이제로갑니다 file.txt. 로 >, 1이 그래서 아무 것도 지정되지 않은 묵시적 파일 기술자입니다 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2지금 어디가는 fd/1 현재 이동 :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

반면 2>&1 > file.txt에을 사용하면 순서가 반대로됩니다.

  1. 2>&1: fd/2이제 fd/1현재 어디로 가든지 , 아무 변화도 없습니다 :

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1지금 간다 file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

중요한 점은 경로 재 지정이 경로 재 지정된 파일 디스크립터가 목표 파일 디스크립터에 대한 모든 향후 변경 사항을 따를 것이라는 것을 의미하지는 않습니다. 현재 상태 만 취합니다 .


감사합니다.보다 자연스러운 설명처럼 보입니다! :) 2.두 번째 부분을 약간 오타로 만들었습니다 . fd/1 -> file.txt그리고 아닙니다 fd/2 -> file.txt.
Train Heartnet

11

쉘이 왼쪽에 리디렉션을 먼저 설정하고 다음 리디렉션을 설정하기 전에 완료 한다고 생각하면 도움이된다고 생각합니다 .

윌리엄 쇼츠 (William Shotts) 의 리눅스 커맨드 라인

먼저 표준 출력을 파일로 리디렉션 한 다음 파일 설명자 2 (표준 오류)를 파일 설명자 1 (표준 출력)로 리디렉션합니다.

이것은 말이되지만

리디렉션 순서가 중요합니다. 표준 출력 리디렉션은 표준 출력 리디렉션 후 항상 발생해야합니다. 그렇지 않으면 작동하지 않습니다.

그러나 실제로 stderr을 동일한 효과로 파일로 리디렉션 한 후 stdout을 stderr로 리디렉션 할 수 있습니다

$ uname -r 2>/dev/null 1>&2
$ 

따라서에서 command > file 2>&1쉘은 stdout을 파일로 보낸 다음 stderr을 stdout (파일로 전송)로 보냅니다. 에 반해 command 2>&1 > file쉘 제 리디렉션 이후 표준 출력 후 (즉, 그 디스플레이 표준 출력 정상적으로 이동 단말에서)과 표준 에러 리다이렉션 파일을 표준 출력. TLCL은 stdout을 먼저 리디렉션해야한다고 잘못 지적하고 있습니다 .stderr을 먼저 파일로 리디렉션 한 다음 stdout을 보낼 수 있기 때문입니다. 우리가 할 수없는 것은 파일로 리디렉션하기 전에 stdout을 stderr로 리디렉션하거나 그 반대로 리디렉션하는 것입니다. 또 다른 예

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

우리는 이것이 stdout을 stderr과 같은 장소에 처분 할 것이라고 생각할 수도 있지만, stdout을 stderr (화면)로 먼저 리디렉션 한 다음 stderr을 다른 방향으로 시도했을 때만 리디렉션합니다 ...

나는 이것이 약간의 빛을 가져 오기를 바랍니다 ...


훨씬 더 설득력이 있습니다!
Arronical

아, 이제 이해합니다! @Zanna와 @Arronical 감사합니다! 명령 줄 여행을 시작했습니다. :)
Train Heartnet

@TrainHeartnet 그것은 기쁨입니다! 당신이 나와 같이 그것을 즐기기를 바랍니다 : D
Zanna

@ 잔나 : 사실, 나야! : D
Train Heartnet

2
@TrainHeartnet 걱정하지 마세요, 좌절과 기쁨의 세계가 당신을 기다립니다!
Arronical

10

당신은 이미 아주 좋은 답변을 받았습니다. 여기에 관련된 두 가지 다른 개념이 있다는 것을 강조하겠습니다.

배경 : 파일 디스크립터 대 파일 테이블

파일 디스크립터는 프로세스의 파일 디스크립터 테이블에있는 인덱스 인 숫자 0 ... n입니다. 관습에 따라 STDIN = 0, STDOUT = 1, STDERR = 2 ( STDIN여기서 용어 등은 일부 프로그래밍 언어 및 매뉴얼 페이지에서 관례에 의해 사용되는 기호 / 매크로 임), STDIN이라는 실제 "객체"는 없습니다. 이 논의의 목적에서 STDIN 0 등입니다.)

해당 파일 디스크립터 테이블 자체에는 실제 파일에 대한 정보가 포함되어 있지 않습니다. 대신 다른 파일 테이블에 대한 포인터를 포함합니다. 후자는 실제 실제 파일 (또는 블록 장치 또는 파이프 또는 Linux가 파일 메커니즘을 통해 처리 할 수있는 모든 것)에 대한 정보와 추가 정보 (즉, 읽기 또는 쓰기 용 여부)를 포함합니다.

따라서 셸에서 >또는 <셸 을 사용 하면 해당 파일 디스크립터의 포인터를 다른 것으로 가리 키기 만하면됩니다. 이 구문은 2>&1단순히 설명자 2를 1이있는 곳을 가리 킵니다. > file.txt단순히 file.txt쓰기 위해 열리고 STDOUT (파일 설명자 1)이이를 가리 키도록합니다.

다른 장점이 있습니다 2>(xxx) (예 : 실행중인 새 프로세스 xxx만들기, 파이프 만들기, 새 프로세스의 파일 설명자 0을 파이프의 읽기 끝에 연결하고 원래 프로세스의 파일 설명자 2를 파이프).

이것은 또한 쉘 이외의 다른 소프트웨어에서 "파일 핸들 매직"의 기초입니다. 예를 들어, Perl 스크립트 dup에서 STDOUT 파일 디스크립터를 다른 (임시) 파일 디스크립터에 연결 한 후 STDOUT을 새로 작성된 임시 파일로 다시 열 수 있습니다. 이 시점부터는 자신의 Perl 스크립트 system() 해당 스크립트의 모든 호출 에서 나온 모든 STDOUT 출력 이 해당 임시 파일에있게됩니다. 완료되면 dupSTDOUT을 저장 한 임시 디스크립터로 재 저장할 수 있으며 이전과 동일합니다. 그 동안 임시 디스크립터에 쓸 수도 있으므로 실제 STDOUT 출력이 임시 파일로 이동하는 동안 실제 STDOUT (일반적으로 사용자)으로 실제로 출력 할 수 있습니다 .

대답

위에 주어진 배경 정보를 질문에 적용하려면 :

셸은 어떤 순서로 명령과 스트림 리디렉션을 실행합니까?

좌에서 우로.

<command> > file.txt 2>&1

  1. fork 새로운 과정에서
  2. file.txt파일 디스크립터 1 (STDOUT)에서 포인터를 열고 저장하십시오.
  3. STDERR (파일 설명자 2)을 fd 1이 지금 가리키는 모든 것을 가리 킵니다 (이것은 file.txt물론 이미 열려 있습니다).
  4. exec 그만큼 <command>

이것은 stderr을 stdout으로 먼저 리디렉션 한 다음 결과 stdout이 file.txt로 리디렉션됩니다.

단지이 있다면이 나을 하나 개의 테이블 만으로는 두 가지가 있습니다 위의 설명했다. 파일 디스크립터가 재귀 적으로 서로를 가리 키지 않으므로 "STDERR을 STDOUT으로 경로 재 지정"하는 것은 의미가 없습니다. 올바른 생각은 "STDERR이 STDOUT이 가리키는 곳을 가리 킵니다"입니다. 나중에 STDOUT을 변경하면 STDERR은 그대로 유지되며 STDOUT에 대한 추가 변경과 함께 마술처럼 진행되지 않습니다.


"file handle magic"비트에 대한 찬성 – 그것이 오늘 새로운 것을 배운 질문에 직접 대답하지는 않지만 ...
Floris

3

순서는 왼쪽에서 오른쪽입니다. Bash 매뉴얼은 이미 당신이 요구하는 것을 다뤘습니다. REDIRECTION매뉴얼 섹션 에서 인용 :

   Redirections  are  processed  in  the
   order they appear, from left to right.

그리고 몇 줄 후에 :

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

명령을 실행하기 전에 먼저 리디렉션이 해결된다는 점에 유의해야합니다. 참조 https://askubuntu.com/a/728386/295286를


3

항상 왼쪽에서 오른쪽으로 ... 때를 제외하고

수학에서와 같이 괄호 (+-) 안의 연산이 곱셈과 나눗셈 전에 수행되는 것을 제외하고는 곱셈과 나눗셈이 덧셈과 뺄셈 전에 수행되는 것을 제외하고 왼쪽에서 오른쪽으로 수행합니다.

여기 Bash 초보자 가이드 ( Bash Beginners Guide )에 따라 8 가지 순서의 계층 구조가 있습니다 (왼쪽에서 오른쪽으로).

  1. 중괄호 확장 "{}"
  2. 틸드 확장 "~"
  3. 쉘 매개 변수 및 변수 표현식 "$"
  4. 명령 대체 "$ (command)"
  5. 산술 식 "$ ((EXPRESSION))"
  6. 프로세스 대체 여기서 말하는 것은 "<(LIST)"또는 "> (LIST)"
  7. 단어 분리 " '<공간> <탭> <개행>'"
  8. 파일 이름 확장명 "*", "?"등

따라서 항상 왼쪽에서 오른쪽으로 유지됩니다.


1
명확하게 말하면 : 프로세스 대체 및 리디렉션은 두 개의 독립적 인 작업입니다. 다른 것없이 하나를 할 수 있습니다. 프로세스 대체가 bash가 경로 재 지정을 처리하는 순서를 변경한다는 것을 의미하지는 않습니다
muru
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.