소량의 데이터를 위한 린 솔루션

소량의 데이터를 위한 린 솔루션

Bash 코드에서

command1 | tee >(command2) | command3

command2in의 출력과 var2in의 출력을 캡처하고 싶습니다 .command3var3

command1다른 명령은 I/O 제약으로 인해 비용이 많이 들지만 command1완료되기 전에 작업을 시작할 수 있습니다.

command2합계의 출력 순서 command3는 고정되어 있지 않습니다. 그래서 파일 설명자를 사용해 보았습니다.

read -r var2 <<< var3=(command1 | tee >(command2 >&3) | command3) 3>&1

또는

{read -u 3 -r var2; read -r var3} <<< command1 | tee >(command2 >&3) | command3

그러나 성공하지 못했습니다.

이 세 가지 명령을 병렬로 실행하여 임시 파일을 만들지 않고 결과를 다른 변수에 저장하는 방법이 있습니까?

답변1

그러면 cmd1의 출력을 cmd2 및 cmd3으로 파이프하고 cmd2 및 cmd3의 출력을 다른 변수로 가져오시겠습니까?

따라서 쉘에서 두 개의 파이프가 필요한 것 같습니다. 하나는 cmd2의 출력에, 다른 하나는 cmd3의 출력에 연결되며 쉘은 두 파이프에서 select()/를 사용합니다.poll()

bash그렇게 하지 않을 것입니다. 와 같은 더 높은 수준의 셸이 필요합니다 zsh. zsh원시 인터페이스는 없지만 pipe()Linux에서는 일반 파이프에 대해 명명된 파이프 역할을 한다는 사실을 활용 /dev/fd/x하고 아래와 유사한 것을 사용할 수 있습니다.쉘 리디렉션을 사용하여 동일한 파일 설명자 읽기/쓰기

#! /bin/zsh -

cmd1() seq 20
cmd2() sed 's/1/<&>/g'
cmd3() tr 0-9 A-J

zmodload zsh/zselect
zmodload zsh/system
typeset -A done out
{
  cmd1 > >(cmd2 >&3 3>&-) > >(cmd3 >&5 5>&-) 3>&- 5>&- &
  exec 4< /dev/fd/3 6< /dev/fd/5 3>&- 5>&-
  while ((! (done[4] && done[6]))) && zselect -A ready 4 6; do
    for fd (${(k)ready[(R)*r*]}) {
      sysread -i $fd && out[$fd]+=$REPLY || done[$fd]=1
    }
  done
} 3> >(:) 5> >(:)

printf '%s output: <%s>\n' cmd2 "$out[4]" cmd3 "$out[6]"

답변2

귀하의 모든 요구 사항을 잘 이해하고 있다면 bash각 명령에 대해 명명되지 않은 파이프를 만든 다음 각 명령의 출력을 해당 명명되지 않은 파이프로 리디렉션하고 마지막으로 Retrieve의 각 출력을 별도의 변수로 파이핑하여 이를 달성할 수 있습니다.

따라서 해결책은 다음과 같습니다.

: {pipe2}<> <(:)
: {pipe3}<> <(:)

command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
var2=$(while read -ru ${pipe2} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)
var3=$(while read -ru ${pipe3} line ; do [ "${line}" = EOF ] && break ; echo "${line}" ; done)

exec {pipe2}<&- {pipe3}<&-

여기서 특별한 주의가 필요한 것은 다음과 같습니다.

  • 이 구조의 사용법 <(:)은 "이름이 지정되지 않은" 파이프를 여는 문서화되지 않은 Bash 트릭입니다.
  • 더 이상 출력이 없음을 루프에 echo EOF알리려면 간단한 방법을 사용하십시오 . while이는 이름 없는 파이프(일반적으로 모든 루프가 종료됨 while read)를 닫는 것만으로는 아무 소용이 없기 때문에 필요합니다. 이러한 파이프는 쓰기 및 읽기용으로 양방향이기 때문입니다. 읽기용과 쓰기용으로 하나씩 일반적인 몇 가지 파일 설명자로 열거나 변환하는 방법을 모르겠습니다.

이 예에서는 순수한 bash 접근 방식을 사용했습니다 ( 각 변수 3을 것을 제외하면tee사용하는sedwhilevar2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"

소량의 데이터를 위한 린 솔루션

: {pipe2}<> <(:)
: {pipe3}<> <(:)

command1 | tee >({ command2 ; echo EOF ; } >&${pipe2}) >({ command3 ; echo EOF ; } >&${pipe3}) > /dev/null &
var2="$(sed -ne '/^EOF$/q;p' <&${pipe2})"
var3="$(sed -ne '/^EOF$/q;p' <&${pipe3})"

exec {pipe2}<&- {pipe3}<&-

대상 변수를 표시하려면 다음과 같이 IFS를 지워 토큰화를 비활성화해야 합니다.

IFS=
echo "${var2}"
echo "${var3}"

그렇지 않으면 출력에서 ​​개행 문자가 손실됩니다.

위의 내용은 매우 깨끗한 솔루션처럼 보입니다. 불행히도 너무 높지 않은 출력에만 사용할 수 있으며 마일리지는 다를 수 있습니다. 테스트에서 약 530k의 출력에 문제가 있었습니다. 4k 제한 내에 있다면 (매우 보수적으로) 괜찮을 것입니다.

이 제한의 이유는 이러한 작업이 두 개라는 사실에 있습니다.명령 대체구문은 동기 작업입니다. 즉, tee두 명령을 동시에 제공하고 명령이 수신 버퍼를 채울 때 모든 명령을 차단하는 것과는 반대로 첫 번째 할당이 완료된 후에만 두 번째 할당이 실행됩니다. 이중 자물쇠.

이 문제를 해결하려면 두 버퍼를 동시에 지우려면 약간 더 복잡한 스크립트가 필요합니다. 이렇게 하려면 while두 파이프를 모두 반복합니다.회의편리하게 오세요.

모든 데이터 볼륨에 대한 보다 표준적인 솔루션

더 표준적인 Bashism은 다음과 같습니다.

declare -a var2 var3
while read -r line ; do
   case "${line}" in
   cmd2:*) var2+=("${line#cmd2:}") ;;
   cmd3:*) var3+=("${line#cmd3:}") ;;
   esac
