Unix(또는 Windows)에서 (가급적 이름이 지정되지 않은) 파이프를 사용하여 한 프로세스의 stdout을 여러 프로세스로 보내려면 어떻게 해야 합니까?

StackOverflow https://stackoverflow.com/questions/60942

문제

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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

나는 노력했다

 proc1 | (proc2 & proc3)

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

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

쓴다

 b23

대신에 stdout으로

 a23
 b23
도움이 되었습니까?

해결책

편집자 주:
- >(…)프로세스 대체 그것은 비표준 쉘 기능 ~의 일부 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

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

Windows에서는 내장 셸에 동등한 기능이 없다고 생각합니다.마이크로소프트의 윈도우 파워셸 가지고있다 tee 그래도 명령해라.

다른 팁

dF가 말했듯이, bash 을 사용하는 것을 허용합니다 >(…) 파일 이름 대신 명령을 실행하는 구성을 만듭니다.(또한 <(…) 대체하기 위해 구성 산출 파일 이름 대신 다른 명령을 사용하지만 지금은 관련이 없으므로 완전성을 위해 언급합니다).

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

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

  • 명령의 출력을 수신해야 하는 프로세스 수를 결정하고 가능한 한 전역 임시 폴더에 FIFO를 생성합니다.
    subprocesses="a b c d"
    mypid=$$
    for i in $subprocesses # this way we are compatible with all sh-derived shells  
    do
        mkfifo /tmp/pipe.$mypid.$i
    done
  • FIFO의 입력을 기다리는 모든 하위 프로세스를 시작하십시오.
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • FIFO에 대한 명령을 실행하십시오.
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • 마지막으로 FIFO를 제거합니다.
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

메모:호환성 이유로 나는 다음을 수행할 것입니다. $(…) 역따옴표를 사용했지만 이 답변을 작성할 수 없었습니다(역따옴표는 SO에서 사용됩니다).일반적으로, $(…) 이전 버전의 ksh에서도 작동할 만큼 오래되었지만, 그렇지 않은 경우 역 따옴표로 묶으십시오.

유닉스(bash, ksh, zsh)

dF.의 답변 포함 씨앗 에 기초한 답변의 tee 그리고 산출 프로세스 대체
(>(...)) 저것 그럴 수도 있고 아닐 수도 있다 귀하의 요구 사항에 따라 작업하십시오.

프로세스 대체는 비표준 (주로) POSIX-FEATURES 전용 쉘과 같은 기능 dash (이것은 다음과 같은 역할을 합니다. /bin/sh 예를 들어 우분투에서) ~ 아니다 지원하다.셸 스크립트 타겟팅 /bin/sh ~해야 한다 ~ 아니다 그들에게 의지하십시오.

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

그만큼 함정 이 접근 방식은 다음과 같습니다.

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

  • ~ 안에 bash 그리고 ksh (반대로 zsh - 단, 아래 예외를 참조하세요):

    • 출력이 도착할 수 있습니다 ~ 후에 명령이 끝났습니다.
    • 후속 명령이 실행되기 시작할 수 있습니다. ~ 전에 프로세스 대체 명령이 완료되었습니다. - bash 그리고 ksh 하다 ~ 아니다 적어도 기본적으로 출력 프로세스 대체 생성 프로세스가 완료될 때까지 기다립니다.
    • jmb dF.의 답변에 대한 의견에 잘 설명되어 있습니다.

