Unix (또는 Windows)에서 (이름없는) 파이프를 사용하여 한 프로세스의 표준 출력을 여러 프로세스로 어떻게 보낼 수 있습니까?


79

proc1 프로세스의 stdout을 두 프로세스 proc2 및 proc3으로 리디렉션하고 싶습니다.

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

나는 시도했다

 proc1 | (proc2 & proc3)

하지만 작동하지 않는 것 같습니다.

 echo 123 | (tr 1 a & tr 1 b)

쓰다

 b23

대신 stdout에

 a23
 b23

답변:


125

편집자 주 :
- >(…)A는 공정 대체 A는 비표준 쉘 기능일부 POSIX 호환 쉘 : bash, ksh, zsh.
-이 대답은 실수로 파이프 라인을 통해 출력 프로세스 대체의 출력을 전송 너무 : echo 123 | tee >(tr 1 a) | tr 1 b.
-프로세스 대체의 출력은 예측할 수없이 인터리브되며,에서를 제외하고 zsh내부 명령이 >(…)수행 되기 전에 파이프 라인이 종료 될 수 있습니다 .

Unix (또는 Mac)에서는 다음 tee명령을 사용합니다 .

$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23

일반적으로 tee출력을 여러 파일로 리디렉션 하는 데 사용 하지만> (...)를 사용하면 다른 프로세스로 리디렉션 할 수 있습니다. 따라서 일반적으로

$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null

당신이 원하는 것을 할 것입니다.

창 아래에서는 내장 셸이 동등하다고 생각하지 않습니다. Microsoft의 Windows PowerShell 에는 tee명령이 있습니다.


4
이것은 POSIX 구조가 아니며 bash 또는 ksh가 필요합니다. tcsh 및 dash 등으로 운이
좋지 않습니다

2
@pixelbeat :… 그러나 POSIX 구조로 나눌 수 있습니다 (내 답변 참조 :)
tzot

12
이것은 @secr이 요청한 것을 정확히 수행하지 않습니다. 파이프를 통해 보내기 전에 tee프로세스 리디렉션의 출력을 추가합니다 stdout. 이는 동일한 인스턴스 stdout를 여러 명령에 파이프하는 것과는 확연히 다릅니다 . 예를 들어 @dF 는 원래 질문의 맥락에서 의미 echo 123 | tee >(tr 1 a) | tr 2 b가 없는를 초래 1b3 ab3합니다.
Dejay Clayton 2011

8
매우 편리하지만> (...) 내부에서 시작된 명령은 원래 셸에서 분리되어 완료시기를 쉽게 결정할 수 없습니다. 티는 모든 것을 작성 후 완료되지만, 치환 과정은 여전히 커널 및 파일 I / O의 다양한 버퍼의 데이터를 소모한다, 플러스 어떤 시간 데이터의 내부 처리에 의해 수행된다. 외부 쉘이 하위 프로세스에서 생성 된 모든 것에 계속 의존하면 경쟁 조건이 발생할 수 있습니다.
jmb

3
@Dejay Clayton :을 사용하여 원래 입력을 버릴 수 있습니다 inproc | tee >(outproc1) >(outproc2) > /dev/null | outproc. outproc은 outproc1 및 outproc2에 의해 생성 된 출력 만 볼 수 있습니다. 원래 입력은 '사라짐'입니다.
ack

22

dF가 말했듯 이 파일 이름 대신 명령을 실행하는 구성 bash을 사용할 수 있습니다 >(…). ( 파일 이름 대신 다른 명령 <(…)출력 을 대체 하는 구조 도 있지만 지금은 관련이 없으며 완전성을 위해 언급합니다).

bash가 없거나 이전 버전의 bash가있는 시스템에서 실행중인 경우 FIFO 파일을 사용하여 bash가 수행하는 작업을 수동으로 수행 할 수 있습니다.

