OS X / Linux: труба на два процесса?
Я знаю о
program1 | program2
а также
program1 | tee outputfile | program2
но есть ли способ передать выходные данные program1 как в program2, так и в program3?
6 ответов
Вы можете сделать это с tee
и процесс замены.
program1 | tee >(program2) >(program3)
Выход из program1
будет направлено к тому, что находится внутри ( )
, в этом случае program2
а также program3
,
Введение о распараллеливании
Это кажется тривиальным, но выполнение этого не только возможно, но и это приведет к одновременному или одновременному процессу.
Возможно, вам придется позаботиться о некоторых конкретных эффектах, таких как порядок выполнения, время выполнения и т. Д.
В конце этого поста есть образец.
Совместимый ответ первым
Поскольку этот вопрос помечен как shell и unix, я сначала дам POSIX-совместимый ответ. (для bashisms, идти дальше.)
Да, есть способ использовать неназванные каналы.
В этом примере я сгенерирую диапазон из 100000 чисел, рандомизирую их и сожму результат, используя 4 различных инструмента сжатия, чтобы сравнить степень сжатия...
Для этого я сначала проведу подготовку:
GZIP_CMD=`which gzip`
BZIP2_CMD=`which bzip2`
LZMA_CMD=`which lzma`
XZ_CMD=`which xz`
MD5SUM_CMD=`which md5sum`
SED_CMD=`which sed`
Примечание: указание полного пути к командам не позволяет некоторому интерпретатору оболочки (например, busybox) запускать встроенный компрессор. А выполнение этого способа гарантирует, что один и тот же синтаксис будет работать независимо от установки ОС (пути могут быть разными для MacO, Ubuntu, RedHat, HP-Ux и т. Д.).
Синтаксис NN>&1
(где NN - число от 3 до 63) генерирует неназванный канал, который может найти в /dev/fd/NN
, (Файловые дескрипторы с 0 по 2 уже открыты для 0: STDIN, 1: STDOUT и 2: STDERR).
Попробуйте это (протестировано под dash, busybox и bash):
(((( seq 1 100000 | shuf | tee /dev/fd/4 /dev/fd/5 /dev/fd/6 /dev/fd/7 | $GZIP_CMD >/tmp/tst.gz ) 4>&1 | $BZIP2_CMD >/tmp/tst.bz2 ) 5>&1 | $LZMA_CMD >/tmp/tst.lzma ) 6>&1 | $XZ_CMD >/tmp/tst.xz ) 7>&1 | $MD5SUM_CMD
или более читаемый:
GZIP_CMD=`which gzip`
BZIP2_CMD=`which bzip2`
LZMA_CMD=`which lzma`
XZ_CMD=`which xz`
MD5SUM_CMD=`which md5sum`
(
(
(
(
seq 1 100000 |
shuf |
tee /dev/fd/4 /dev/fd/5 /dev/fd/6 /dev/fd/7 |
$GZIP_CMD >/tmp/tst.gz
) 4>&1 |
$BZIP2_CMD >/tmp/tst.bz2
) 5>&1 |
$LZMA_CMD >/tmp/tst.lzma
) 6>&1 |
$XZ_CMD >/tmp/tst.xz
) 7>&1 |
$MD5SUM_CMD
2e67f6ad33745dc5134767f0954cbdd6 -
Как shuf
сделать случайное размещение, если вы попробуете это, вы должны получить другой результат,
ls -ltrS /tmp/tst.*
-rw-r--r-- 1 user user 230516 oct 1 22:14 /tmp/tst.bz2
-rw-r--r-- 1 user user 254811 oct 1 22:14 /tmp/tst.lzma
-rw-r--r-- 1 user user 254892 oct 1 22:14 /tmp/tst.xz
-rw-r--r-- 1 user user 275003 oct 1 22:14 /tmp/tst.gz
но вы должны быть в состоянии сравнить контрольные суммы md5:
SED_CMD=`which sed`
for chk in gz:$GZIP_CMD bz2:$BZIP2_CMD lzma:$LZMA_CMD xz:$XZ_CMD;do
${chk#*:} -d < /tmp/tst.${chk%:*} |
$MD5SUM_CMD |
$SED_CMD s/-$/tst.${chk%:*}/
done
2e67f6ad33745dc5134767f0954cbdd6 tst.gz
2e67f6ad33745dc5134767f0954cbdd6 tst.bz2
2e67f6ad33745dc5134767f0954cbdd6 tst.lzma
2e67f6ad33745dc5134767f0954cbdd6 tst.xz
Использование функций bash
При использовании некоторых bashim это может выглядеть лучше, для примера использования /dev/fd/{4,5,6,7}
, вместо tee /dev/fd/4 /dev/fd/5 /...
(((( seq 1 100000 | shuf | tee /dev/fd/{4,5,6,7} | gzip >/tmp/tst.gz ) 4>&1 |
bzip2 >/tmp/tst.bz2 ) 5>&1 | lzma >/tmp/tst.lzma ) 6>&1 |
xz >/tmp/tst.xz ) 7>&1 | md5sum
29078875555e113b31bd1ae876937d4b -
будет работать так же.
Окончательная проверка
Это не создаст никакого файла, но позволит сравнить размер сжатого диапазона отсортированных целых чисел между 4 различными инструментами сжатия (для развлечения я использовал 4 различных способа форматирования вывода):
(
(
(
(
(
seq 1 100000 |
tee /dev/fd/{4,5,6,7} |
gzip |
wc -c |
sed s/^/gzip:\ \ / >&3
) 4>&1 |
bzip2 |
wc -c |
xargs printf "bzip2: %s\n" >&3
) 5>&1 |
lzma |
wc -c |
perl -pe 's/^/lzma: /' >&3
) 6>&1 |
xz |
wc -c |
awk '{printf "xz: %9s\n",$1}' >&3
) 7>&1 |
wc -c
) 3>&1
gzip: 215157
bzip2: 124009
lzma: 17948
xz: 17992
588895
Это демонстрирует, как использовать stdin и stdout, перенаправленные в subshell и объединенные в консоли для окончательного вывода.
Синтаксис >(...)
а также <(...)
Последние версии bash позволяют использовать новую синтаксическую функцию.
seq 1 100000 | wc -l
100000
seq 1 100000 > >( wc -l )
100000
wc -l < <( seq 1 100000 )
100000
Как |
это безымянная труба /dev/fd/0
Синтаксис <()
генерировать временный безымянный канал с дескриптором файла других /dev/fd/XX
,
md5sum <(zcat /tmp/tst.gz) <(bzcat /tmp/tst.bz2) <(
lzcat /tmp/tst.lzma) <(xzcat /tmp/tst.xz)
29078875555e113b31bd1ae876937d4b /dev/fd/63
29078875555e113b31bd1ae876937d4b /dev/fd/62
29078875555e113b31bd1ae876937d4b /dev/fd/61
29078875555e113b31bd1ae876937d4b /dev/fd/60
Более сложная демонстрация
Это требует GNU file
утилита для установки. Определит команду для запуска по расширению или типу файла.
for file in /tmp/tst.*;do
cmd=$(which ${file##*.}) || {
cmd=$(file -b --mime-type $file)
cmd=$(which ${cmd#*-})
}
read -a md5 < <($cmd -d <$file|md5sum)
echo $md5 \ $file
done
29078875555e113b31bd1ae876937d4b /tmp/tst.bz2
29078875555e113b31bd1ae876937d4b /tmp/tst.gz
29078875555e113b31bd1ae876937d4b /tmp/tst.lzma
29078875555e113b31bd1ae876937d4b /tmp/tst.xz
Это позволит вам сделать то же самое, используя следующий синтаксис:
seq 1 100000 |
shuf |
tee >(
echo gzip. $( gzip | wc -c )
) >(
echo gzip, $( wc -c < <(gzip))
) >(
gzip | wc -c | sed s/^/gzip:\ \ /
) >(
bzip2 | wc -c | xargs printf "bzip2: %s\n"
) >(
lzma | wc -c | perl -pe 's/^/lzma: /'
) >(
xz | wc -c | awk '{printf "xz: %9s\n",$1}'
) > >(
echo raw: $(wc -c)
) |
xargs printf "%-8s %9d\n"
raw: 588895
xz: 254556
lzma: 254472
bzip2: 231111
gzip: 274867
gzip, 274867
gzip. 274867
Обратите внимание, я использовал другой способ, используемый для вычисления gzip
сжатый счет
Примечание. Поскольку эта операция выполнялась одновременно, порядок вывода будет зависеть от времени, необходимого для каждой команды.
Идти дальше о распараллеливании
Если вы используете какой-нибудь многоядерный или многопроцессорный компьютер, попробуйте сравнить это:
i=1
time for file in /tmp/tst.*;do
cmd=$(which ${file##*.}) || {
cmd=$(file -b --mime-type $file)
cmd=$(which ${cmd#*-})
}
read -a md5 < <($cmd -d <$file|md5sum)
echo $((i++)) $md5 \ $file
done |
cat -n
который может сделать:
1 1 29078875555e113b31bd1ae876937d4b /tmp/tst.bz2
2 2 29078875555e113b31bd1ae876937d4b /tmp/tst.gz
3 3 29078875555e113b31bd1ae876937d4b /tmp/tst.lzma
4 4 29078875555e113b31bd1ae876937d4b /tmp/tst.xz
real 0m0.101s
с этим:
time (
i=1 pids=()
for file in /tmp/tst.*;do
cmd=$(which ${file##*.}) || {
cmd=$(file -b --mime-type $file)
cmd=$(which ${cmd#*-})
}
(
read -a md5 < <($cmd -d <$file|md5sum)
echo $i $md5 \ $file
) & pids+=($!)
((i++))
done
wait ${pids[@]}
) |
cat -n
мог бы дать:
1 2 29078875555e113b31bd1ae876937d4b /tmp/tst.gz
2 1 29078875555e113b31bd1ae876937d4b /tmp/tst.bz2
3 4 29078875555e113b31bd1ae876937d4b /tmp/tst.xz
4 3 29078875555e113b31bd1ae876937d4b /tmp/tst.lzma
real 0m0.070s
где порядок зависит от типа, используемого каждой вилкой.
Вы всегда можете попытаться сохранить выходные данные program1 в файл, а затем передать их на вход program2 и program3.
program1 > temp; program2 < temp; program3 < temp;
Другие ответы вводят понятие. Вот фактическая демонстрация:
$ echo "Leeroy Jenkins" | tee >(md5sum > out1) >(sha1sum > out2) > out3
$ cat out1
11e001d91e4badcff8fe22aea05a7458 -
$ echo "Leeroy Jenkins" | md5sum
11e001d91e4badcff8fe22aea05a7458 -
$ cat out2
5ed25619ce04b421fab94f57438d6502c66851c1 -
$ echo "Leeroy Jenkins" | sha1sum
5ed25619ce04b421fab94f57438d6502c66851c1 -
$ cat out3
Leeroy Jenkins
Конечно вы можете > /dev/null
вместо out3.
bash
Руководство упоминает, как оно эмулирует >(...)
синтаксис с использованием именованных каналов или дескрипторов именованных файлов, поэтому, если вы не хотите зависеть от bash
Возможно, вы могли бы сделать это вручную в вашем скрипте.
mknod FIFO
program3 < FIFO &
program1 | tee FIFO | program2
wait
rm FIFO
Использование (;)
синтаксис... попробуй ps aux | (head -n 1; tail -n 1)