명령이 내부에서 시작되었다는 점에 유의하세요. >(...) 원래 껍질에서 분리되어 언제 완료되는지 쉽게 확인할 수 없습니다.그만큼 tee 모든 내용을 작성한 후 완료되지만 대체 프로세스는 여전히 커널 및 파일 I/O의 다양한 버퍼에서 데이터를 소비하고 내부 데이터 처리에 소요되는 시간도 소모하게 됩니다.외부 셸이 하위 프로세스에서 생성된 모든 항목에 계속 의존하면 경쟁 조건이 발생할 수 있습니다.

  • zsh 유일한 껍질이다 하다 기본적으로 출력 프로세스 대체에서 실행되는 프로세스가 완료될 때까지 기다립니다., 제외하고 그렇다면 표준 오류 이는 하나의 경로로 리디렉션됩니다(2> >(...)).

  • ksh (적어도 버전에서는 93u+) 인수 없는 사용을 허용합니다. wait 출력 프로세스 대체 생성 프로세스가 완료될 때까지 기다립니다.
    보류 중인 항목을 기다릴 수 있는 대화형 세션에서는 백그라운드 작업 그러나 역시.

  • bash v4.4+ 기다릴 수 있다 가장 최근에 출력 프로세스 대체를 시작했습니다. wait $!, 그러나 인수가 없음 wait 하다 ~ 아니다 작동하므로 다음 명령에 적합하지 않습니다. 다수의 출력 프로세스 대체.

  • 하지만, bash 그리고 ksh 될 수 있다 강요된 기다리다 명령을 파이핑하여 | cat, 하지만 이렇게 하면 명령이 다음과 같은 방식으로 실행됩니다. 서브쉘. 주의사항:

    • ksh (현재 ksh 93u+)는 전송을 지원하지 않습니다 표준 오류 출력 프로세스 대체(2> >(...));그러한 시도는 조용히 무시하다.

    • 하는 동안 zsh (훌륭하게도) 동기식입니다 기본적으로 (훨씬 더 일반적임) 표준 출력 출력 프로세스 대체, 심지어 | cat 기술로는 동기화할 수 없습니다. 표준 오류 출력 프로세스 대체(2> >(...)).

  • 하지만, 보장한다고 해도 동기 실행, 문제는 예측할 수 없게 인터리브된 출력 유적.

다음 명령은 실행 시 bash 또는 ksh, 문제가 있는 동작을 보여줍니다(보려면 여러 번 실행해야 할 수도 있습니다). 둘 다 증상):그만큼 AFTER 일반적으로 인쇄됩니다 ~ 전에 출력 대체의 출력과 후자의 출력이 예측할 수 없게 인터리브될 수 있습니다.

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

요컨대:

  • 특정 명령별 출력 순서 보장:

    • 어느 것도 아니다 bash ...도 아니다 ksh ...도 아니다 zsh 그것을 지원하십시오.
  • 동기 실행:

    • 다음을 제외하고는 가능합니다. 표준 오류- 소스 출력 프로세스 대체:
      • ~ 안에 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 병렬 사용)

설치 중 암소 비슷한 일종의 영양 parallel 가능하게 강력한 솔루션 ~와 함께 직렬화된(명령별) 출력 추가로 허용하는 것 병렬 실행:

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

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

메모:일부 Linux 배포판에는 다른 parallel 위의 명령으로는 작동하지 않는 유틸리티입니다.사용 parallel --version 어떤 것이 있는지 확인합니다.


윈도우

Jay Bazuzi의 유용한 답변 그것을 수행하는 방법을 보여줍니다 파워셸.그것은 말했다:그의 대답은 루핑과 유사합니다 bash 위의 대답은 그럴 것이다 큰 입력 세트에서는 엄청나게 느립니다. 그리고 또한 대체 대상 명령의 출력 라인.



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

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

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

POSIX를 엄격하게 준수하지는 않지만 bash 스크립트, 그래야만 해 다음을 갖춘 모든 Unix 플랫폼으로 이식 가능 bash.

메모:MIT 라이센스에 따라 릴리스된 보다 완전한 구현을 다음에서 찾을 수 있습니다. 이 요점.

아래 코드를 스크립트로 저장하면 fanout, 실행 가능하게 만들고 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[@]}"

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

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

a23
1b3

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

또 다른 방법은 다음과 같습니다.

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

산출:

a23
b23

여기서 서브쉘을 생성할 필요가 없습니다

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top