tee(2)

๐Ÿ’– tee(2) ๐Ÿ’–

okay bestie, so this is the system call manual page for tee โ€” that's <hotpink>tee(2)</hotpink> from the System Calls Manual section ๐Ÿ’… (yes, there's also a tee(1) command and no, they are not the same thing, don't @ me). this whole page lives in the Linux man-pages, and we're gonna get through it together, no cap โœจ


๐ŸŒธ NAME ๐ŸŒธ

tee โ€” duplicating pipe content ๐Ÿซถ

๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library (libc, -lc) โ€” so pretty standard, iconic behavior, lowkey nothing fancy ๐ŸŽ€

โœจ SYNOPSIS โœจ

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <fcntl.h>

ssize_t tee(int fd_in, int fd_out, size_t size, unsigned int flags);
not me needing that #define _GNU_SOURCE first or the compiler just ghosting me ๐Ÿ˜ญ it's the See feature_test_macros(7) thing, keep it bestie.

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

tee() duplicates up to size bytes of data from the pipe referred to by the file descriptor fd_in to the pipe referred to by the file descriptor fd_out. ๐Ÿช„

and here's the gag, the main character twist: it does not consume the data that is duplicated from fd_in. so that data stays put and can be copied by a subsequent splice(2). it's giving "borrow your notes but you still have your notes" energy ๐Ÿ“๐Ÿ’–

flags is a bit mask that is composed by ORing together zero or more of the following values:

  • SPLICE_F_MOVE โ€” currently has no effect for tee(); see splice(2). it's just there, vibing ๐Ÿ™ˆ
  • SPLICE_F_NONBLOCK โ€” do not block on I/O; see splice(2) for further details. the โœจimpatientโœจ option, we love her
  • SPLICE_F_MORE โ€” currently has no effect for tee(), but may be implemented in the future; see splice(2). she's a maybe, a future era ๐Ÿ’ญ
  • SPLICE_F_GIFT โ€” unused for tee(); see vmsplice(2). literally unused, she came to the party with nothing ๐ŸŽ๐Ÿ’€

๐ŸŽ€ RETURN VALUE ๐ŸŽ€

upon successful completion, tee() returns the number of bytes that were duplicated between the input and output. ๐Ÿ“

a return value of 0 means that there was no data to transfer, and it would not make sense to block, because there are no writers connected to the write end of the pipe referred to by fd_in. so 0 = nobody's writing, nothing to do, go home girl ๐ŸŒ™

on error, tee() returns -1 and errno is set to indicate the error. classic ๐Ÿ˜”

๐Ÿ’€ ERRORS ๐Ÿ’€

  • EAGAIN โ€” SPLICE_F_NONBLOCK was specified in flags or one of the file descriptors had been marked as nonblocking (O_NONBLOCK), and the operation would block. she said "i would block but i'm literally not allowed to" ๐Ÿ™…โ™€๏ธ
  • EINVAL โ€” fd_in or fd_out does not refer to a pipe; or fd_in and fd_out refer to the same pipe. bestie these are PIPES only, and they gotta be different pipes, iykyk ๐Ÿšซ
  • ENOMEM โ€” out of memory. the laptop said no ๐ŸŒช๏ธ

๐ŸŒˆ STANDARDS ๐ŸŒˆ

Linux. that's it, that's the standard ๐Ÿ‘‘

โญ HISTORY โญ

Linux 2.6.17, glibc 2.5. a lil retro moment, we respect the elders ๐Ÿ•ฐ๏ธ

๐Ÿ’œ NOTES ๐Ÿ’œ

conceptually, tee() copies the data between the two pipes. but in reality, no real data copying takes place though โ€” under the covers, tee() assigns data to the output by merely grabbing a reference to the input. ๐Ÿซข

the way that it's fake-copying but actually just holding a reference?? efficiency icon fr fr, not a single byte was moved and yet โœจeverythingโœจ happened. no cap.

๐Ÿงš EXAMPLES ๐Ÿงš

the example below implements a basic tee(1) program using the tee() system call. here is an example of its use:

$ date | ./a.out out.log | cat;
Tue Oct 28 10:06:00 CET 2014
$ cat out.log;
Tue Oct 28 10:06:00 CET 2014

see how date's output went into the file and to cat? that's the whole point bestie, oh she ATE ๐Ÿ‘‘

๐Ÿ“ Program source ๐Ÿ“

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    int      fd;
    ssize_t  size, ssize;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    for (;;) {
        /*
         * tee stdin to stdout.
         */
        size = tee(STDIN_FILENO, STDOUT_FILENO,
                  INT_MAX, SPLICE_F_NONBLOCK);
        if (size < 0) {
            if (errno == EAGAIN)
                continue;
            perror("tee");
            exit(EXIT_FAILURE);
        }
        if (size == 0)
            break;

        /*
         * Consume stdin by splicing it to a file.
         */
        while (size > 0) {
            ssize = splice(STDIN_FILENO, NULL, fd, NULL,
                          size, SPLICE_F_MOVE);
            if (ssize < 0) {
                perror("splice");
                exit(EXIT_FAILURE);
            }
            size -= ssize;
        }
    }

    close(fd);
    exit(EXIT_SUCCESS);
}

copy it exactly, bestie, every include, every SSIZE_MAX-adjacent vibe, don't be "fixing" the indentation ๐Ÿ’…

๐Ÿ”ฅ SEE ALSO ๐Ÿ”ฅ

splice(2), vmsplice(2), pipe(7) โ€” the whole friend group, go read them too ๐Ÿซถ


and that's the page!! ๐ŸŒŸ this was Linux man-pages 6.20, dated 2026-02-08 โ€” shoutout to the man-pages girlies for keeping the tee(2) lore alive, ilysm, drink water, and remember: tee() never consumes, she only shares ๐Ÿ’–โœจ๐ŸŽ€๐Ÿฆ‹๐ŸŒธ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms