program tip

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

radiobox 2020. 10. 29. 07:58
반응형

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


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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

나는 시도했다

 proc1 | (proc2 & proc3)

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

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

쓰다

 b23

대신 stdout에

 a23
 b23

편집자 주 :
- >(…)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명령이 있습니다.


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에서도 작동하기에 충분히 오래되었지만 그렇지 않은 경우 부분을 ​​역 따옴표로 묶으십시오 .


유닉스 ( 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의 답변에 제시된 접근 방식의 간단하지만 합리적으로 강력한 구현입니다 .

  • 동기 실행
  • serialized (grouped) output

While not strictly POSIX compliant, because it is a bash script, it should be portable to any Unix platform that has bash.

Note: You can find a more full-fledged implementation released under the MIT license in this Gist.

If you save the code below as script fanout, make it executable and put int your PATH, the command from the question would work as follows:

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

fanout script source code:

#!/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[@]}"

Since @dF: mentioned that PowerShell has tee, I thought I'd show a way to do this in PowerShell.

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

a23
1b3

Note that each object coming out of the first command is processed before the next object is created. This can allow scaling to very large inputs.


another way to do would be,

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

output:

a23
b23

no need to create a subshell here

참고URL : https://stackoverflow.com/questions/60942/how-can-i-send-the-stdout-of-one-process-to-multiple-processes-using-preferably

반응형