diff --git a/src/content/unix/tee.md b/src/content/unix/tee.md new file mode 100644 index 0000000..be4781c --- /dev/null +++ b/src/content/unix/tee.md @@ -0,0 +1,117 @@ +--- +title: 'Tee Command' +description: 'The powerful shell-stream controller' +updateDate: 'December 29 2023' +--- + +# Unix Tee + +The `tee` command is available on all Unix systems. It takes in any number of +files as arguments and copies the `stdin` to all these files and the `stdout`. +This can be a very helpful command when using Unix pipes. + +## Multi-file Output + +It can be helpful to duplicate the output of a command across multiple files. By +default, shells only provide one stdout stream, so it'll take several commands +or a for-loop to write to several files. + +`tee` provides an alternative. The command below copies the output across 3 +files: + +```bash +echo "something" | tee file1 | tee file2 > file3 +``` + +`tee` can also take in multiple file name arguments, where the output is copied +to all of them. The following command is equivalent to the above: + +```bash +echo "something" | tee file1 file2 file3 > /dev/null +``` + +This can further be further exploited with bash for loops. The following will +copy the message across 100 different files: + +```bash +echo "something" | tee $(for x in {1..100}; do echo file$x; done) >/dev/null +``` + +You can similarly append to files using the `-a` option. This is analogous to +the `bash` syntax `>>`. + +The following command will append to files 1 and 3, but overwrite file 2: + +```bash +echo "something" | tee -a file1 | tee file2 >> file3 +``` + +### Writing Stderr + +Consider this C program, which will write "something" to the `stderr`: + +```c +#include + +int main(void) { + fprintf(stderr, "something\n"); +} +``` + +`tee` only reads from the `stdin`. To use this output with `tee`, we'll need to +first redirect the `stderr` to `stdout`. Suppose the C program above is compiled +into an executable called `a.out`: + +```bash +./a.out 2>&1 | tee file1 file2 +``` + +## Elevating Permissions + +It's common on a single-user system, like your personal computer, to need to +write to files that are write-only for the root user. For example consider the +following command: + +```bash +sudo echo "something" > root_file +``` + +This won't work, as the `sudo` only applies to `echo "something"` not `> +root_file`. One alternative would be to run the entire shell as root: + +```bash +sudo bash -c 'echo "something" > root_file' +``` + +But now you've given root access to the entire command! We only want to give +root access to the "write" operation. `tee` can do just that! + +Both of the following will use the root user to write to the file `root_file`, +but the `echo` command itself will still be run by your user. + +```bash +echo "something" | sudo tee root_file +echo "something" | sudo tee root_file &>/dev/null +``` + +`echo` isn't a very serious application of `tee`, though there are many cases +where we only want to elevate permissions for the write and not the whole +command. + +### Elevating Permissions for Vim + +If you open a file that's inaccessible for writing by your user with Vim, `tee` +can be used to switch to the root user just for writing! + +The following vim command writes your current buffer to the file as the root +user: + +```vim +:w ! sudo tee % +``` + +To deconstruct that command a bit, we start with `:w` which is the write +command. We use `!` to invoke a shell. `:w` will provide the current buffer as +through the stdin to the shell. We run the command `sudo tee %`, where Vim +expands `%` to the current file name. `tee` will overwrite the file name expand +from `%` with the stdin provided by the `:w` command.