Я знаю о
program1 | program2
и
program1 | tee outputfile | program2
но есть ли способ передать выход program1 как в program2, так и в program3?
Я знаю о
program1 | program2
и
program1 | tee outputfile | program2
но есть ли способ передать выход program1 как в program2, так и в program3?
Вы можете сделать это с помощью tee
и заменить процесс.
program1 | tee >(program2) >(program3)
Вывод program1
будет передан на все, что находится внутри ( )
, в этом случае program2
и program3
.
Это кажется тривиальным, но делать это не только возможно, но и это приведет к одновременному или одновременному процессу.
Возможно, вам придется позаботиться о некоторых конкретных эффектах, таких как порядок выполнения, время выхода и т.д.
В конце этого сообщения есть несколько примеров.
По мере того, как этот вопрос помечен shell и unix, я сначала поставлю POSIX совместимый ответ. (для багизмов, идите дальше.)
Да, есть способ использовать неназванные каналы.
В этом примере я буду генерировать диапазон 100 000 чисел, рандомизировать их и сжать результат с помощью 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) запускать встроенный компрессор. И делать путь обеспечит тот же синтаксис, который будет выполняться независимо от установки os (пути могут быть разными между MacOS, 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
Используя некоторые bashims, это может выглядеть лучше, для использования образца /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, перенаправленные в подоболочку и, объединенные в консоли для окончательного вывода.
>(...)
и <(...)
Последние 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
где порядок зависит от типа, используемого каждой вилкой.
Вы всегда можете попытаться сохранить вывод программы1 в файл, а затем подать его на входы program2 и program3.
program1 > temp; program2 < temp; program3 < temp;
В руководстве bash
упоминается, как он эмулирует синтаксис >(...)
, используя либо именованные каналы, либо именованные файловые дескрипторы, поэтому, если вы не хотите зависеть от bash
, возможно, вы можете сделать это вручную в своем script.
mknod FIFO
program3 < FIFO &
program1 | tee FIFO | program2
wait
rm FIFO
Другие ответы вводят понятие. Вот настоящая демонстрация:
$ 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.
использовать синтаксис (;)
... try ps aux | (head -n 1; tail -n 1)