원하는 것을 달성하는 일반적인 방법은 다음과 같습니다.

  • 명령의 출력을 수신해야하는 프로세스 수를 결정하고 가능한 한 전역 임시 폴더에 FIFO를 생성합니다.
    subprocesses = "abc d"
    mypid = $$
    for i in $ subprocesses # 이런 식으로 우리는 모든 sh 파생 쉘과 호환됩니다.  
    하다
        mkfifo /tmp/pipe.$mypid.$i
    끝난
  • FIFO에서 입력을 기다리는 모든 하위 프로세스를 시작합니다.
    $ 하위 프로세스의 i를 위해
    하다
        tr 1 $ i </tmp/pipe.$mypid.$i & # 배경!
    끝난
  • FIFO에 대한 명령 티잉을 실행하십시오.
    proc1 | tee $ (for i in $ subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • 마지막으로 FIFO를 제거합니다.
    나는 $ 하위 프로세스에서; rm /tmp/pipe.$mypid.$i; 끝난

참고 : 호환성을 위해 $(…)역 따옴표로 작업을 수행 하지만이 답변을 작성할 수는 없습니다 (역 따옴표는 SO에서 사용됨). 일반적으로 $(…)는 이전 버전의 ksh에서도 작동하기에 충분히 오래되었지만 그렇지 않은 경우 부분을 ​​역 따옴표로 묶으십시오 .


1
++을 사용하는 mkfifo것이 좋지만 mknod, 전자 만 POSIX를 준수하기 때문에 대신 을 사용해야 합니다. 또한 인용되지 않은 명령 대체를 사용하는 것은 깨지기 쉬우 며 효율성을 위해 globbing을 사용할 가능성이 있습니다. 나는 bash내 대답에서 더 강력한 솔루션을 구현할 자유를 얻었습니다 . 참고 $(…)내가 멀리 예측하기에서 벗어나 겠어 지금 오랫동안 POSIX의 일부가되었습니다 `…`SO 확실히 (그리고의 사용을 허용 `코드 블록에, 심지어 인라인 코드 스팬 (적어도 지금 :)).
mklement0

읽기 쪽 프로세스 중 하나가 소비를 중지하면 쓰기 쪽이 차단되는 것 같습니다 (예 : 시작 실패, 사망 등). 솔루션에 필요한 복원력에 대해 생각할 때 고려해야 할 사항입니다.
Russell Speight

9

유닉스 ( bash, ksh, zsh)

. DF의 대답은 포함 씨앗 을 기반으로 답변 tee출력 프로세스 대체를
( >(...)) 그 수도 있고 수도없는 일, 당신의 요구 사항에 따라 :

프로세스 대체는 (대부분) ( 예를 들어 Ubuntu에서 작동하는 ) POSIX 기능 전용 셸이 지원 하지 않는 비표준 기능입니다 . 셸 스크립트 대상 은이 스크립트에 의존 해서는 안됩니다 .dash/bin/sh/bin/sh

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

이 접근 방식 의 함정 은 다음과 같습니다.

  • 예측할 수없는 비동기 출력 동작 : 출력 프로세스 대체 내부 명령의 출력 스트림 >(...)이 예측할 수없는 방식으로 인터리브됩니다.

  • In bashand ksh(반대 zsh-그러나 아래 예외 참조) :

    • 명령이 완료된 후에 출력이 도착할 수 있습니다 .
    • 후속 명령이 실행을 시작할 수 있습니다 전에 프로세스 대체의 명령이 완료 - bash그리고 ksh않습니다 하지 적어도 기본적으로, 마무리로 출력 프로세스 대체 스폰 프로세스를 기다립니다.
    • jmb 는 dF의 답변에 대한 의견에 잘 설명합니다.

내부 >(...)에서 시작된 명령 은 원래 셸과 분리되어 있으며 언제 완료되는지 쉽게 확인할 수 없습니다. 은 tee모든 것을 작성 후 완료되지만, 치환 과정은 여전히 커널 및 파일 I / O의 다양한 버퍼의 데이터를 소모한다, 플러스 어떤 시간 데이터의 내부 처리에 의해 수행된다. 외부 쉘이 하위 프로세스에서 생성 된 모든 것에 계속 의존하면 경쟁 조건이 발생할 수 있습니다.

  • zsh유일한 셸 않는 프로세스가 끝까지 출력 프로세스 대체 실행을위한 기본 대기에 의해 , 를 제외하고 이 경우 표준 에러 (하나에 리디렉션됩니다 2> >(...)).

  • ksh(적어도 버전 현재 93u+) wait출력 프로세스 대체 생성 프로세스가 완료 될 때까지 기다릴 인수없는 사용을 허용합니다 . 그러나
    대기중인 백그라운드 작업 을 기다리는 결과를 초래할 수있는 대화식 세션에서도 유의하십시오 .

  • bash v4.4+를 사용하여 가장 최근에 실행 된 출력 프로세스 대체를 기다릴 수 wait $!있지만 인수없는 기능 wait은 작동 하지 않으므로 여러 출력 프로세스 대체가 있는 명령에 적합 하지 않습니다 .

  • 그러나 bashksh강제 기다려야 에 명령을 파이프하여 | cat이것이 실행 명령한다,하지만 노트 서브 쉘을 . 주의 사항 :

    • ksh(현재 ksh 93u+) stderr 을 출력 프로세스 대체로 보내는 것을 지원하지 않습니다 ( 2> >(...)); 그러한 시도는 조용히 무시 됩니다.

    • 기본적 으로 (훨씬 더 일반적인) stdout 출력 프로세스 대체 와 zsh(권장 할 만하게) 동기식 이지만 기술 조차도 stderr 출력 프로세스 대체 ( ) 와 동기식으로 만들 수 없습니다 .| cat2> >(...)

  • 그러나 동기 실행 을 보장하더라도 예기치 않게 인터리브 출력 문제가 남아 있습니다.

bash또는 ksh에서 실행될 때 다음 명령 은 문제가있는 동작을 보여줍니다 ( 증상을 모두 보려면 여러 번 실행해야 할 수 있음 ). AFTER일반적으로 는 출력 대체에서 출력 되기 전에 인쇄 되며 후자의 출력은 예측할 수없이 인터리브 될 수 있습니다.

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

간단히 말해서 :

  • 특정 명령 별 출력 시퀀스 보장 :

    • 그것도 지원 bash하지도 ksh않습니다 zsh.
  • 동기 실행 :

    • stderr 소스 출력 프로세스 대체를 제외하고는 가능합니다.
      • 에서 zsh, 그들은있어 변함없이 비동기.
      • 에서 ksh, 그들은 전혀 작동하지 않습니다 .

이러한 제한을 감수 할 수 있다면 출력 프로세스 대체를 사용하는 것이 실행 가능한 옵션입니다 (예 : 모두가 별도의 출력 파일에 기록하는 경우).


참고 tzot의 훨씬 더의 복잡하지만, 잠재적으로 POSIX 호환 솔루션은 또한 예측할 수없는 출력 동작을 보여 ; 그러나를 사용 wait하면 모든 백그라운드 프로세스가 완료 될 때까지 후속 명령이 실행을 시작하지 않도록 할 수 있습니다. 보다 강력한 동기식 직렬화 된 출력 구현
은 하단참조하십시오 .


예측 가능한 출력 동작을 가진 유일한 간단한 bash 솔루션 은 다음과 같습니다. 그러나 쉘 루프는 본질적으로 느리기 때문에 큰 입력 세트 에서는 엄청나게 느 립니다.
또한 이것은 대상 명령의 출력 행을 대체 합니다 .

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

Unix (GNU 병렬 사용)

GNU를parallel 설치 하면 병렬 실행 을 추가로 허용하는 직렬화 된 (명령 별) 출력 으로 강력한 솔루션 을 사용할 수 있습니다 .

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel기본적으로 다른 명령의 출력이 인터리브되지 않도록합니다 (이 동작은 수정할 수 있습니다-참조 man parallel).

참고 : 일부 Linux 배포판 에는 위의 명령과 함께 작동하지 않는 다른 parallel 유틸리티 가 함께 제공됩니다. parallel --version어떤 것을 가지고 있는지 결정하는 데 사용하십시오 .


윈도우

Jay Bazuzi의 유용한 답변PowerShell 에서 수행하는 방법을 보여줍니다 . 즉, 그의 대답은 bash위 의 루핑 대답 과 유사 하며 큰 입력 세트에서는 엄청나게 느리고 대상 명령의 출력 라인번갈아 나타납니다 .



bash기반이지만 동기 실행 및 출력 직렬화 기능이있는 이식 가능한 Unix 솔루션

다음은 추가로 제공하는 tzot의 답변에 제시된 접근 방식의 간단하지만 합리적으로 강력한 구현입니다 .

  • 동기 실행
  • 직렬화 된 (그룹화 된) 출력

그것이 있기 때문에 엄격하게 POSIX를 준수하면서, bash스크립트, 그것을해야한다 있는 모든 유닉스 플랫폼에 이식bash .

참고 : 이 Gist 에서 MIT 라이선스에 따라 출시 된보다 완전한 구현을 찾을 수 있습니다 .

아래 코드를 script로 저장하고 fanout실행 가능하게 만들고 int를 넣으면 PATH질문의 명령이 다음과 같이 작동합니다.

$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23

fanout스크립트 소스 코드 :

#!/usr/bin/env bash

# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )

# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT 

# Determine the number padding for the sequential FIFO / output-capture names, 
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"

# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
  printf -v suffix "$fmtString" $i
  aFifos[i]="$tmpDir/fifo-$suffix"
  aOutFiles[i]="$tmpDir/out-$suffix"
done

# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit

# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
  fifo=${aFifos[i]}
  outFile=${aOutFiles[i]}
  cmd=${aCmds[i]}
  printf '# %s\n' "$cmd" > "$outFile"
  eval "$cmd" < "$fifo" >> "$outFile" &
done

# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit

# Wait for all background processes to finish.
wait

# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"

5

@dF : PowerShell에 티가 있다고 언급했기 때문에 PowerShell에서이 작업을 수행하는 방법을 보여줄 것이라고 생각했습니다.

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

첫 번째 명령에서 나오는 각 개체는 다음 개체가 생성되기 전에 처리됩니다. 이를 통해 매우 큰 입력으로 확장 할 수 있습니다.


예, 그러나 이것은 while IFS= read -r line; do tr 1 a <<<"$line"; tr 1 b <<<"$line"; done < <(echo '123')Bash에서 수행하는 것과 동일하며 메모리 측면 에서는 확장되지만 성능 측면 에서는 그렇지 않습니다 .
mklement0

1

출력을 변수에 저장하고 다른 프로세스에 사용할 수도 있습니다.

out=$(proc1); echo "$out" | proc2; echo "$out" | proc3

그러나 이는

  1. proc1 어떤 지점에서 종료됩니다 :-)
  2. proc1 너무 많은 출력을 생성하지 않습니다 (제한이 무엇인지 모르지만 아마도 RAM 일 것입니다)

그러나 기억하기 쉽고 거기에서 생성 한 프로세스에서 얻은 출력에 대한 더 많은 옵션을 남깁니다. 예 :

out=$(proc1); echo $(echo "$out" | proc2) / $(echo "$out" | proc3) | bc

나는 | tee >(proc2) >(proc3) >/dev/null접근 방식으로 그런 일을하는 데 어려움을 겪었습니다 .


-1

또 다른 방법은

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

산출:

a23
b23

여기에 서브 쉘을 만들 필요가 없습니다.


이것은 어떤 쉘에서 작동합니까? 존재하지 않는다고 echo 123 |{tr 1 a,tr 1 b}불평하는 모든 것을 평가 {tr하고 여분의 공백을 넣으면 쉼표로 인해 추가 입력을 기다립니다. 쉼표를 세미콜론이나 앰퍼샌드로 변경하면 첫 번째 항목 만 인쇄됩니다.
Jerry Jeremiah

@JerryJeremiah : 확장 (bash , ksh, zsh명령 줄 작성하여) echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'A의 문자열을 해당 문자열을 전달 다음과 eval. 즉, (a) 문자열을 구성하는 과정에서 3 개의 하위 셸을 생성합니다 (1 `...`은 포함 된 파이프 라인의 세그먼트에 대해 1 개 , 그리고 (b) 더 중요한 것은 입력 명령을 복제 하여 별도의 복사본 각 대상에 대한 실행 tr명령을 제외하고 비 효율성에서, 동일한 명령을 실행 두 번 반드시 두 번 같은 출력을 생성하지 않습니다..
mklement0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.