적절한 동기화를 사용하여 가능한 경합을 제어해야 하는 것 외에도 bash에서는 데이터 소스를 여러 파이프라인에 동시에 공급하고 나중에 모든 출력을 공통 데이터 싱크에 수집할 수 있습니다.
예를 들어 이메일을 보내기 전에 별도의 프로세스를 통해 이메일의 헤더와 본문을 별도로 전처리하려는 경우 다음과 같이 할 수 있습니다.
cat email.txt \
| { tee >(sed -ne '1,/^$/p' | process_header >&3) \
| sed -e '1,/^$/d' | process_body; } 3>&1 \
| sendmail -oi -- [email protected]
이를 고려하여 이러한 파이프 중 하나의 출력을 사용하여 다른 파이프 중 하나의 명령줄이나 최종 데이터 싱크에 표시하는 방법을 찾고 있습니다. 지금까지 내가 달성할 수 있었던 최고는 명명된 파이프와 두 개의 입력 소스를 허용하는 xargs에 -a 옵션을 사용하는 것입니다.
예를 들어, 이메일 본문의 줄 수를 이메일 제목 줄에 자동으로 추가하려면 다음을 사용합니다.
cat email.txt \
| { tee >(sed -ne '1,/^$/p' >&3; : branch off the header) \
| sed -e '1,/^$/d' \
| tee >(wc -l >~/.fifo); : number of body lines into pipe; } 3>&1 \
| xargs -I% -a ~/.fifo sed -e '1,/^$/{/^Subject:/Is/$/ (%)/}' \
| sendmail ...
( xargs -I% -a /dev/fd/4 4<~/.fifo ...
아래 내용도 참조하세요.) 이 예에서 파일 ~/.fifo
은 mkfifo ~/.fifo
.
그러나 첫 번째 예와 비슷하게 파일 설명자와 리디렉션만 사용하여 명명된 파이프 없이 이 작업을 수행하려고 하면
cat email.txt \
| { tee >(sed -ne '1,/^$/p' >&3) \
| sed -e '1,/^$/d' | tee >(wc -l >&4); } 3>&1 \
| xargs -I% -a /dev/fd/4 sed -e '1,/^$/{/^Subject:/Is/$/ (%)/}' \
| sendmail ...
이로 인해 오류가 발생합니다.
xargs: Cannot open input file ‘/dev/fd/4’: No such file or directory
bash: 4: Bad file descriptor
[고쳐 쓰다:호출 내에서 대체하는 것도 -a /dev/fd/4
작동하지 않습니다. 존재하지 않는다는 불만은 다른 오류로 대체됩니다. 출력()에 대한 fd 4가 입력()에 대한 fd 4에 연결되지 않은 것 같습니다. ]-a <(cat <&4)
xargs
/dev/fd/4
Bad file descriptor
>&4
<&4
/dev/fd/4
리디렉션과 프로세스 확장을 영리하게 조합하여 명명된 파이프를 제거할 수 있는 방법이 있습니까? 물론, 다음과 같이 데이터 소스를 여러 번 지정할 필요는 없습니다.
nol="$(sed -e '1,/^$/d' email.txt | wc -l)"
sed -e "1,/^$/{/^Subject:/Is/$/ ($nol)/}" email.txt | sendmail ...
답변1
명령의 오류는 fd 4가 전혀 열려 있지 않기 때문입니다.
실제로 두 개의 "잘못된 파일 설명자" 메시지가 나타납니다. 하나는 (또는 ) wc -l
에서 하나입니다 .cat <&4
xargs -a /dev/fd/4
fd 4를 열려면 이름 없는 파이프가 필요하지만 Bash에서 이름 없는 파이프를 갖는 유일한 공식 방법은 실제로 명령을 사용하는 것입니다 coproc
.
그러나 특정 사용 사례에는 멋진 바로가기가 있습니다.
가장 간단한 방법: "가짜" 이름 없는 파이프
이 트릭은 Bash v5에 문서화되어 있지 않지만 적어도 v4.3에서는 작동합니다(아직 v5를 테스트할 수 없음).
함께 사용하면 이를 지원하는 시스템에서 임의의 "이름이 지정되지 않은" 파이프를 얻을 수 있는 몇 가지 표준 관용구를 활용합니다. 통과"이름없는 파이프" 내 말은"먼저 또는 동등한 명령을 통해 p
파일 시스템에 파일 유형의 FIFO를 생성할 필요가 없습니다.mkfifo
". (이름이 지정되지 않은 파이프에 대한 이 정의는 정확하지 않지만 명령 셸을 사용할 때 실제로 의미가 있다고 감히 말할 수 있습니다.)
이러한 "이름이 지정되지 않은" 파이프의 사용 사례 예시는 다음과 같습니다.
cat email.txt | ( : {pipe}<> <(:) ; tee >(sed -e '1,/^$/d' | wc -l >&${pipe}) | xargs -I% -a <({ read count ; echo $count; } <&${pipe}) sed -e '1,/^$/{/^Subject:/Is/$/ (%)/}' )
위의 명령줄은 예시 상황에 따라 예상된 결과를 생성해야 합니다.
고장 설명: (명확하게 설명하자면, 복사하여 붙여넣을 때는 작동하지 않습니다)
cat email.txt | \ # pipe data to ...
( \ # a compound statement, which ...
: {pipe}<> <(:) ; \ # ... first opens the unnamed pipe in RW mode and put its fd into the (arbitrary) variable ${pipe}
tee \ # then mirrors the data from main stdin to ...
>( \ # the side processing of main input ...
sed -e '1,/^$/d' | wc -l \ # ... which counts the body lines sending the result ...
>&${pipe} \ # ... to the unnamed pipe
) \
| \ # the tee also pipes all main input to ...
xargs -I% -a \ # an xargs that reads iterative lines from ...
<({ read count ; echo $count ; } <&${pipe}) \ # a compound command that reads the one-single line (being the count provided by wc) from ${pipe} fd, and echoes it back to xargs -a
sed -e \ # that finally executes the sed command which looks for Subject: line in header part
'/1,^$/{/^Subject:/Is/$/ (%)/}' ; \ # to append it with the count number
)
몇 가지 추가 참고 사항:
- 하나는 읽기 측용이고 다른 하나는 쓰기 측용인 일반적인 파이프 쌍을 열 수 있는 방법을 찾지 못했기 때문에 명명되지 않은 파이프 RW를 열어야 합니다.
- 즉, 읽기 부분에 더 이상 데이터가 들어오지 않는다는 일반적인 EOF 이벤트 알림이 있을 수 없으며 다른 방법으로 직접 수행해야 하지만 여기서는 관심 있는 행이 하나만 있다는 사실을 활용할 수 있습니다. 그러니 하나만 있어도
read
충분해요. 반대로, 사이드 채널에서 여러 줄을 읽어야 하는 경우 출력 맨 끝에 추가된 간단한 EOF 문자열과 같은 일종의 대역 내 EOF 알림이 필요합니다xargs -a
. 읽기를 제거합니다. 이는 완전히 가능하지만 명령줄 입력 시간이 상당히 깁니다. 대역 내 EOF 문자열을 제거하는 것도 가능하지만 더 복잡합니다. - 이름이 지정되지 않은 파이프의 관리는 전적으로 귀하에게 달려 있으므로 다음
exec {pipe}<&-
을 통해 명시적으로 닫아야 할 수도 있습니다. 이 예에서는 fd가 하위 프로세스에서 생성되었기 때문에 이 작업을 수행할 필요가 없습니다.
완전성을 기하기 위해 여기에는 coproc
상호 연결된 파일 설명자의 일반적인 쌍을 통해 진정한 이름 없는 파이프를 제공하는 동등한 버전이 사용됩니다.
이름 없는 파이프의 공식적인 방법: coproc
coproc을 사용하는 방법은 여러 가지가 있지만 귀하의 경우 가장 좋은 방법은 다음과 같습니다.
cat email.txt | (coproc cat ; : {input}<&${COPROC[0]} {output}>&${COPROC[1]} ; tee >(sed -e '1,/^$/d' | wc -l >&${output}) | xargs -I% -a <(exec cat <&${input}) sed -e '1,/^$/{/FOO/Is/$/ (%)/}' & )
고장 설명: (명확하게 설명하자면, 복사하여 붙여넣을 때는 작동하지 않습니다)
cat email.txt | \ # pipe data to ...
( \ # a subcommand statement, which ...
coproc cat ; \ # ... first spawns the coprocess, a simple cat command acting as a simple line-oriented bridge
: {cp_output}<&${COPROC[0]} {cp_input}>&${COPROC[1]} ; \ # then copies coproc’s own fds into new ones whose number are put into (arbitrary) variables ${cp_output} and ${cp_input}
tee \ # and then mirrors the data from main stdin to ...
>( \ # the side processing of main input ...
sed -e '1,/^$/d' | wc -l \ # ... which counts the body lines sending the result ...
>&${output} \ # ... to the (bridging) coproc
) \
| \ # the tee also pipes all main input to ...
xargs -I% -a \ # an xargs that reads iterative lines from ...
<(exec cat <&${pipe}) \ # another cat that reads from the coproc bridging the count provided by wc, and echoes it back to xargs -a
sed -e \ # that finally executes the sed command which looks for Subject: line in header part
'/1,^$/{/^Subject:/Is/$/ (%)/}' ; \ # to append it with the count number
)
다시 한 번 메모를 추가하세요.
- coproc의 데이터(예: 프로세스 및 fds)가 대화형 bash로 유출되지 않도록 하위 명령문을 사용하는 것이 좋습니다(이 짐승을 대화형으로 실행한다고 가정합니다!)
- 그렇지 않으면 해당 coproc 데이터 관리는 전적으로 귀하에게 달려 있으므로
exec {cp_input}<&-
또는를 통해 명시적으로 fds를 닫아야 할 수도 있습니다.exec {COPROC[1]}<&-
- coproc에서는 모든 명령을 사용할 수 있지만 두 개의 fd로 구성된 간단한 브리지를 사용하는 것이 편리한 일반적인 솔루션이 될 수 있다는 것을 항상 알고 있습니다
cat
. 그러나 두 작업자 프로세스 중 하나를 coproc 자체에 포함하면 성능을 최적화할 수 있습니다. 예를 들어 전체 명령줄을 많이 재정렬해야 합니다. - Bash v4 문서에 따르면 Bash는 한 번에 하나의 coproc만 지원합니다.
- 그러나 적어도 v4.3에서는 명시적인 경고에도 불구하고 더 많은 coproc을 허용하며 Bash v5 문서에는 제한 사항이 명시되어 있지 않습니다.
- coproc이 더 있는 경우 각 coproc에 대해 명시적인 이름을 사용해야 합니다(자세한 내용은 설명서 참조).
- coproc의 fd는 이 예제에서 사용된 파이프 및 프로세스 대체에서 살아남을 수 있도록 임의의 fd로 이동/복사해야 합니다. 배열은
${COPROC[*]}
하위 프로세스로 내보내지지 않고 자체 fd는 항상 exec에서 닫히기 때문입니다. xargs -a
여기서는 두 표준 입력 모두에서 적극적으로 읽는 이점을 누릴 수 있습니다.그리고따라서 파이프의 버퍼가 채워-a
지지 않도록 하십시오tee
. 그렇지 않으면 교착 상태가 발생하고 이를 방지하려면 좀 더 정교한 방법이 필요합니다.