done < <(
   command1 | tee >(command2 | stdbuf -oL sed -re 's/^/cmd2:/') >(command3 | stdbuf -oL sed -re 's/^/cmd3:/') > /dev/null
)

여기서 두 명령의 행을 단일 표준 "stdout" 파일 설명자로 다중화한 다음 출력을 각 해당 변수에 병합하여 역다중화합니다.

주의:

  • 인덱스 배열을 대상 변수로 사용: 이는 일반 변수에 추가하는 것만으로도 출력량이 많아지면 속도가 매우 느려지기 때문입니다.
  • 명령을 사용하여 sed각 출력 줄 앞에 문자열 "cmd2:" 또는 "cmd3:"(각각)을 추가하면 스크립트가 각 줄이 어떤 변수에 속하는지 알 수 있습니다.
  • 명령 출력을 위한 라인 버퍼링의 필수 사용 stdbuf -oL: 이는 여기에 있는 두 명령이 동일한 출력 파일 설명자를 공유하기 때문입니다. 따라서 스트리밍 중인 경우 가장 일반적인 경쟁 조건에서 동시에 서로의 출력을 쉽게 덮어쓸 수 있습니다. 출력 데이터; 라인 버퍼 출력은 이를 방지하는 데 도움이 됩니다.
  • 또한 각 체인의 마지막 명령만 stdbuf, 즉 공유 파일 설명자에 직접 출력하는 명령(이 경우 각 commandX의 출력 앞에 고유 접두사를 추가하는 sed 명령)을 사용해야 합니다.

이러한 인덱스 배열을 올바르게 표시하는 안전한 방법은 다음과 같습니다.

for ((i = 0; i < ${#var2[*]}; i++)) ; do
   echo "${var2[$i]}"
done

물론 직접 사용할 수도 있습니다 "${var2[*]}".

echo "${var2[*]}"

하지만 라인이 많으면 효율성이 그다지 높지 않습니다.

답변3

잘 작동하는 것 같은 것을 찾았습니다.

exec 3<> <(:)
var3=$(command1 | tee >(command2 >&3) | command3)
var2=$(while IFS= read -t .01 -r -u 3 line; do printf '%s\n' "$line"; done)

<(:)파일 설명자 3에 대한 익명 파이프를 설정하고 command2해당 출력을 파이핑하는 방식으로 작동합니다. 파일 설명자 3의 출력을 캡처 var3하고 0.01초 동안 새 데이터가 수신되지 않을 때까지 마지막 줄을 읽습니다.command3

command2익명 파이프에 의해 버퍼링되는 것으로 보이는 최대 65536바이트의 출력에만 작동합니다 .

솔루션의 마지막 줄이 마음에 들지 않습니다. 0.01초 동안 기다렸다가 버퍼가 비자마자 중지하는 것보다 한 번에 모든 내용을 읽는 편이 낫습니다. 하지만 더 좋은 방법은 모르겠습니다.

답변4

4.0부터는 가능합니다. bash는 쉘 예약어 coproc을 추가합니다. 백그라운드로 명령을 따르기 위해 하위 프로세스로 분기되지만(일반적으로 변수 전달이 허용되지 않음) 배열을 생성합니다(기본값은 COPROC이지만 이름을 지정할 수 있음). ${COPROC[0]}은 하위 프로세스 ${COPROC의 표준 입력에 연결됩니다.1이러한 프로세스는 작업으로 작동할 수 있고 비동기식이므로 tee를 사용하여 두 코루틴으로 파이프하고 별도의 파일로 출력한 다음 "${ 코루틴을 먼저 호출할 수 있습니다.1} ${코루틴 초1}" 이는 파이프 안에 있을 필요도 없기 때문에 매우 좋습니다.

bash coproc command_1 { command1 arg1 arg2 arg3 >> command_1_output.txt } coproc command_2 { command2 arg1 arg2 arg3 >> command_2_output.txt } othercommand tee>^${command_11}" >&"${command_21}" 읽기 -r 결과1 <&"${command_1[0]" 읽기 -r resluts2 <&"${command_2[0]" echo "$result1 $result2" | command3 >>결과 병합.txt

언급했듯이 이 솔루션은 아직 완성되지 않았으므로 충격을 받았지만 원칙은 타당하고 위의 답변과 유사합니다. 그러나 주제에 대한 좋은 기사를 안내하고 작업이 가능할 때 이 답변으로 돌아오겠습니다.

coprocess의 변수 설정 예

코루틴 및 명명된 파이프에 대해 자세히 알아보기

용도와 함정

관련 정보