31

Мы все знаем mkfifo и трубопроводы. Первый создает именованный канал, поэтому нужно выбрать имя, скорее всего, с помощью mktemp а затем не забудьте отсоединить. Другой создает анонимный канал, без проблем с именами и удалением, но концы канала привязываются к командам в конвейере, не очень удобно как-то захватывать файловые дескрипторы и использовать их в остальных сценария. В скомпилированной программе я бы просто сделал ret=pipe(filedes) ; в Bash есть exec 5<>file поэтому можно ожидать что-то вроде "exec 5<> -" или "pipe <5 >6" - есть ли что-то подобное в Bash?

6 ответов6

36

Вы можете отсоединить именованный канал сразу после присоединения его к текущему процессу, что практически приводит к анонимному каналу:

# 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>&-

Если вы действительно хотите избежать именованных каналов (например, файловая система доступна только для чтения), ваша идея "получить контроль над дескрипторами файлов" также работает. Обратите внимание, что это специфично для Linux из-за использования procfs.

# 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<&-
22

Хотя ни одна из известных мне оболочек не может создавать трубы без разветвления, у некоторых все же лучше, чем у базовой оболочки.

В 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 .

10

Bash 4 имеет сопроцессы.

Копроцесс выполняется в подоболочке асинхронно, как если бы команда была завершена оператором управления «&», и между исполняющей оболочкой и сопроцессом установлен двусторонний канал.

Формат для сопроцесса:

coproc [NAME] command [redirections] 
3

По состоянию на октябрь 2012 г. эта функциональность еще не существует в Bash, но можно использовать coproc, если все, что вам нужно для безымянных / анонимных каналов, - это поговорить с дочерним процессом. Проблема с coproc на данный момент заключается в том, что, по-видимому, одновременно поддерживается только один. Я не могу понять, почему у coproc такое ограничение. Они должны были улучшить существующий фоновый код задачи (& op), но это вопрос для авторов bash.

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<&-
0

Следующая функция была протестирована с использованием 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() {
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" 
    local "MakeFIFO_directory=" "MakeFIFO_file" "MakeFIFO_trap=$(trap -p EXIT)"
    local "MakeFIFO_handler=${MakeFIFO_trap#*\'}"
    MakeFIFO_CleanUp() {
        rm -rf "$MakeFIFO_directory"
        "$MakeFIFO_handler" 
    }
    MakeFIFO_handler="${MakeFIFO_handler%\'*}"
    [[ -n $MakeFIFO_trap ]] || MakeFIFO_trap="trap EXIT"
    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"
    eval "$MakeFIFO_trap"
    "$MakeFIFO_success" || { echo "$FUNCNAME: $1: Could not create FIFO" >&2; return "1"; }
}

Вышеуказанные функции могут быть объединены в одну функцию, которая будет работать в обеих операционных системах. Ниже приведен пример такой функции. Здесь делается попытка создать действительно анонимный FIFO. В случае неудачи именованный FIFO создается и преобразуется в анонимный FIFO.

MakeFIFO() {
    local "MakeFIFO_success=false" "MakeFIFO_upper=$(ulimit -n)" 
    local "MakeFIFO_directory=" "MakeFIFO_file" "MakeFIFO_trap=$(trap -p EXIT)"
    local "MakeFIFO_handler=${MakeFIFO_trap#*\'}"
    MakeFIFO_CleanUp() {
        rm -rf "$MakeFIFO_directory"
        "$MakeFIFO_handler" 
    }
    MakeFIFO_handler="${MakeFIFO_handler%\'*}"
    [[ -n $MakeFIFO_trap ]] || MakeFIFO_trap="trap EXIT"
    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"
    eval "$MakeFIFO_trap"
    "$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>&-"

Рекомендации:
Создание анонимного канала для последующего использования
Файлы в общедоступных каталогах опасны
Shell Script Security

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .