배쉬 : 익명의 fifo 만들기


38

우리는 모두 mkfifo파이프 라인을 알고 있습니다. 첫 번째는 명명 된 파이프를 생성하므로 이름 을 선택해야하며 mktemp, 나중에 연결을 해제해야합니다. 다른 하나는 익명의 파이프를 생성하고 이름과 제거가 번거롭지 않지만 파이프의 끝은 파이프 라인의 명령에 연결되므로 파일 설명자를 잡고 나머지에서 사용하는 것이 실제로 편리하지 않습니다. 스크립트의. 컴파일 된 프로그램에서, 나는 단지 할 것이다 ret=pipe(filedes); Bash에는 exec 5<>file이와 비슷한 것이 "exec 5<> -"있거나 "pipe <5 >6"Bash에는 이와 비슷한 것이 있습니까?

답변:


42

명명 된 파이프를 현재 프로세스에 연결 한 후 즉시 명명 된 파이프의 연결을 해제 할 수 있으며 실제로 익명 파이프가 생성됩니다.

# create a temporary named pipe
PIPE=$(mktemp -u)
mkfifo $PIPE
# attach it to file descriptor 3
exec 3<>$PIPE
# unlink the named pipe
rm $PIPE
...
# anything we write to fd 3 can be read back from it
echo 'Hello world!' >&3
head -n1 <&3
...
# close the file descriptor when we are finished (optional)
exec 3>&-

명명 된 파이프 (예 : 파일 시스템이 읽기 전용)를 피하고 싶다면 "파일 설명자 파악"아이디어도 효과가 있습니다. 이것은 procfs를 사용하기 때문에 Linux에 따라 다릅니다.

# start a background pipeline with two processes running forever
tail -f /dev/null | tail -f /dev/null &
# save the process ids
PID2=$!
PID1=$(jobs -p %+)
# hijack the pipe's file descriptors using procfs
exec 3>/proc/$PID1/fd/1 4</proc/$PID2/fd/0
# kill the background processes we no longer need
# (using disown suppresses the 'Terminated' message)
disown $PID2
kill $PID1 $PID2
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptors when we are finished (optional)
exec 3>&- 4<&-

당신은 사용되지 않는 파일 기술자를 자동으로 찾는이 결합 할 수 있습니다 : stackoverflow.com/questions/8297415/...
CMCDragonkai에게

23

내가 아는 쉘 중 어느 것도 포크하지 않고 파이프를 만들 수 없지만 일부는 기본 쉘 파이프 라인보다 낫습니다.

bash, ksh 및 zsh에서 시스템 지원 /dev/fd(대부분의 요즘)을 가정하면 명령의 입력 또는 출력을 파일 이름으로 묶을 수 있습니다. 출력에 <(command)연결된 파이프를 지정하는 파일 이름으로 확장 command하고 >(command)확장합니다. 입력에 연결된 파이프를 지정하는 파일 이름으로 command. 이 기능을 프로세스 대체 라고 합니다 . 주요 목적은 하나 이상의 명령을 다른 명령으로 또는 다른 명령으로 파이프하는 것입니다.

diff <(transform <file1) <(transform <file2)
tee >(transform1 >out1) >(transform2 >out2)

이것은 또한 기본 쉘 파이프의 일부 단점을 해결하는 데 유용합니다. 예를 들어, command2 < <(command1)동등 command1 | command2, 그 상태의 것을 제외 command2. 또 다른 사용 사례는 exec > >(postprocessing)전체 스크립트를 내부에 넣는 것보다 읽기 쉽지만 가독성이 높습니다 { ... } | postprocessing.


나는 이것을 diff로 시도했지만 작동했지만 kdiff3 또는 emacs에서는 작동하지 않았습니다. 내 생각에 kdiff3이 읽기 전에 임시 / dev / fd 파일이 제거되고 있다고 생각합니다. 아니면 kdiff3이 파일을 두 번 읽으려고하고 파이프가 한 번만 전송합니까?
Eyal

@Eyal 프로세스 분리와 함께 파일 이름은 파이프 (또는 이러한 마법 변형을 지원하지 않는 Unix 변형의 임시 파일)에 대한 "마술"참조입니다. 마법의 구현 방법은 OS에 따라 다릅니다. Linux는 대상이 유효한 파일 이름이 아닌 "마법"심볼릭 링크로 구현합니다 (와 유사 pipe:[123456]). 이맥스는 심볼릭 링크의 대상이 기존 파일 이름이 아니며 파일을 읽을 수 없을 정도로 혼동합니다 (어쨌든 파일을 읽을 수있는 옵션이있을 수 있습니다). 어쨌든 파일).
Gilles 'SO- 악마 그만'


3

2012 년 10 월 현재이 기능은 Bash에 존재하지 않는 것 같지만 명명되지 않은 / 익명 파이프가 필요한 모든 프로세스가 자식 프로세스와 통신하는 경우 coproc을 사용할 수 있습니다. 이 시점에서 coproc의 문제점은 분명히 한 번에 하나만 지원된다는 것입니다. coproc이 왜이 제한을 받았는지 알 수 없습니다. 기존의 작업 배경 코드 (& op)를 개선해야했지만 bash 작성자에게는 문제입니다.


하나의 공동 프로세스 만 지원되지 않습니다. 간단한 명령을 제공하지 않는 한 이름을 지정할 수 있습니다. 대신 명령 목록을 제공하십시오. coproc THING { dothing; }이제 FD가 들어 와서 둘 다 물건을주고 받고받을 ${THING[*]}수 있습니다 coproc OTHERTHING { dothing; }.
clacke

2
@clacke의 man bash 의 BUGS 제목 아래에 다음과 같이 말합니다. 한 번에 하나의 활성 코 프로세스 만있을 수 있습니다 . 두 번째 coproc을 시작하면 경고가 표시됩니다. 작동하는 것처럼 보이지만 백그라운드에서 무엇이 폭발하는지 모르겠습니다.
Radu C

좋아, 그것은 의도적으로 아니기 때문에 현재 운에 의해서만 작동합니다. 공정한 경고, 감사합니다. :-)
clacke

2

@DavidAnderson의 답변 은 모든 기반을 다루고 훌륭한 보호 수단을 제공하지만 가장 중요한 것은 익명 파이프에 손을들이는 것이 <(:)Linux에 머무르는 한 쉽다는 것 입니다.

따라서 귀하의 질문에 대한 가장 짧고 간단한 답변은 다음과 같습니다.

exec 5<> <(:)

macOS에서는 작동하지 않으며, 리디렉션 될 때까지 명명 된 fifo를 보관할 임시 디렉토리를 만들어야합니다. 다른 BSD에 대해서는 모른다.


당신은 당신의 대답이 리눅스의 버그 때문에 작동한다는 것을 알고 있습니다. 이 버그는 macOS에는 존재하지 않으므로 더 복잡한 솔루션이 필요합니다. 내가 게시 한 최종 버전은 Linux의 버그가 수정 된 경우에도 Linux에서 작동합니다.
David Anderson

@DavidAnderson 당신이 나보다 이것에 대해 더 깊이 알고있는 것 같습니다. Linux 동작이 왜 버그입니까?
clacke

1
exec읽기 전용으로 열린 익명 fifo가 전달 되면 exec사용자 정의 파일 디스크립터를 사용하여이 익명 fifo를 읽고 쓸 수 있도록 열지 않아야합니다. -bash: /dev/fd/5: Permission deniedmacOS에서 발생 하는 메시지가 나타납니다. 버그는 우분투가 동일한 메시지를 생성하지 않는다는 것입니다. 누군가가 문서화를 할 수 있다면 기꺼이 마음을 바꿀 것 exec 5<> <(:)입니다.
데이비드 앤더슨

@DavidAnderson 와우, 정말 매력적입니다. bash가 내부적으로 무언가를하고 있다고 가정했지만 Linux가 open(..., O_RDWR)대체로 제공되는 단방향 파이프 엔드 하나에서 간단하게 수행 할 수 있으며 하나의 FD에서 양방향 파이프로 바꿉니다. 당신은 아마 이것에 의존해서는 안되는 것이 맞을 것입니다. :-D execline의 piperw 를 사용하여 파이프를 생성 한 후 bash로 다시 용도 변경 한 결과 <>: libranet.de/display/0b6b25a8-195c-84af-6ac7-ee6696661765
clacke

중요하지 않지만 우분투에서 전달 된 내용을 보려면 exec 5<>을 입력하십시오 fun() { ls -l $1; ls -lH $1; }; fun <(:).
David 앤더슨

1

다음 기능을 사용하여 테스트했습니다 GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu). 운영 체제는 Ubuntu 18입니다.이 함수는 익명의 FIFO에 필요한 파일 디스크립터 인 단일 매개 변수를 사용합니다.

MakeFIFO() {
    local "MakeFIFO_upper=$(ulimit -n)" 
    if [[ $# -ne 1 || ${#1} -gt ${#MakeFIFO_upper} || -n ${1%%[0-9]*} || 10#$1 -le 2
        || 10#$1 -ge MakeFIFO_upper ]] || eval ! exec "$1<> " <(:) 2>"/dev/null"; then
        echo "$FUNCNAME: $1: Could not create FIFO" >&2
        return "1"
    fi
}

다음 기능을 사용하여 테스트했습니다 GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). 운영 체제는 macOS High Sierra입니다. 이 함수는 이름이 지정된 FIFO 를 생성 한 프로세스에만 알려진 임시 디렉토리에 생성하여 시작 합니다 . 다음으로 파일 디스크립터가 FIFO로 경로 재 지정됩니다. 마지막으로 FIFO는 임시 디렉토리를 삭제하여 파일 이름에서 연결 해제됩니다. 이것은 FIFO를 익명으로 만듭니다.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
        MakeFIFO_file="$MakeFIFO_directory/pipe"
        mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
        ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

위 기능은 두 운영 체제 모두에서 작동하는 단일 기능으로 결합 될 수 있습니다. 아래는 그러한 기능의 예입니다. 여기서는 익명의 FIFO를 만들려고합니다. 실패하면 명명 된 FIFO가 만들어지고 익명의 FIFO로 변환됩니다.

MakeFIFO() {
    MakeFIFO.SetStatus() {
        return "${1:-$?}"
    }
    MakeFIFO.CleanUp() {
        local "MakeFIFO_status=$?"
        rm -rf "${MakeFIFO_directory:-}"    
        unset "MakeFIFO_directory"
        MakeFIFO.SetStatus "$MakeFIFO_status" && true
        eval eval "${MakeFIFO_handler:-:}'; true'" 
    }
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" "MakeFIFO_file=" 
    MakeFIFO_handler="$(trap -p EXIT)"
    MakeFIFO_handler="${MakeFIFO_handler#trap -- }"
    MakeFIFO_handler="${MakeFIFO_handler% *}"
    trap -- 'MakeFIFO.CleanUp' EXIT
    until "$MakeFIFO_success"; do
        [[ $# -eq 1 && ${#1} -le ${#MakeFIFO_upper} && -z ${1%%[0-9]*}
        && 10#$1 -gt 2 && 10#$1 -lt MakeFIFO_upper ]] || break
        if eval ! exec "$1<> " <(:) 2>"/dev/null"; then
            MakeFIFO_directory=$(mktemp -d) 2>"/dev/null" || break
            MakeFIFO_file="$MakeFIFO_directory/pipe"
            mkfifo -m 600 $MakeFIFO_file 2>"/dev/null" || break
            ! eval ! exec "$1<> $MakeFIFO_file" 2>"/dev/null" || break
        fi
        MakeFIFO_success="true"
    done
    rm -rf "${MakeFIFO_directory:-}"
    unset  "MakeFIFO_directory"
    eval trap -- "$MakeFIFO_handler" EXIT
    unset  "MakeFIFO_handler"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

다음은 익명의 FIFO를 만든 다음 동일한 FIFO에 일부 텍스트를 쓰는 예입니다.

fd="6"
MakeFIFO "$fd"
echo "Now is the" >&"$fd"
echo "time for all" >&"$fd"
echo "good men" >&"$fd"

아래는 익명의 FIFO의 전체 내용을 읽는 예입니다.

echo "EOF" >&"$fd"
while read -u "$fd" message; do
    [[ $message != *EOF ]] || break
    echo "$message"
done

다음과 같은 출력이 생성됩니다.

Now is the
time for all
good men

아래 명령은 익명의 FIFO를 닫습니다.

eval exec "$fd>&-"

참조 :
나중에 사용할 수 있도록 익명 파이프 만들기
공개적으로 쓰기 가능한 디렉토리의 파일이 위험한 셸 스크립트 보안


0

htamas의 위대하고 밝은 대답을 사용하여 하나의 라이너에서 사용하도록 약간 수정했습니다.

# create a temporary named pipe
PIPE=(`(exec 0</dev/null 1</dev/null; (( read -d \  e < /proc/self/stat ; echo $e >&2 ; exec tail -f /dev/null 2> /dev/null ) | ( read -d \  e < /proc/self/stat ; echo $e  >&2 ; exec tail -f /dev/null 2> /dev/null )) &) 2>&1 | for ((i=0; i<2; i++)); do read e; printf "$e "; done`)
# attach it to file descriptors 3 and 4
exec 3>/proc/${PIPE[0]}/fd/1 4</proc/${PIPE[1]}/fd/0
...
# kill the temporary pids
kill ${PIPE[@]}
...
# anything we write to fd 3 can be read back from fd 4
echo 'Hello world!' >&3
head -n1 <&4
...
# close the file descriptor when we are finished (optional)
exec 3>&- 4<&-

7
한 줄 짜리 줄에 줄이 여러 개 있다는 것을 알 수 없습니다 .
Dmitry Grigoryev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.